Skip to content

Commit

Permalink
Fix unstable_allowDynamic when used with pnpm
Browse files Browse the repository at this point in the history
When using dependencies in Middleware that make use of dynamic code
evaluation, Next.js emits a build error because this is not supported in
the Edge runtime.

In rare cases, when the code can not be reached at runtime and can't be
removed by tree-shaking, users might opt in to using the
`unstable_allowDynamic` config.

When combined with pnpm, the provided glob patterns as documented at
https://nextjs.org/docs/messages/edge-dynamic-code-evaluation#possible-ways-to-fix-it
did not match correctly because of pnpm's use of the `.pnpm` directory.

To fix the pattern matching, we need to provide the `dot` option to
[picomatch](https://github.com/micromatch/picomatch), which enables
dotfile matching.

fixes #51401
  • Loading branch information
unstubbable committed Dec 10, 2024
1 parent dfa14fb commit f08220c
Show file tree
Hide file tree
Showing 7 changed files with 33 additions and 16 deletions.
2 changes: 1 addition & 1 deletion docs/01-app/03-api-reference/07-edge.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export const config = {
// allows a single file
'/lib/utilities.js',
// use a glob to allow anything in the function-bind 3rd party module
'/node_modules/function-bind/**',
'**/node_modules/function-bind/**',
],
}
```
Expand Down
2 changes: 1 addition & 1 deletion errors/edge-dynamic-code-evaluation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ You can relax the check to allow specific files with your Middleware [configurat
export const config = {
unstable_allowDynamic: [
'/lib/utilities.js', // allows a single file
'/node_modules/function-bind/**', // use a glob to allow anything in the function-bind 3rd party module
'**/node_modules/function-bind/**', // use a glob to allow anything in the function-bind 3rd party module
],
}
```
Expand Down
4 changes: 3 additions & 1 deletion packages/next/src/build/webpack/plugins/middleware-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,9 @@ function isDynamicCodeEvaluationAllowed(

const name = fileName.replace(rootDir ?? '', '')

return picomatch(middlewareConfig?.unstable_allowDynamic ?? [])(name)
return picomatch(middlewareConfig?.unstable_allowDynamic ?? [], {
dot: true,
})(name)
}

function buildUnsupportedApiError({
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@ const context = {
logs: { output: '', stdout: '', stderr: '' },
api: new File(join(__dirname, '../pages/api/route.js')),
middleware: new File(join(__dirname, '../middleware.js')),
lib: new File(join(__dirname, '../lib/index.js')),
lib: new File(
join(
__dirname,
// Simulated .pnpm node_modules path:
'../node_modules/.pnpm/test/node_modules/lib/index.js'
)
),
}
const appOption = {
env: { __NEXT_TEST_WITH_DEVTOOL: 1 },
Expand Down Expand Up @@ -74,7 +80,7 @@ describe('Edge runtime configurable guards', () => {
}
export const config = {
runtime: 'edge',
unstable_allowDynamic: '/lib/**'
unstable_allowDynamic: '**/node_modules/lib/**'
}
`)
await waitFor(500)
Expand Down Expand Up @@ -167,14 +173,14 @@ describe('Edge runtime configurable guards', () => {
url: routeUrl,
init() {
context.api.write(`
import { hasDynamic } from '../../lib'
import { hasDynamic } from 'lib'
export default async function handler(request) {
await hasDynamic()
return Response.json({ result: true })
}
export const config = {
runtime: 'edge',
unstable_allowDynamic: '/lib/**'
unstable_allowDynamic: '**/node_modules/lib/**'
}
`)
context.lib.write(`
Expand All @@ -190,15 +196,15 @@ describe('Edge runtime configurable guards', () => {
init() {
context.middleware.write(`
import { NextResponse } from 'next/server'
import { hasDynamic } from './lib'
import { hasDynamic } from 'lib'
// populated with tests
export default async function () {
await hasDynamic()
return NextResponse.next()
}
export const config = {
unstable_allowDynamic: '/lib/**'
unstable_allowDynamic: '**/node_modules/lib/**'
}
`)
context.lib.write(`
Expand Down Expand Up @@ -265,14 +271,14 @@ describe('Edge runtime configurable guards', () => {
url: routeUrl,
init() {
context.api.write(`
import { hasUnusedDynamic } from '../../lib'
import { hasUnusedDynamic } from 'lib'
export default async function handler(request) {
await hasUnusedDynamic()
return Response.json({ result: true })
}
export const config = {
runtime: 'edge',
unstable_allowDynamic: '/lib/**'
unstable_allowDynamic: '**/node_modules/lib/**'
}
`)
context.lib.write(`
Expand All @@ -290,14 +296,14 @@ describe('Edge runtime configurable guards', () => {
init() {
context.middleware.write(`
import { NextResponse } from 'next/server'
import { hasUnusedDynamic } from './lib'
import { hasUnusedDynamic } from 'lib'
// populated with tests
export default async function () {
await hasUnusedDynamic()
return NextResponse.next()
}
export const config = {
unstable_allowDynamic: '/lib/**'
unstable_allowDynamic: '**/node_modules/lib/**'
}
`)
context.lib.write(`
Expand Down Expand Up @@ -356,7 +362,7 @@ describe('Edge runtime configurable guards', () => {
url: routeUrl,
init() {
context.api.write(`
import { hasDynamic } from '../../lib'
import { hasDynamic } from 'lib'
export default async function handler(request) {
await hasDynamic()
return Response.json({ result: true })
Expand All @@ -379,7 +385,7 @@ describe('Edge runtime configurable guards', () => {
init() {
context.middleware.write(`
import { NextResponse } from 'next/server'
import { hasDynamic } from './lib'
import { hasDynamic } from 'lib'
export default async function () {
await hasDynamic()
return NextResponse.next()
Expand Down Expand Up @@ -446,7 +452,7 @@ describe('Edge runtime configurable guards', () => {
init() {
context.middleware.write(`
import { NextResponse } from 'next/server'
import { returnTrue } from './lib'
import { returnTrue } from 'lib'
export default async function () {
(() => {}) instanceof Function
return NextResponse.next()
Expand Down

0 comments on commit f08220c

Please sign in to comment.