2022-08-07 21:16:10 +02:00
|
|
|
import { isServerRuntime } from '../../server/config-shared'
|
2022-05-20 14:24:00 +02:00
|
|
|
import type { NextConfig } from '../../server/config-shared'
|
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
|
|
|
import type { Middleware, RouteHas } from '../../lib/load-custom-routes'
|
2022-07-21 21:56:52 +02:00
|
|
|
import {
|
|
|
|
extractExportedConstValue,
|
|
|
|
UnsupportedValueError,
|
|
|
|
} from './extract-const-value'
|
2022-05-20 14:24:00 +02:00
|
|
|
import { parseModule } from './parse-module'
|
|
|
|
import { promises as fs } from 'fs'
|
2022-06-03 18:35:44 +02:00
|
|
|
import { tryToParsePath } from '../../lib/try-to-parse-path'
|
2022-06-13 20:17:44 +02:00
|
|
|
import * as Log from '../output/log'
|
2022-06-27 03:02:24 +02:00
|
|
|
import { SERVER_RUNTIME } from '../../lib/constants'
|
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
|
|
|
import { ServerRuntime } from 'next/types'
|
|
|
|
import { checkCustomRoutes } from '../../lib/load-custom-routes'
|
feat(edge): allows configuring Dynamic code execution guard (#39539)
### 📖 What's in there?
Dynamic code evaluation (`eval()`, `new Function()`, ...) is not
supported on the edge runtime, hence why we fail the build when
detecting such statement in the middleware or `experimental-edge` routes
at build time.
However, there could be false positives, which static analysis and
tree-shaking can not exclude:
- `qs` through these dependencies (get-intrinsic:
[source](https://github.com/ljharb/get-intrinsic/blob/main/index.js#L12))
- `function-bind`
([source](https://github.com/Raynos/function-bind/blob/master/implementation.js#L42))
- `has`
([source](https://github.com/tarruda/has/blob/master/src/index.js#L5))
This PR leverages the existing `config` export to let user allow some of
their files.
it’s meant for allowing users to import 3rd party modules who embed
dynamic code evaluation, but do not use it (because or code paths), and
can't be tree-shaked.
By default, it’s keeping the existing behavior: warn in dev, fails to
build.
If users allow dynamic code, and that code is reached at runtime, their
app stills breaks.
### 🧪 How to test?
- (existing) integration tests for disallowing dynamic code evaluation:
`pnpm testheadless --testPathPattern=runtime-dynamic`
- (new) integration tests for allowing dynamic code evaluation: `pnpm
testheadless --testPathPattern=runtime-configurable`
- (amended) production tests for validating the new configuration keys:
`pnpm testheadless --testPathPattern=config-validations`
To try it live, you could have an application such as:
```js
// lib/index.js
/* eslint-disable no-eval */
export function hasUnusedDynamic() {
if ((() => false)()) {
eval('100')
}
}
export function hasDynamic() {
eval('100')
}
// pages/index.jsx
export default function Page({ edgeRoute }) {
return <p>{edgeRoute}</p>
}
export const getServerSideProps = async (req) => {
const res = await fetch(`http://localhost:3000/api/route`)
const data = await res.json()
return { props: { edgeRoute: data.ok ? `Hi from the edge route` : '' } }
}
// pages/api/route.js
import { hasDynamic } from '../../lib'
export default async function handle() {
hasDynamic()
return Response.json({ ok: true })
}
export const config = {
runtime: 'experimental-edge' ,
allowDynamic: '/lib/**'
}
```
Playing with `config.allowDynamic`, you should be able to:
- build the app even if it uses `eval()` (it will obviously fail at
runtime)
- build the app that _imports but does not use_ `eval()`
- run the app in dev, even if it uses `eval()` with no warning
### 🆙 Notes to reviewers
Before adding documentation and telemetry, I'd like to collect comments
on a couple of points:
- the overall design for this feature: is a list of globs useful and
easy enough?
- should the globs be relative to the application root (current
implementation) to to the edge route/middleware file?
- (especially to @sokra) is the implementation idiomatic enough? I've
leverage loaders to read the _entry point_ configuration once, then the
ModuleGraph to get it back during the parsing phase. I couldn't re-use
the existing `getExtractMetadata()` facility since it's happening late
after the parsing.
- there's a glitch with `import { ServerRuntime } from '../../types'` in
`get-page-static-info.ts`
([here](https://github.com/vercel/next.js/pull/39539/files#diff-cb7ac6392c3dd707c5edab159c3144ec114eafea92dad5d98f4eedfc612174d2L12)).
I had to use `next/types` because it was failing during lint. Any clue
why?
### ☑️ Checklist
- [ ] Implements an existing feature request or RFC. Make sure the
feature request has been accepted for implementation before opening a
PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [x] Documentation added
- [x] Telemetry added. In case of a feature if it's used or not.
- [x] Errors have helpful link attached, see `contributing.md`
2022-09-13 00:01:00 +02:00
|
|
|
import { matcher } from 'next/dist/compiled/micromatch'
|
2022-09-18 02:00:16 +02:00
|
|
|
import { RSC_MODULE_TYPES } from '../../shared/lib/constants'
|
2022-06-03 18:35:44 +02:00
|
|
|
|
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
|
|
|
export interface MiddlewareConfig {
|
|
|
|
matchers: MiddlewareMatcher[]
|
2022-09-13 00:32:18 +02:00
|
|
|
unstable_allowDynamicGlobs: string[]
|
2022-09-27 19:30:15 +02:00
|
|
|
regions: string[] | string
|
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface MiddlewareMatcher {
|
|
|
|
regexp: string
|
|
|
|
locale?: false
|
|
|
|
has?: RouteHas[]
|
2022-06-03 18:35:44 +02:00
|
|
|
}
|
2022-05-20 14:24:00 +02:00
|
|
|
|
|
|
|
export interface PageStaticInfo {
|
2022-06-27 03:02:24 +02:00
|
|
|
runtime?: ServerRuntime
|
2022-05-20 14:24:00 +02:00
|
|
|
ssg?: boolean
|
|
|
|
ssr?: boolean
|
2022-09-18 02:00:16 +02:00
|
|
|
rsc?: RSCModuleType
|
2022-06-03 18:35:44 +02:00
|
|
|
middleware?: Partial<MiddlewareConfig>
|
2022-05-20 14:24:00 +02:00
|
|
|
}
|
|
|
|
|
2022-09-18 02:00:16 +02:00
|
|
|
const CLIENT_MODULE_LABEL = `/* __next_internal_client_entry_do_not_use__ */`
|
|
|
|
export type RSCModuleType = 'server' | 'client'
|
|
|
|
export function getRSCModuleType(source: string): RSCModuleType {
|
|
|
|
return source.includes(CLIENT_MODULE_LABEL)
|
|
|
|
? RSC_MODULE_TYPES.client
|
|
|
|
: RSC_MODULE_TYPES.server
|
|
|
|
}
|
|
|
|
|
2022-05-20 14:24:00 +02:00
|
|
|
/**
|
|
|
|
* Receives a parsed AST from SWC and checks if it belongs to a module that
|
|
|
|
* requires a runtime to be specified. Those are:
|
|
|
|
* - Modules with `export function getStaticProps | getServerSideProps`
|
|
|
|
* - Modules with `export { getStaticProps | getServerSideProps } <from ...>`
|
2022-10-20 02:39:25 +02:00
|
|
|
* - Modules with `export const runtime = ...`
|
2022-05-20 14:24:00 +02:00
|
|
|
*/
|
2022-10-20 02:39:25 +02:00
|
|
|
function checkExports(swcAST: any): {
|
|
|
|
ssr: boolean
|
|
|
|
ssg: boolean
|
|
|
|
runtime?: string
|
|
|
|
} {
|
2022-05-20 14:24:00 +02:00
|
|
|
if (Array.isArray(swcAST?.body)) {
|
|
|
|
try {
|
2022-10-20 02:39:25 +02:00
|
|
|
let runtime: string | undefined
|
|
|
|
let ssr: boolean = false
|
|
|
|
let ssg: boolean = false
|
|
|
|
|
2022-05-20 14:24:00 +02:00
|
|
|
for (const node of swcAST.body) {
|
2022-10-20 02:39:25 +02:00
|
|
|
if (
|
|
|
|
node.type === 'ExportDeclaration' &&
|
|
|
|
node.declaration?.type === 'VariableDeclaration'
|
|
|
|
) {
|
|
|
|
const id = node.declaration?.declarations[0]?.id.value
|
|
|
|
if (id === 'runtime') {
|
|
|
|
runtime = node.declaration?.declarations[0]?.init.value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-20 14:24:00 +02:00
|
|
|
if (
|
|
|
|
node.type === 'ExportDeclaration' &&
|
|
|
|
node.declaration?.type === 'FunctionDeclaration' &&
|
|
|
|
['getStaticProps', 'getServerSideProps'].includes(
|
|
|
|
node.declaration.identifier?.value
|
|
|
|
)
|
|
|
|
) {
|
2022-10-20 02:39:25 +02:00
|
|
|
ssg = node.declaration.identifier.value === 'getStaticProps'
|
|
|
|
ssr = node.declaration.identifier.value === 'getServerSideProps'
|
2022-05-20 14:24:00 +02:00
|
|
|
}
|
|
|
|
|
2022-09-07 18:28:15 +02:00
|
|
|
if (
|
|
|
|
node.type === 'ExportDeclaration' &&
|
|
|
|
node.declaration?.type === 'VariableDeclaration'
|
|
|
|
) {
|
|
|
|
const id = node.declaration?.declarations[0]?.id.value
|
|
|
|
if (['getStaticProps', 'getServerSideProps'].includes(id)) {
|
2022-10-20 02:39:25 +02:00
|
|
|
ssg = id === 'getStaticProps'
|
|
|
|
ssr = id === 'getServerSideProps'
|
2022-09-07 18:28:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-20 14:24:00 +02:00
|
|
|
if (node.type === 'ExportNamedDeclaration') {
|
|
|
|
const values = node.specifiers.map(
|
|
|
|
(specifier: any) =>
|
|
|
|
specifier.type === 'ExportSpecifier' &&
|
|
|
|
specifier.orig?.type === 'Identifier' &&
|
|
|
|
specifier.orig?.value
|
|
|
|
)
|
|
|
|
|
2022-10-20 02:39:25 +02:00
|
|
|
ssg = values.some((value: any) => ['getStaticProps'].includes(value))
|
|
|
|
ssr = values.some((value: any) =>
|
|
|
|
['getServerSideProps'].includes(value)
|
|
|
|
)
|
2022-05-20 14:24:00 +02:00
|
|
|
}
|
|
|
|
}
|
2022-10-20 02:39:25 +02:00
|
|
|
|
|
|
|
return { ssr, ssg, runtime }
|
2022-05-20 14:24:00 +02:00
|
|
|
} catch (err) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
return { ssg: false, ssr: false }
|
|
|
|
}
|
|
|
|
|
|
|
|
async function tryToReadFile(filePath: string, shouldThrow: boolean) {
|
|
|
|
try {
|
|
|
|
return await fs.readFile(filePath, {
|
|
|
|
encoding: 'utf8',
|
|
|
|
})
|
|
|
|
} catch (error) {
|
|
|
|
if (shouldThrow) {
|
|
|
|
throw error
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-06-03 18:35:44 +02:00
|
|
|
|
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
|
|
|
function getMiddlewareMatchers(
|
2022-06-21 00:34:03 +02:00
|
|
|
matcherOrMatchers: unknown,
|
|
|
|
nextConfig: NextConfig
|
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
|
|
|
): MiddlewareMatcher[] {
|
|
|
|
let matchers: unknown[] = []
|
2022-06-03 18:35:44 +02:00
|
|
|
if (Array.isArray(matcherOrMatchers)) {
|
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
|
|
|
matchers = matcherOrMatchers
|
|
|
|
} else {
|
|
|
|
matchers.push(matcherOrMatchers)
|
2022-06-03 18:35:44 +02:00
|
|
|
}
|
2022-08-10 00:58:40 +02:00
|
|
|
const { i18n } = nextConfig
|
2022-06-03 18:35:44 +02:00
|
|
|
|
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
|
|
|
let routes = matchers.map(
|
|
|
|
(m) => (typeof m === 'string' ? { source: m } : m) as Middleware
|
|
|
|
)
|
2022-06-03 18:35:44 +02:00
|
|
|
|
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
|
|
|
// check before we process the routes and after to ensure
|
|
|
|
// they are still valid
|
|
|
|
checkCustomRoutes(routes, 'middleware')
|
2022-06-03 18:35:44 +02:00
|
|
|
|
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
|
|
|
routes = routes.map((r) => {
|
|
|
|
let { source } = r
|
2022-06-03 18:35:44 +02:00
|
|
|
|
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
|
|
|
const isRoot = source === '/'
|
2022-06-21 00:34:03 +02:00
|
|
|
|
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
|
|
|
if (i18n?.locales && r.locale !== false) {
|
|
|
|
source = `/:nextInternalLocale([^/.]{1,})${isRoot ? '' : source}`
|
|
|
|
}
|
2022-08-10 00:58:40 +02:00
|
|
|
|
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
|
|
|
source = `/:nextData(_next/data/[^/]{1,})?${source}${
|
|
|
|
isRoot
|
|
|
|
? `(${nextConfig.i18n ? '|\\.json|' : ''}/?index|/?index\\.json)?`
|
|
|
|
: '(.json)?'
|
|
|
|
}`
|
2022-08-10 00:58:40 +02:00
|
|
|
|
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
|
|
|
if (nextConfig.basePath) {
|
|
|
|
source = `${nextConfig.basePath}${source}`
|
|
|
|
}
|
2022-06-03 18:35:44 +02:00
|
|
|
|
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
|
|
|
return { ...r, source }
|
|
|
|
})
|
|
|
|
|
|
|
|
checkCustomRoutes(routes, 'middleware')
|
|
|
|
|
|
|
|
return routes.map((r) => {
|
|
|
|
const { source, ...rest } = r
|
|
|
|
const parsedPage = tryToParsePath(source)
|
|
|
|
|
|
|
|
if (parsedPage.error || !parsedPage.regexStr) {
|
|
|
|
throw new Error(`Invalid source: ${source}`)
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
...rest,
|
|
|
|
regexp: parsedPage.regexStr,
|
|
|
|
}
|
|
|
|
})
|
2022-06-03 18:35:44 +02:00
|
|
|
}
|
2022-06-13 20:17:44 +02:00
|
|
|
|
2022-08-15 16:29:51 +02:00
|
|
|
function getMiddlewareConfig(
|
feat(edge): allows configuring Dynamic code execution guard (#39539)
### 📖 What's in there?
Dynamic code evaluation (`eval()`, `new Function()`, ...) is not
supported on the edge runtime, hence why we fail the build when
detecting such statement in the middleware or `experimental-edge` routes
at build time.
However, there could be false positives, which static analysis and
tree-shaking can not exclude:
- `qs` through these dependencies (get-intrinsic:
[source](https://github.com/ljharb/get-intrinsic/blob/main/index.js#L12))
- `function-bind`
([source](https://github.com/Raynos/function-bind/blob/master/implementation.js#L42))
- `has`
([source](https://github.com/tarruda/has/blob/master/src/index.js#L5))
This PR leverages the existing `config` export to let user allow some of
their files.
it’s meant for allowing users to import 3rd party modules who embed
dynamic code evaluation, but do not use it (because or code paths), and
can't be tree-shaked.
By default, it’s keeping the existing behavior: warn in dev, fails to
build.
If users allow dynamic code, and that code is reached at runtime, their
app stills breaks.
### 🧪 How to test?
- (existing) integration tests for disallowing dynamic code evaluation:
`pnpm testheadless --testPathPattern=runtime-dynamic`
- (new) integration tests for allowing dynamic code evaluation: `pnpm
testheadless --testPathPattern=runtime-configurable`
- (amended) production tests for validating the new configuration keys:
`pnpm testheadless --testPathPattern=config-validations`
To try it live, you could have an application such as:
```js
// lib/index.js
/* eslint-disable no-eval */
export function hasUnusedDynamic() {
if ((() => false)()) {
eval('100')
}
}
export function hasDynamic() {
eval('100')
}
// pages/index.jsx
export default function Page({ edgeRoute }) {
return <p>{edgeRoute}</p>
}
export const getServerSideProps = async (req) => {
const res = await fetch(`http://localhost:3000/api/route`)
const data = await res.json()
return { props: { edgeRoute: data.ok ? `Hi from the edge route` : '' } }
}
// pages/api/route.js
import { hasDynamic } from '../../lib'
export default async function handle() {
hasDynamic()
return Response.json({ ok: true })
}
export const config = {
runtime: 'experimental-edge' ,
allowDynamic: '/lib/**'
}
```
Playing with `config.allowDynamic`, you should be able to:
- build the app even if it uses `eval()` (it will obviously fail at
runtime)
- build the app that _imports but does not use_ `eval()`
- run the app in dev, even if it uses `eval()` with no warning
### 🆙 Notes to reviewers
Before adding documentation and telemetry, I'd like to collect comments
on a couple of points:
- the overall design for this feature: is a list of globs useful and
easy enough?
- should the globs be relative to the application root (current
implementation) to to the edge route/middleware file?
- (especially to @sokra) is the implementation idiomatic enough? I've
leverage loaders to read the _entry point_ configuration once, then the
ModuleGraph to get it back during the parsing phase. I couldn't re-use
the existing `getExtractMetadata()` facility since it's happening late
after the parsing.
- there's a glitch with `import { ServerRuntime } from '../../types'` in
`get-page-static-info.ts`
([here](https://github.com/vercel/next.js/pull/39539/files#diff-cb7ac6392c3dd707c5edab159c3144ec114eafea92dad5d98f4eedfc612174d2L12)).
I had to use `next/types` because it was failing during lint. Any clue
why?
### ☑️ Checklist
- [ ] Implements an existing feature request or RFC. Make sure the
feature request has been accepted for implementation before opening a
PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [x] Documentation added
- [x] Telemetry added. In case of a feature if it's used or not.
- [x] Errors have helpful link attached, see `contributing.md`
2022-09-13 00:01:00 +02:00
|
|
|
pageFilePath: string,
|
2022-08-15 16:29:51 +02:00
|
|
|
config: any,
|
|
|
|
nextConfig: NextConfig
|
|
|
|
): Partial<MiddlewareConfig> {
|
|
|
|
const result: Partial<MiddlewareConfig> = {}
|
|
|
|
|
|
|
|
if (config.matcher) {
|
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
|
|
|
result.matchers = getMiddlewareMatchers(config.matcher, nextConfig)
|
2022-08-15 16:29:51 +02:00
|
|
|
}
|
|
|
|
|
2022-09-27 19:30:15 +02:00
|
|
|
if (typeof config.regions === 'string' || Array.isArray(config.regions)) {
|
|
|
|
result.regions = config.regions
|
|
|
|
} else if (typeof config.regions !== 'undefined') {
|
|
|
|
Log.warn(
|
|
|
|
`The \`regions\` config was ignored: config must be empty, a string or an array of strings. (${pageFilePath})`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-09-13 00:32:18 +02:00
|
|
|
if (config.unstable_allowDynamic) {
|
|
|
|
result.unstable_allowDynamicGlobs = Array.isArray(
|
|
|
|
config.unstable_allowDynamic
|
|
|
|
)
|
|
|
|
? config.unstable_allowDynamic
|
|
|
|
: [config.unstable_allowDynamic]
|
|
|
|
for (const glob of result.unstable_allowDynamicGlobs ?? []) {
|
feat(edge): allows configuring Dynamic code execution guard (#39539)
### 📖 What's in there?
Dynamic code evaluation (`eval()`, `new Function()`, ...) is not
supported on the edge runtime, hence why we fail the build when
detecting such statement in the middleware or `experimental-edge` routes
at build time.
However, there could be false positives, which static analysis and
tree-shaking can not exclude:
- `qs` through these dependencies (get-intrinsic:
[source](https://github.com/ljharb/get-intrinsic/blob/main/index.js#L12))
- `function-bind`
([source](https://github.com/Raynos/function-bind/blob/master/implementation.js#L42))
- `has`
([source](https://github.com/tarruda/has/blob/master/src/index.js#L5))
This PR leverages the existing `config` export to let user allow some of
their files.
it’s meant for allowing users to import 3rd party modules who embed
dynamic code evaluation, but do not use it (because or code paths), and
can't be tree-shaked.
By default, it’s keeping the existing behavior: warn in dev, fails to
build.
If users allow dynamic code, and that code is reached at runtime, their
app stills breaks.
### 🧪 How to test?
- (existing) integration tests for disallowing dynamic code evaluation:
`pnpm testheadless --testPathPattern=runtime-dynamic`
- (new) integration tests for allowing dynamic code evaluation: `pnpm
testheadless --testPathPattern=runtime-configurable`
- (amended) production tests for validating the new configuration keys:
`pnpm testheadless --testPathPattern=config-validations`
To try it live, you could have an application such as:
```js
// lib/index.js
/* eslint-disable no-eval */
export function hasUnusedDynamic() {
if ((() => false)()) {
eval('100')
}
}
export function hasDynamic() {
eval('100')
}
// pages/index.jsx
export default function Page({ edgeRoute }) {
return <p>{edgeRoute}</p>
}
export const getServerSideProps = async (req) => {
const res = await fetch(`http://localhost:3000/api/route`)
const data = await res.json()
return { props: { edgeRoute: data.ok ? `Hi from the edge route` : '' } }
}
// pages/api/route.js
import { hasDynamic } from '../../lib'
export default async function handle() {
hasDynamic()
return Response.json({ ok: true })
}
export const config = {
runtime: 'experimental-edge' ,
allowDynamic: '/lib/**'
}
```
Playing with `config.allowDynamic`, you should be able to:
- build the app even if it uses `eval()` (it will obviously fail at
runtime)
- build the app that _imports but does not use_ `eval()`
- run the app in dev, even if it uses `eval()` with no warning
### 🆙 Notes to reviewers
Before adding documentation and telemetry, I'd like to collect comments
on a couple of points:
- the overall design for this feature: is a list of globs useful and
easy enough?
- should the globs be relative to the application root (current
implementation) to to the edge route/middleware file?
- (especially to @sokra) is the implementation idiomatic enough? I've
leverage loaders to read the _entry point_ configuration once, then the
ModuleGraph to get it back during the parsing phase. I couldn't re-use
the existing `getExtractMetadata()` facility since it's happening late
after the parsing.
- there's a glitch with `import { ServerRuntime } from '../../types'` in
`get-page-static-info.ts`
([here](https://github.com/vercel/next.js/pull/39539/files#diff-cb7ac6392c3dd707c5edab159c3144ec114eafea92dad5d98f4eedfc612174d2L12)).
I had to use `next/types` because it was failing during lint. Any clue
why?
### ☑️ Checklist
- [ ] Implements an existing feature request or RFC. Make sure the
feature request has been accepted for implementation before opening a
PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [x] Documentation added
- [x] Telemetry added. In case of a feature if it's used or not.
- [x] Errors have helpful link attached, see `contributing.md`
2022-09-13 00:01:00 +02:00
|
|
|
try {
|
|
|
|
matcher(glob)
|
|
|
|
} catch (err) {
|
|
|
|
throw new Error(
|
2022-09-13 00:32:18 +02:00
|
|
|
`${pageFilePath} exported 'config.unstable_allowDynamic' contains invalid pattern '${glob}': ${
|
feat(edge): allows configuring Dynamic code execution guard (#39539)
### 📖 What's in there?
Dynamic code evaluation (`eval()`, `new Function()`, ...) is not
supported on the edge runtime, hence why we fail the build when
detecting such statement in the middleware or `experimental-edge` routes
at build time.
However, there could be false positives, which static analysis and
tree-shaking can not exclude:
- `qs` through these dependencies (get-intrinsic:
[source](https://github.com/ljharb/get-intrinsic/blob/main/index.js#L12))
- `function-bind`
([source](https://github.com/Raynos/function-bind/blob/master/implementation.js#L42))
- `has`
([source](https://github.com/tarruda/has/blob/master/src/index.js#L5))
This PR leverages the existing `config` export to let user allow some of
their files.
it’s meant for allowing users to import 3rd party modules who embed
dynamic code evaluation, but do not use it (because or code paths), and
can't be tree-shaked.
By default, it’s keeping the existing behavior: warn in dev, fails to
build.
If users allow dynamic code, and that code is reached at runtime, their
app stills breaks.
### 🧪 How to test?
- (existing) integration tests for disallowing dynamic code evaluation:
`pnpm testheadless --testPathPattern=runtime-dynamic`
- (new) integration tests for allowing dynamic code evaluation: `pnpm
testheadless --testPathPattern=runtime-configurable`
- (amended) production tests for validating the new configuration keys:
`pnpm testheadless --testPathPattern=config-validations`
To try it live, you could have an application such as:
```js
// lib/index.js
/* eslint-disable no-eval */
export function hasUnusedDynamic() {
if ((() => false)()) {
eval('100')
}
}
export function hasDynamic() {
eval('100')
}
// pages/index.jsx
export default function Page({ edgeRoute }) {
return <p>{edgeRoute}</p>
}
export const getServerSideProps = async (req) => {
const res = await fetch(`http://localhost:3000/api/route`)
const data = await res.json()
return { props: { edgeRoute: data.ok ? `Hi from the edge route` : '' } }
}
// pages/api/route.js
import { hasDynamic } from '../../lib'
export default async function handle() {
hasDynamic()
return Response.json({ ok: true })
}
export const config = {
runtime: 'experimental-edge' ,
allowDynamic: '/lib/**'
}
```
Playing with `config.allowDynamic`, you should be able to:
- build the app even if it uses `eval()` (it will obviously fail at
runtime)
- build the app that _imports but does not use_ `eval()`
- run the app in dev, even if it uses `eval()` with no warning
### 🆙 Notes to reviewers
Before adding documentation and telemetry, I'd like to collect comments
on a couple of points:
- the overall design for this feature: is a list of globs useful and
easy enough?
- should the globs be relative to the application root (current
implementation) to to the edge route/middleware file?
- (especially to @sokra) is the implementation idiomatic enough? I've
leverage loaders to read the _entry point_ configuration once, then the
ModuleGraph to get it back during the parsing phase. I couldn't re-use
the existing `getExtractMetadata()` facility since it's happening late
after the parsing.
- there's a glitch with `import { ServerRuntime } from '../../types'` in
`get-page-static-info.ts`
([here](https://github.com/vercel/next.js/pull/39539/files#diff-cb7ac6392c3dd707c5edab159c3144ec114eafea92dad5d98f4eedfc612174d2L12)).
I had to use `next/types` because it was failing during lint. Any clue
why?
### ☑️ Checklist
- [ ] Implements an existing feature request or RFC. Make sure the
feature request has been accepted for implementation before opening a
PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [x] Documentation added
- [x] Telemetry added. In case of a feature if it's used or not.
- [x] Errors have helpful link attached, see `contributing.md`
2022-09-13 00:01:00 +02:00
|
|
|
(err as Error).message
|
|
|
|
}`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-15 16:29:51 +02:00
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2022-08-11 23:32:52 +02:00
|
|
|
let warnedAboutExperimentalEdgeApiFunctions = false
|
2022-06-13 20:17:44 +02:00
|
|
|
function warnAboutExperimentalEdgeApiFunctions() {
|
|
|
|
if (warnedAboutExperimentalEdgeApiFunctions) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
Log.warn(`You are using an experimental edge runtime, the API might change.`)
|
|
|
|
warnedAboutExperimentalEdgeApiFunctions = true
|
|
|
|
}
|
|
|
|
|
2022-07-21 21:56:52 +02:00
|
|
|
const warnedUnsupportedValueMap = new Map<string, boolean>()
|
|
|
|
function warnAboutUnsupportedValue(
|
|
|
|
pageFilePath: string,
|
2022-07-22 21:31:47 +02:00
|
|
|
page: string | undefined,
|
|
|
|
error: UnsupportedValueError
|
2022-07-21 21:56:52 +02:00
|
|
|
) {
|
|
|
|
if (warnedUnsupportedValueMap.has(pageFilePath)) {
|
|
|
|
return
|
|
|
|
}
|
2022-07-22 21:31:47 +02:00
|
|
|
|
2022-07-21 21:56:52 +02:00
|
|
|
Log.warn(
|
2022-07-22 21:31:47 +02:00
|
|
|
`Next.js can't recognize the exported \`config\` field in ` +
|
|
|
|
(page ? `route "${page}"` : `"${pageFilePath}"`) +
|
|
|
|
':\n' +
|
|
|
|
error.message +
|
|
|
|
(error.path ? ` at "${error.path}"` : '') +
|
|
|
|
'.\n' +
|
|
|
|
'The default config will be used instead.\n' +
|
|
|
|
'Read More - https://nextjs.org/docs/messages/invalid-page-config'
|
2022-07-21 21:56:52 +02:00
|
|
|
)
|
2022-07-22 21:31:47 +02:00
|
|
|
|
2022-07-21 21:56:52 +02:00
|
|
|
warnedUnsupportedValueMap.set(pageFilePath, true)
|
|
|
|
}
|
2022-08-15 16:29:51 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* For a given pageFilePath and nextConfig, if the config supports it, this
|
|
|
|
* function will read the file and return the runtime that should be used.
|
|
|
|
* It will look into the file content only if the page *requires* a runtime
|
|
|
|
* to be specified, that is, when gSSP or gSP is used.
|
|
|
|
* Related discussion: https://github.com/vercel/next.js/discussions/34179
|
|
|
|
*/
|
|
|
|
export async function getPageStaticInfo(params: {
|
|
|
|
nextConfig: Partial<NextConfig>
|
|
|
|
pageFilePath: string
|
|
|
|
isDev?: boolean
|
|
|
|
page?: string
|
2022-10-27 23:55:35 +02:00
|
|
|
pageType?: 'pages' | 'app'
|
2022-08-15 16:29:51 +02:00
|
|
|
}): Promise<PageStaticInfo> {
|
2022-10-27 23:55:35 +02:00
|
|
|
const { isDev, pageFilePath, nextConfig, page, pageType } = params
|
2022-08-15 16:29:51 +02:00
|
|
|
|
|
|
|
const fileContent = (await tryToReadFile(pageFilePath, !isDev)) || ''
|
feat(edge): allows configuring Dynamic code execution guard (#39539)
### 📖 What's in there?
Dynamic code evaluation (`eval()`, `new Function()`, ...) is not
supported on the edge runtime, hence why we fail the build when
detecting such statement in the middleware or `experimental-edge` routes
at build time.
However, there could be false positives, which static analysis and
tree-shaking can not exclude:
- `qs` through these dependencies (get-intrinsic:
[source](https://github.com/ljharb/get-intrinsic/blob/main/index.js#L12))
- `function-bind`
([source](https://github.com/Raynos/function-bind/blob/master/implementation.js#L42))
- `has`
([source](https://github.com/tarruda/has/blob/master/src/index.js#L5))
This PR leverages the existing `config` export to let user allow some of
their files.
it’s meant for allowing users to import 3rd party modules who embed
dynamic code evaluation, but do not use it (because or code paths), and
can't be tree-shaked.
By default, it’s keeping the existing behavior: warn in dev, fails to
build.
If users allow dynamic code, and that code is reached at runtime, their
app stills breaks.
### 🧪 How to test?
- (existing) integration tests for disallowing dynamic code evaluation:
`pnpm testheadless --testPathPattern=runtime-dynamic`
- (new) integration tests for allowing dynamic code evaluation: `pnpm
testheadless --testPathPattern=runtime-configurable`
- (amended) production tests for validating the new configuration keys:
`pnpm testheadless --testPathPattern=config-validations`
To try it live, you could have an application such as:
```js
// lib/index.js
/* eslint-disable no-eval */
export function hasUnusedDynamic() {
if ((() => false)()) {
eval('100')
}
}
export function hasDynamic() {
eval('100')
}
// pages/index.jsx
export default function Page({ edgeRoute }) {
return <p>{edgeRoute}</p>
}
export const getServerSideProps = async (req) => {
const res = await fetch(`http://localhost:3000/api/route`)
const data = await res.json()
return { props: { edgeRoute: data.ok ? `Hi from the edge route` : '' } }
}
// pages/api/route.js
import { hasDynamic } from '../../lib'
export default async function handle() {
hasDynamic()
return Response.json({ ok: true })
}
export const config = {
runtime: 'experimental-edge' ,
allowDynamic: '/lib/**'
}
```
Playing with `config.allowDynamic`, you should be able to:
- build the app even if it uses `eval()` (it will obviously fail at
runtime)
- build the app that _imports but does not use_ `eval()`
- run the app in dev, even if it uses `eval()` with no warning
### 🆙 Notes to reviewers
Before adding documentation and telemetry, I'd like to collect comments
on a couple of points:
- the overall design for this feature: is a list of globs useful and
easy enough?
- should the globs be relative to the application root (current
implementation) to to the edge route/middleware file?
- (especially to @sokra) is the implementation idiomatic enough? I've
leverage loaders to read the _entry point_ configuration once, then the
ModuleGraph to get it back during the parsing phase. I couldn't re-use
the existing `getExtractMetadata()` facility since it's happening late
after the parsing.
- there's a glitch with `import { ServerRuntime } from '../../types'` in
`get-page-static-info.ts`
([here](https://github.com/vercel/next.js/pull/39539/files#diff-cb7ac6392c3dd707c5edab159c3144ec114eafea92dad5d98f4eedfc612174d2L12)).
I had to use `next/types` because it was failing during lint. Any clue
why?
### ☑️ Checklist
- [ ] Implements an existing feature request or RFC. Make sure the
feature request has been accepted for implementation before opening a
PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [x] Documentation added
- [x] Telemetry added. In case of a feature if it's used or not.
- [x] Errors have helpful link attached, see `contributing.md`
2022-09-13 00:01:00 +02:00
|
|
|
if (
|
2022-09-27 19:30:15 +02:00
|
|
|
/runtime|getStaticProps|getServerSideProps|export const config/.test(
|
feat(edge): allows configuring Dynamic code execution guard (#39539)
### 📖 What's in there?
Dynamic code evaluation (`eval()`, `new Function()`, ...) is not
supported on the edge runtime, hence why we fail the build when
detecting such statement in the middleware or `experimental-edge` routes
at build time.
However, there could be false positives, which static analysis and
tree-shaking can not exclude:
- `qs` through these dependencies (get-intrinsic:
[source](https://github.com/ljharb/get-intrinsic/blob/main/index.js#L12))
- `function-bind`
([source](https://github.com/Raynos/function-bind/blob/master/implementation.js#L42))
- `has`
([source](https://github.com/tarruda/has/blob/master/src/index.js#L5))
This PR leverages the existing `config` export to let user allow some of
their files.
it’s meant for allowing users to import 3rd party modules who embed
dynamic code evaluation, but do not use it (because or code paths), and
can't be tree-shaked.
By default, it’s keeping the existing behavior: warn in dev, fails to
build.
If users allow dynamic code, and that code is reached at runtime, their
app stills breaks.
### 🧪 How to test?
- (existing) integration tests for disallowing dynamic code evaluation:
`pnpm testheadless --testPathPattern=runtime-dynamic`
- (new) integration tests for allowing dynamic code evaluation: `pnpm
testheadless --testPathPattern=runtime-configurable`
- (amended) production tests for validating the new configuration keys:
`pnpm testheadless --testPathPattern=config-validations`
To try it live, you could have an application such as:
```js
// lib/index.js
/* eslint-disable no-eval */
export function hasUnusedDynamic() {
if ((() => false)()) {
eval('100')
}
}
export function hasDynamic() {
eval('100')
}
// pages/index.jsx
export default function Page({ edgeRoute }) {
return <p>{edgeRoute}</p>
}
export const getServerSideProps = async (req) => {
const res = await fetch(`http://localhost:3000/api/route`)
const data = await res.json()
return { props: { edgeRoute: data.ok ? `Hi from the edge route` : '' } }
}
// pages/api/route.js
import { hasDynamic } from '../../lib'
export default async function handle() {
hasDynamic()
return Response.json({ ok: true })
}
export const config = {
runtime: 'experimental-edge' ,
allowDynamic: '/lib/**'
}
```
Playing with `config.allowDynamic`, you should be able to:
- build the app even if it uses `eval()` (it will obviously fail at
runtime)
- build the app that _imports but does not use_ `eval()`
- run the app in dev, even if it uses `eval()` with no warning
### 🆙 Notes to reviewers
Before adding documentation and telemetry, I'd like to collect comments
on a couple of points:
- the overall design for this feature: is a list of globs useful and
easy enough?
- should the globs be relative to the application root (current
implementation) to to the edge route/middleware file?
- (especially to @sokra) is the implementation idiomatic enough? I've
leverage loaders to read the _entry point_ configuration once, then the
ModuleGraph to get it back during the parsing phase. I couldn't re-use
the existing `getExtractMetadata()` facility since it's happening late
after the parsing.
- there's a glitch with `import { ServerRuntime } from '../../types'` in
`get-page-static-info.ts`
([here](https://github.com/vercel/next.js/pull/39539/files#diff-cb7ac6392c3dd707c5edab159c3144ec114eafea92dad5d98f4eedfc612174d2L12)).
I had to use `next/types` because it was failing during lint. Any clue
why?
### ☑️ Checklist
- [ ] Implements an existing feature request or RFC. Make sure the
feature request has been accepted for implementation before opening a
PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [x] Documentation added
- [x] Telemetry added. In case of a feature if it's used or not.
- [x] Errors have helpful link attached, see `contributing.md`
2022-09-13 00:01:00 +02:00
|
|
|
fileContent
|
|
|
|
)
|
|
|
|
) {
|
2022-08-15 16:29:51 +02:00
|
|
|
const swcAST = await parseModule(pageFilePath, fileContent)
|
2022-10-20 02:39:25 +02:00
|
|
|
const { ssg, ssr, runtime } = checkExports(swcAST)
|
2022-09-18 02:00:16 +02:00
|
|
|
const rsc = getRSCModuleType(fileContent)
|
2022-08-15 16:29:51 +02:00
|
|
|
|
|
|
|
// default / failsafe value for config
|
|
|
|
let config: any = {}
|
|
|
|
try {
|
|
|
|
config = extractExportedConstValue(swcAST, 'config')
|
|
|
|
} catch (e) {
|
|
|
|
if (e instanceof UnsupportedValueError) {
|
|
|
|
warnAboutUnsupportedValue(pageFilePath, page, e)
|
|
|
|
}
|
|
|
|
// `export config` doesn't exist, or other unknown error throw by swc, silence them
|
|
|
|
}
|
|
|
|
|
2022-10-20 02:39:25 +02:00
|
|
|
// Currently, we use `export const config = { runtime: '...' }` to specify the page runtime.
|
|
|
|
// But in the new app directory, we prefer to use `export const runtime = '...'`
|
|
|
|
// and deprecate the old way. To prevent breaking changes for `pages`, we use the exported config
|
|
|
|
// as the fallback value.
|
|
|
|
let resolvedRuntime = runtime || config.runtime
|
|
|
|
|
2022-08-15 16:29:51 +02:00
|
|
|
if (
|
2022-10-20 02:39:25 +02:00
|
|
|
typeof resolvedRuntime !== 'undefined' &&
|
|
|
|
!isServerRuntime(resolvedRuntime)
|
2022-08-15 16:29:51 +02:00
|
|
|
) {
|
|
|
|
const options = Object.values(SERVER_RUNTIME).join(', ')
|
2022-10-20 02:39:25 +02:00
|
|
|
if (typeof resolvedRuntime !== 'string') {
|
2022-09-07 22:12:13 +02:00
|
|
|
Log.error(
|
2022-08-15 16:29:51 +02:00
|
|
|
`The \`runtime\` config must be a string. Please leave it empty or choose one of: ${options}`
|
|
|
|
)
|
|
|
|
} else {
|
2022-09-07 22:12:13 +02:00
|
|
|
Log.error(
|
2022-08-15 16:29:51 +02:00
|
|
|
`Provided runtime "${config.runtime}" is not supported. Please leave it empty or choose one of: ${options}`
|
|
|
|
)
|
|
|
|
}
|
2022-09-07 22:12:13 +02:00
|
|
|
if (!isDev) {
|
|
|
|
process.exit(1)
|
|
|
|
}
|
2022-08-15 16:29:51 +02:00
|
|
|
}
|
|
|
|
|
2022-10-27 23:55:35 +02:00
|
|
|
const requiresServerRuntime = ssr || ssg || pageType === 'app'
|
|
|
|
|
2022-10-20 02:39:25 +02:00
|
|
|
resolvedRuntime =
|
|
|
|
SERVER_RUNTIME.edge === resolvedRuntime
|
2022-08-15 16:29:51 +02:00
|
|
|
? SERVER_RUNTIME.edge
|
2022-10-27 23:55:35 +02:00
|
|
|
: requiresServerRuntime
|
2022-10-20 02:39:25 +02:00
|
|
|
? resolvedRuntime || nextConfig.experimental?.runtime
|
2022-08-15 16:29:51 +02:00
|
|
|
: undefined
|
|
|
|
|
2022-10-20 02:39:25 +02:00
|
|
|
if (resolvedRuntime === SERVER_RUNTIME.edge) {
|
2022-08-15 16:29:51 +02:00
|
|
|
warnAboutExperimentalEdgeApiFunctions()
|
|
|
|
}
|
|
|
|
|
feat(edge): allows configuring Dynamic code execution guard (#39539)
### 📖 What's in there?
Dynamic code evaluation (`eval()`, `new Function()`, ...) is not
supported on the edge runtime, hence why we fail the build when
detecting such statement in the middleware or `experimental-edge` routes
at build time.
However, there could be false positives, which static analysis and
tree-shaking can not exclude:
- `qs` through these dependencies (get-intrinsic:
[source](https://github.com/ljharb/get-intrinsic/blob/main/index.js#L12))
- `function-bind`
([source](https://github.com/Raynos/function-bind/blob/master/implementation.js#L42))
- `has`
([source](https://github.com/tarruda/has/blob/master/src/index.js#L5))
This PR leverages the existing `config` export to let user allow some of
their files.
it’s meant for allowing users to import 3rd party modules who embed
dynamic code evaluation, but do not use it (because or code paths), and
can't be tree-shaked.
By default, it’s keeping the existing behavior: warn in dev, fails to
build.
If users allow dynamic code, and that code is reached at runtime, their
app stills breaks.
### 🧪 How to test?
- (existing) integration tests for disallowing dynamic code evaluation:
`pnpm testheadless --testPathPattern=runtime-dynamic`
- (new) integration tests for allowing dynamic code evaluation: `pnpm
testheadless --testPathPattern=runtime-configurable`
- (amended) production tests for validating the new configuration keys:
`pnpm testheadless --testPathPattern=config-validations`
To try it live, you could have an application such as:
```js
// lib/index.js
/* eslint-disable no-eval */
export function hasUnusedDynamic() {
if ((() => false)()) {
eval('100')
}
}
export function hasDynamic() {
eval('100')
}
// pages/index.jsx
export default function Page({ edgeRoute }) {
return <p>{edgeRoute}</p>
}
export const getServerSideProps = async (req) => {
const res = await fetch(`http://localhost:3000/api/route`)
const data = await res.json()
return { props: { edgeRoute: data.ok ? `Hi from the edge route` : '' } }
}
// pages/api/route.js
import { hasDynamic } from '../../lib'
export default async function handle() {
hasDynamic()
return Response.json({ ok: true })
}
export const config = {
runtime: 'experimental-edge' ,
allowDynamic: '/lib/**'
}
```
Playing with `config.allowDynamic`, you should be able to:
- build the app even if it uses `eval()` (it will obviously fail at
runtime)
- build the app that _imports but does not use_ `eval()`
- run the app in dev, even if it uses `eval()` with no warning
### 🆙 Notes to reviewers
Before adding documentation and telemetry, I'd like to collect comments
on a couple of points:
- the overall design for this feature: is a list of globs useful and
easy enough?
- should the globs be relative to the application root (current
implementation) to to the edge route/middleware file?
- (especially to @sokra) is the implementation idiomatic enough? I've
leverage loaders to read the _entry point_ configuration once, then the
ModuleGraph to get it back during the parsing phase. I couldn't re-use
the existing `getExtractMetadata()` facility since it's happening late
after the parsing.
- there's a glitch with `import { ServerRuntime } from '../../types'` in
`get-page-static-info.ts`
([here](https://github.com/vercel/next.js/pull/39539/files#diff-cb7ac6392c3dd707c5edab159c3144ec114eafea92dad5d98f4eedfc612174d2L12)).
I had to use `next/types` because it was failing during lint. Any clue
why?
### ☑️ Checklist
- [ ] Implements an existing feature request or RFC. Make sure the
feature request has been accepted for implementation before opening a
PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [x] Documentation added
- [x] Telemetry added. In case of a feature if it's used or not.
- [x] Errors have helpful link attached, see `contributing.md`
2022-09-13 00:01:00 +02:00
|
|
|
const middlewareConfig = getMiddlewareConfig(
|
|
|
|
page ?? 'middleware/edge API route',
|
|
|
|
config,
|
|
|
|
nextConfig
|
|
|
|
)
|
2022-08-15 16:29:51 +02:00
|
|
|
|
|
|
|
return {
|
|
|
|
ssr,
|
|
|
|
ssg,
|
2022-09-18 02:00:16 +02:00
|
|
|
rsc,
|
2022-08-15 16:29:51 +02:00
|
|
|
...(middlewareConfig && { middleware: middlewareConfig }),
|
2022-10-20 02:39:25 +02:00
|
|
|
...(resolvedRuntime && { runtime: resolvedRuntime }),
|
2022-08-15 16:29:51 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-18 02:00:16 +02:00
|
|
|
return {
|
|
|
|
ssr: false,
|
|
|
|
ssg: false,
|
|
|
|
rsc: RSC_MODULE_TYPES.server,
|
|
|
|
runtime: nextConfig.experimental?.runtime,
|
|
|
|
}
|
2022-08-15 16:29:51 +02:00
|
|
|
}
|