Skip to content

Commit 049a690

Browse files
authored
Backport: Fix unstable_allowDynamic when used with pnpm (#73765)
> [!NOTE] > This is a backport of #73732 for Next.js 14. 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. _Side note: Ideally we would detect dynamic code evaluation after tree shaking, to reduce the number of cases where users need to revert to using `unstable_allowDynamic`._ fixes #51401
1 parent 663fa9c commit 049a690

File tree

8 files changed

+55
-24
lines changed

8 files changed

+55
-24
lines changed

docs/02-app/02-api-reference/07-edge.mdx

+1-1
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ export const config = {
155155
// allows a single file
156156
'/lib/utilities.js',
157157
// use a glob to allow anything in the function-bind 3rd party module
158-
'/node_modules/function-bind/**',
158+
'**/node_modules/function-bind/**',
159159
],
160160
}
161161
```

errors/edge-dynamic-code-evaluation.mdx

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export const config = {
3838
runtime: 'edge', // for Edge API Routes only
3939
unstable_allowDynamic: [
4040
'/lib/utilities.js', // allows a single file
41-
'/node_modules/function-bind/**', // use a glob to allow anything in the function-bind 3rd party module
41+
'**/node_modules/function-bind/**', // use a glob to allow anything in the function-bind 3rd party module
4242
],
4343
}
4444
```

packages/next/src/build/analysis/get-page-static-info.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ function getMiddlewareConfig(
409409
: [config.unstable_allowDynamic]
410410
for (const glob of result.unstable_allowDynamicGlobs ?? []) {
411411
try {
412-
picomatch(glob)
412+
picomatch(glob, { dot: true })
413413
} catch (err) {
414414
throw new Error(
415415
`${pageFilePath} exported 'config.unstable_allowDynamic' contains invalid pattern '${glob}': ${

packages/next/src/build/webpack/plugins/middleware-plugin.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,9 @@ function isDynamicCodeEvaluationAllowed(
291291

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

294-
return picomatch(middlewareConfig?.unstable_allowDynamicGlobs ?? [])(name)
294+
return picomatch(middlewareConfig?.unstable_allowDynamicGlobs ?? [], {
295+
dot: true,
296+
})(name)
295297
}
296298

297299
function buildUnsupportedApiError({

test/integration/edge-runtime-configurable-guards/node_modules/.pnpm/test/node_modules/lib/package.json

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/integration/edge-runtime-configurable-guards/node_modules/lib

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/integration/edge-runtime-configurable-guards/test/index.test.js

+40-20
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@ const context = {
2121
logs: { output: '', stdout: '', stderr: '' },
2222
api: new File(join(__dirname, '../pages/api/route.js')),
2323
middleware: new File(join(__dirname, '../middleware.js')),
24-
lib: new File(join(__dirname, '../lib/index.js')),
24+
lib: new File(
25+
join(
26+
__dirname,
27+
// Simulated .pnpm node_modules path:
28+
'../node_modules/.pnpm/test/node_modules/lib/index.js'
29+
)
30+
),
2531
}
2632
const appOption = {
2733
env: { __NEXT_TEST_WITH_DEVTOOL: 1 },
@@ -74,7 +80,7 @@ describe('Edge runtime configurable guards', () => {
7480
}
7581
export const config = {
7682
runtime: 'edge',
77-
unstable_allowDynamic: '/lib/**'
83+
unstable_allowDynamic: '**/node_modules/lib/**'
7884
}
7985
`)
8086
await waitFor(500)
@@ -162,14 +168,14 @@ describe('Edge runtime configurable guards', () => {
162168
url: routeUrl,
163169
init() {
164170
context.api.write(`
165-
import { hasDynamic } from '../../lib'
171+
import { hasDynamic } from 'lib'
166172
export default async function handler(request) {
167173
await hasDynamic()
168174
return Response.json({ result: true })
169175
}
170176
export const config = {
171177
runtime: 'edge',
172-
unstable_allowDynamic: '/lib/**'
178+
unstable_allowDynamic: '**/node_modules/lib/**'
173179
}
174180
`)
175181
context.lib.write(`
@@ -178,22 +184,25 @@ describe('Edge runtime configurable guards', () => {
178184
}
179185
`)
180186
},
187+
// TODO: Re-enable when Turbopack applies the middleware dynamic code
188+
// evaluation transforms also to code in node_modules.
189+
skip: Boolean(process.env.TURBOPACK),
181190
},
182191
{
183192
title: 'Middleware using lib',
184193
url: middlewareUrl,
185194
init() {
186195
context.middleware.write(`
187196
import { NextResponse } from 'next/server'
188-
import { hasDynamic } from './lib'
197+
import { hasDynamic } from 'lib'
189198
190199
// populated with tests
191200
export default async function () {
192201
await hasDynamic()
193202
return NextResponse.next()
194203
}
195204
export const config = {
196-
unstable_allowDynamic: '/lib/**'
205+
unstable_allowDynamic: '**/node_modules/lib/**'
197206
}
198207
`)
199208
context.lib.write(`
@@ -202,15 +211,19 @@ describe('Edge runtime configurable guards', () => {
202211
}
203212
`)
204213
},
214+
// TODO: Re-enable when Turbopack applies the middleware dynamic code
215+
// evaluation transforms also to code in node_modules.
216+
skip: Boolean(process.env.TURBOPACK),
205217
},
206-
])('$title with allowed, used dynamic code', ({ init, url }) => {
218+
])('$title with allowed, used dynamic code', ({ init, url, skip }) => {
207219
beforeEach(() => init())
208-
209-
it('still warns in dev at runtime', async () => {
220+
;(skip ? it.skip : it)('still warns in dev at runtime', async () => {
210221
context.app = await launchApp(context.appDir, context.appPort, appOption)
211222
const res = await fetchViaHTTP(context.appPort, url)
212223
await waitFor(500)
224+
// eslint-disable-next-line jest/no-standalone-expect
213225
expect(res.status).toBe(200)
226+
// eslint-disable-next-line jest/no-standalone-expect
214227
expect(context.logs.output).toContain(
215228
`Dynamic Code Evaluation (e. g. 'eval', 'new Function') not allowed in Edge Runtime`
216229
)
@@ -260,14 +273,14 @@ describe('Edge runtime configurable guards', () => {
260273
url: routeUrl,
261274
init() {
262275
context.api.write(`
263-
import { hasUnusedDynamic } from '../../lib'
276+
import { hasUnusedDynamic } from 'lib'
264277
export default async function handler(request) {
265278
await hasUnusedDynamic()
266279
return Response.json({ result: true })
267280
}
268281
export const config = {
269282
runtime: 'edge',
270-
unstable_allowDynamic: '/lib/**'
283+
unstable_allowDynamic: '**/node_modules/lib/**'
271284
}
272285
`)
273286
context.lib.write(`
@@ -285,14 +298,14 @@ describe('Edge runtime configurable guards', () => {
285298
init() {
286299
context.middleware.write(`
287300
import { NextResponse } from 'next/server'
288-
import { hasUnusedDynamic } from './lib'
301+
import { hasUnusedDynamic } from 'lib'
289302
// populated with tests
290303
export default async function () {
291304
await hasUnusedDynamic()
292305
return NextResponse.next()
293306
}
294307
export const config = {
295-
unstable_allowDynamic: '/lib/**'
308+
unstable_allowDynamic: '**/node_modules/lib/**'
296309
}
297310
`)
298311
context.lib.write(`
@@ -340,7 +353,7 @@ describe('Edge runtime configurable guards', () => {
340353
url: routeUrl,
341354
init() {
342355
context.api.write(`
343-
import { hasDynamic } from '../../lib'
356+
import { hasDynamic } from 'lib'
344357
export default async function handler(request) {
345358
await hasDynamic()
346359
return Response.json({ result: true })
@@ -356,14 +369,17 @@ describe('Edge runtime configurable guards', () => {
356369
}
357370
`)
358371
},
372+
// TODO: Re-enable when Turbopack applies the edge runtime transforms also
373+
// to code in node_modules.
374+
skip: Boolean(process.env.TURBOPACK),
359375
},
360376
{
361377
title: 'Middleware using lib',
362378
url: middlewareUrl,
363379
init() {
364380
context.middleware.write(`
365381
import { NextResponse } from 'next/server'
366-
import { hasDynamic } from './lib'
382+
import { hasDynamic } from 'lib'
367383
export default async function () {
368384
await hasDynamic()
369385
return NextResponse.next()
@@ -378,20 +394,24 @@ describe('Edge runtime configurable guards', () => {
378394
}
379395
`)
380396
},
397+
// TODO: Re-enable when Turbopack applies the middleware dynamic code
398+
// evaluation transforms also to code in node_modules.
399+
skip: Boolean(process.env.TURBOPACK),
381400
},
382-
])('$title with unallowed, used dynamic code', ({ init, url }) => {
401+
])('$title with unallowed, used dynamic code', ({ init, url, skip }) => {
383402
beforeEach(() => init())
384-
385-
it('warns in dev at runtime', async () => {
403+
;(skip ? it.skip : it)('warns in dev at runtime', async () => {
386404
context.app = await launchApp(context.appDir, context.appPort, appOption)
387405
const res = await fetchViaHTTP(context.appPort, url)
388406
await waitFor(500)
407+
// eslint-disable-next-line jest/no-standalone-expect
389408
expect(res.status).toBe(200)
409+
// eslint-disable-next-line jest/no-standalone-expect
390410
expect(context.logs.output).toContain(
391411
`Dynamic Code Evaluation (e. g. 'eval', 'new Function') not allowed in Edge Runtime`
392412
)
393413
})
394-
;(process.env.TURBOPACK_DEV ? describe.skip : describe)(
414+
;(skip || process.env.TURBOPACK_DEV ? describe.skip : describe)(
395415
'production mode',
396416
() => {
397417
it('fails to build because of dynamic code evaluation', async () => {
@@ -429,7 +449,7 @@ describe('Edge runtime configurable guards', () => {
429449
init() {
430450
context.middleware.write(`
431451
import { NextResponse } from 'next/server'
432-
import { returnTrue } from './lib'
452+
import { returnTrue } from 'lib'
433453
export default async function () {
434454
(() => {}) instanceof Function
435455
return NextResponse.next()

0 commit comments

Comments
 (0)