From 33bda80a4be2e05af9142e132e583d84e4d46e87 Mon Sep 17 00:00:00 2001 From: feugy Date: Tue, 17 May 2022 15:33:37 +0200 Subject: [PATCH 1/4] feat(middleware): issues warnings when using node.js global APIs in middleware --- .../webpack/plugins/middleware-plugin.ts | 77 +++++++++++- packages/next/server/web/sandbox/context.ts | 94 +++++++++++---- packages/next/shared/lib/constants.ts | 30 +++++ .../pages/_middleware.js | 5 + .../overrides-node.js-api/test/index.test.ts | 47 ++++++++ .../with-node.js-apis/pages/_middleware.js | 94 +++++++++++++++ .../with-node.js-apis/test/index.test.ts | 114 ++++++++++++++++++ 7 files changed, 434 insertions(+), 27 deletions(-) create mode 100644 test/integration/middleware/overrides-node.js-api/pages/_middleware.js create mode 100644 test/integration/middleware/overrides-node.js-api/test/index.test.ts create mode 100644 test/integration/middleware/with-node.js-apis/pages/_middleware.js create mode 100644 test/integration/middleware/with-node.js-apis/test/index.test.ts diff --git a/packages/next/build/webpack/plugins/middleware-plugin.ts b/packages/next/build/webpack/plugins/middleware-plugin.ts index 37c38d20c411b..1045b4923a2f1 100644 --- a/packages/next/build/webpack/plugins/middleware-plugin.ts +++ b/packages/next/build/webpack/plugins/middleware-plugin.ts @@ -6,6 +6,7 @@ import { getSortedRoutes } from '../../../shared/lib/router/utils' import { webpack, sources, webpack5 } from 'next/dist/compiled/webpack/webpack' import { EDGE_RUNTIME_WEBPACK, + EDGE_UNSUPPORTED_NODE_APIS, MIDDLEWARE_BUILD_MANIFEST, MIDDLEWARE_FLIGHT_MANIFEST, MIDDLEWARE_MANIFEST, @@ -57,7 +58,11 @@ export default class MiddlewarePlugin { /** * This is the static code analysis phase. */ - const codeAnalyzer = getCodeAnalizer({ dev: this.dev, compiler }) + const codeAnalyzer = getCodeAnalizer({ + dev: this.dev, + compiler, + compilation, + }) hooks.parser.for('javascript/auto').tap(NAME, codeAnalyzer) hooks.parser.for('javascript/dynamic').tap(NAME, codeAnalyzer) hooks.parser.for('javascript/esm').tap(NAME, codeAnalyzer) @@ -93,11 +98,13 @@ export default class MiddlewarePlugin { function getCodeAnalizer(params: { dev: boolean compiler: webpack5.Compiler + compilation: webpack5.Compilation }) { return (parser: webpack5.javascript.JavascriptParser) => { const { dev, compiler: { webpack: wp }, + compilation, } = params const { hooks } = parser @@ -107,7 +114,7 @@ function getCodeAnalizer(params: { * but actually execute the expression. */ const handleWrapExpression = (expr: any) => { - if (parser.state.module?.layer !== 'middleware') { + if (!isInMiddlewareLayer(parser)) { return } @@ -135,7 +142,7 @@ function getCodeAnalizer(params: { * module path that is using it. */ const handleExpression = () => { - if (parser.state.module?.layer !== 'middleware') { + if (!isInMiddlewareLayer(parser)) { return } @@ -169,7 +176,7 @@ function getCodeAnalizer(params: { } buildInfo.nextUsedEnvVars.add(members[1]) - if (parser.state.module?.layer !== 'middleware') { + if (!isInMiddlewareLayer(parser)) { return true } } @@ -179,8 +186,7 @@ function getCodeAnalizer(params: { * A noop handler to skip analyzing some cases. * Order matters: for it to work, it must be registered first */ - const skip = () => - parser.state.module?.layer === 'middleware' ? true : undefined + const skip = () => (isInMiddlewareLayer(parser) ? true : undefined) for (const prefix of ['', 'global.']) { hooks.expression.for(`${prefix}Function.prototype`).tap(NAME, skip) @@ -193,6 +199,7 @@ function getCodeAnalizer(params: { } hooks.callMemberChain.for('process').tap(NAME, handleCallMemberChain) hooks.expressionMemberChain.for('process').tap(NAME, handleCallMemberChain) + registerUnsupportedApiHooks(parser, compilation) } } @@ -412,3 +419,61 @@ function getEntryFiles(entryFiles: string[], meta: EntryMetadata) { ) return files } + +function registerUnsupportedApiHooks( + parser: webpack5.javascript.JavascriptParser, + compilation: webpack5.Compilation +) { + const { WebpackError } = compilation.compiler.webpack + for (const expression of EDGE_UNSUPPORTED_NODE_APIS) { + parser.hooks.expression.for(expression).tap(NAME, (node: any) => { + if (!isInMiddlewareLayer(parser)) { + return + } + compilation.warnings.push( + makeUnsupportedApiError(WebpackError, parser, node.name, node.loc) + ) + }) + } + + const warnForUnsupportedProcessApi = (node: any, [callee]: string[]) => { + if (!isInMiddlewareLayer(parser) || callee === 'env') { + return + } + compilation.warnings.push( + makeUnsupportedApiError( + WebpackError, + parser, + `process.${callee}`, + node.loc + ) + ) + } + + parser.hooks.callMemberChain + .for('process') + .tap(NAME, warnForUnsupportedProcessApi) + parser.hooks.expressionMemberChain + .for('process') + .tap(NAME, warnForUnsupportedProcessApi) +} + +function makeUnsupportedApiError( + WebpackError: typeof webpack5.WebpackError, + parser: webpack5.javascript.JavascriptParser, + name: string, + loc: any +) { + const error = new WebpackError( + `You're using a Node.js API (${name} at line: ${loc.start.line}) which is not supported in the Edge Runtime that Middleware uses. +Learn more: https://nextjs.org/docs/api-reference/edge-runtime` + ) + error.name = NAME + error.module = parser.state.current + error.loc = loc + return error +} + +function isInMiddlewareLayer(parser: webpack5.javascript.JavascriptParser) { + return parser.state.module?.layer === 'middleware' +} diff --git a/packages/next/server/web/sandbox/context.ts b/packages/next/server/web/sandbox/context.ts index 11f989b6fd483..0ca80f333e7de 100644 --- a/packages/next/server/web/sandbox/context.ts +++ b/packages/next/server/web/sandbox/context.ts @@ -11,6 +11,7 @@ import { } from 'next/dist/compiled/abort-controller' import vm from 'vm' import type { WasmBinding } from '../../../build/webpack/loaders/get-module-build-info' +import { EDGE_UNSUPPORTED_NODE_APIS } from '../../../shared/lib/constants' const WEBPACK_HASH_REGEX = /__webpack_require__\.h = function\(\) \{ return "[0-9a-f]+"; \}/g @@ -47,19 +48,21 @@ const caches = new Map< } >() +interface ModuleContextOptions { + module: string + onWarning: (warn: Error) => void + useCache: boolean + env: string[] + wasm: WasmBinding[] +} + /** * For a given module name this function will create a context for the * runtime. It returns a function where we can provide a module path and * run in within the context. It may or may not use a cache depending on * the parameters. */ -export async function getModuleContext(options: { - module: string - onWarning: (warn: Error) => void - useCache: boolean - env: string[] - wasm: WasmBinding[] -}) { +export async function getModuleContext(options: ModuleContextOptions) { let moduleCache = options.useCache ? caches.get(options.module) : await createModuleContext(options) @@ -97,12 +100,7 @@ export async function getModuleContext(options: { * 2. Dependencies that require runtime globals such as Blob. * 3. Dependencies that are scoped for the provided parameters. */ -async function createModuleContext(options: { - onWarning: (warn: Error) => void - module: string - env: string[] - wasm: WasmBinding[] -}) { +async function createModuleContext(options: ModuleContextOptions) { const requireCache = new Map([ [require.resolve('next/dist/compiled/cookie'), { exports: cookie }], ]) @@ -181,11 +179,10 @@ async function createModuleContext(options: { * Create a base context with all required globals for the runtime that * won't depend on any externally provided dependency. */ -function createContext(options: { - /** Environment variables to be provided to the context */ - env: string[] -}) { - const context: { [key: string]: unknown } = { +function createContext( + options: Pick +) { + const context: Context = { _ENTRIES: {}, atob: polyfills.atob, Blob, @@ -209,9 +206,7 @@ function createContext(options: { crypto: new polyfills.Crypto(), File, FormData, - process: { - env: buildEnvironmentVariablesFrom(options.env), - }, + process: createProcessPolyfill(options), ReadableStream, setInterval, setTimeout, @@ -245,6 +240,9 @@ function createContext(options: { ArrayBuffer, SharedArrayBuffer, } + for (const name of EDGE_UNSUPPORTED_NODE_APIS) { + addStub(context, name, options) + } // Self references context.self = context @@ -286,3 +284,57 @@ async function loadWasm( return modules } + +function createProcessPolyfill( + options: Pick +) { + const env = buildEnvironmentVariablesFrom(options.env) + + const processPolyfill = { env } + const overridenValue: Record = {} + for (const key of Object.keys(process)) { + if (key === 'env') continue + Object.defineProperty(processPolyfill, key, { + get() { + emitWarning(`process.${key}`, options) + return overridenValue[key] + }, + set(value) { + overridenValue[key] = value + }, + enumerable: false, + }) + } + return processPolyfill +} + +const warnedAlready = new Set() + +function addStub( + context: Context, + name: string, + contextOptions: Pick +) { + Object.defineProperty(context, name, { + get() { + emitWarning(name, contextOptions) + return undefined + }, + enumerable: false, + }) +} + +function emitWarning( + name: string, + contextOptions: Pick +) { + if (!warnedAlready.has(name)) { + const warning = + new Error(`You're using a Node.js API (${name}) which is not supported in the Edge Runtime that Middleware uses. +Learn more: https://nextjs.org/docs/api-reference/edge-runtime`) + warning.name = 'NodejsRuntimeApiInMiddlewareWarning' + contextOptions.onWarning(warning) + console.warn(warning.message) + warnedAlready.add(name) + } +} diff --git a/packages/next/shared/lib/constants.ts b/packages/next/shared/lib/constants.ts index 714a997dec5d3..4db53f5ef07c5 100644 --- a/packages/next/shared/lib/constants.ts +++ b/packages/next/shared/lib/constants.ts @@ -67,3 +67,33 @@ export const OPTIMIZED_FONT_PROVIDERS = [ ] export const STATIC_STATUS_PAGES = ['/500'] export const TRACE_OUTPUT_VERSION = 1 + +export const EDGE_UNSUPPORTED_NODE_APIS = [ + 'clearImmediate', + 'setImmediate', + 'structuredClone', + 'queueMicrotask', // TODO allow that one? + 'BroadcastChannel', + 'ByteLengthQueuingStrategy', + 'CompressionStream', + 'CountQueuingStrategy', + 'CryptoKey', + 'DecompressionStream', + 'DomException', + 'Event', + 'EventTarget', + 'MessageChannel', + 'MessageEvent', + 'MessagePort', + 'ReadableByteStreamController', + 'ReadableStreamBYOBReader', + 'ReadableStreamBYOBRequest', + 'ReadableStreamDefaultController', + 'ReadableStreamDefaultReader', + 'SubtleCrypto', + 'TextDecoderStream', + 'TextEncoderStream', + 'TransformStreamDefaultController', + 'WritableStreamDefaultController', + 'WritableStreamDefaultWriter', +] diff --git a/test/integration/middleware/overrides-node.js-api/pages/_middleware.js b/test/integration/middleware/overrides-node.js-api/pages/_middleware.js new file mode 100644 index 0000000000000..1bea9d9ca3cb3 --- /dev/null +++ b/test/integration/middleware/overrides-node.js-api/pages/_middleware.js @@ -0,0 +1,5 @@ +export default function middleware() { + process.cwd = () => 'fixed-value' + console.log(process.cwd(), process.env) + return new Response() +} diff --git a/test/integration/middleware/overrides-node.js-api/test/index.test.ts b/test/integration/middleware/overrides-node.js-api/test/index.test.ts new file mode 100644 index 0000000000000..f9c3ed52948ef --- /dev/null +++ b/test/integration/middleware/overrides-node.js-api/test/index.test.ts @@ -0,0 +1,47 @@ +/* eslint-env jest */ + +import { + fetchViaHTTP, + findPort, + killApp, + launchApp, + waitFor, +} from 'next-test-utils' +import { join } from 'path' + +const context = { appDir: join(__dirname, '../'), appPort: NaN, app: null } + +jest.setTimeout(1000 * 60 * 2) + +describe('Middleware overriding a Node.js API', () => { + describe('dev mode', () => { + let output = '' + + beforeAll(async () => { + output = '' + context.appPort = await findPort() + context.app = await launchApp(context.appDir, context.appPort, { + onStdout(msg) { + output += msg + }, + onStderr(msg) { + output += msg + }, + }) + }) + + afterAll(() => killApp(context.app)) + + it('shows a warning but allows overriding', async () => { + const res = await fetchViaHTTP(context.appPort, '/') + await waitFor(500) + expect(res.status).toBe(200) + expect(output) + .toContain(`NodejsRuntimeApiInMiddlewareWarning: You're using a Node.js API (process.cwd) which is not supported in the Edge Runtime that Middleware uses. +Learn more: https://nextjs.org/docs/api-reference/edge-runtime`) + expect(output).toContain('fixed-value') + expect(output).not.toContain('TypeError') + expect(output).not.toContain(`You're using a Node.js API (process.env)`) + }) + }) +}) diff --git a/test/integration/middleware/with-node.js-apis/pages/_middleware.js b/test/integration/middleware/with-node.js-apis/pages/_middleware.js new file mode 100644 index 0000000000000..57f7ee3094345 --- /dev/null +++ b/test/integration/middleware/with-node.js-apis/pages/_middleware.js @@ -0,0 +1,94 @@ +/* eslint-disable no-undef */ +import { NextResponse } from 'next/server' + +export default function middleware({ nextUrl: { pathname } }) { + let handle + if (pathname === '/setImmediate') { + handle = setImmediate(() => {}) + } + if (pathname === '/clearImmediate') { + clearImmediate(handle) + } + if (pathname === '/structuredClone') { + structuredClone({}) + } + if (pathname === '/queueMicrotask') { + queueMicrotask(() => {}) + } + if (pathname === '/process.cwd') { + console.log(process.cwd()) + } + if (pathname === '/process.getuid') { + console.log(process.getuid()) + } + if (pathname === '/BroadcastChannel') { + new BroadcastChannel() + } + if (pathname === '/ByteLengthQueuingStrategy') { + new ByteLengthQueuingStrategy() + } + if (pathname === '/CompressionStream') { + new CompressionStream() + } + if (pathname === '/CountQueuingStrategy') { + new CountQueuingStrategy() + } + if (pathname === '/CryptoKey') { + new CryptoKey() + } + if (pathname === '/DecompressionStream') { + new DecompressionStream() + } + if (pathname === '/DomException') { + new DomException() + } + if (pathname === '/Event') { + new Event() + } + if (pathname === '/EventTarget') { + new EventTarget() + } + if (pathname === '/MessageChannel') { + new MessageChannel() + } + if (pathname === '/MessageEvent') { + new MessageEvent() + } + if (pathname === '/MessagePort') { + new MessagePort() + } + if (pathname === '/ReadableByteStreamController') { + new ReadableByteStreamController() + } + if (pathname === '/ReadableStreamBYOBReader') { + new ReadableStreamBYOBReader() + } + if (pathname === '/ReadableStreamBYOBRequest') { + new ReadableStreamBYOBRequest() + } + if (pathname === '/ReadableStreamDefaultController') { + new ReadableStreamDefaultController() + } + if (pathname === '/ReadableStreamDefaultReader') { + new ReadableStreamDefaultReader() + } + if (pathname === '/SubtleCrypto') { + new SubtleCrypto() + } + if (pathname === '/TextDecoderStream') { + new TextDecoderStream() + } + if (pathname === '/TextEncoderStream') { + new TextEncoderStream() + } + if (pathname === '/TransformStreamDefaultController') { + new TransformStreamDefaultController() + } + if (pathname === '/WritableStreamDefaultController') { + new WritableStreamDefaultController() + } + if (pathname === '/WritableStreamDefaultWriter') { + new WritableStreamDefaultWriter() + } + return NextResponse.next() +} diff --git a/test/integration/middleware/with-node.js-apis/test/index.test.ts b/test/integration/middleware/with-node.js-apis/test/index.test.ts new file mode 100644 index 0000000000000..2c0211e3b1168 --- /dev/null +++ b/test/integration/middleware/with-node.js-apis/test/index.test.ts @@ -0,0 +1,114 @@ +/* eslint-env jest */ + +import { + fetchViaHTTP, + findPort, + killApp, + launchApp, + nextBuild, + waitFor, +} from 'next-test-utils' +import { join } from 'path' + +jest.setTimeout(1000 * 60 * 2) + +const unsupportedFunctions = [ + 'setImmediate', + 'clearImmediate', + 'structuredClone', + 'queueMicrotask', + // no need to test all of the process methods + 'process.cwd', + 'process.getuid', +] +const unsupportedClasses = [ + 'BroadcastChannel', + 'ByteLengthQueuingStrategy', + 'CompressionStream', + 'CountQueuingStrategy', + 'CryptoKey', + 'DecompressionStream', + 'DomException', + 'Event', + 'EventTarget', + 'MessageChannel', + 'MessageEvent', + 'MessagePort', + 'ReadableByteStreamController', + 'ReadableStreamBYOBReader', + 'ReadableStreamBYOBRequest', + 'ReadableStreamDefaultController', + 'ReadableStreamDefaultReader', + 'SubtleCrypto', + 'TextDecoderStream', + 'TextEncoderStream', + 'TransformStreamDefaultController', + 'WritableStreamDefaultController', + 'WritableStreamDefaultWriter', +] + +describe('Middleware using Node.js API', () => { + const appDir = join(__dirname, '..') + + describe('dev mode', () => { + let output = '' + let appPort: number + let app = null + + beforeAll(async () => { + output = '' + appPort = await findPort() + app = await launchApp(appDir, appPort, { + onStdout(msg) { + output += msg + }, + onStderr(msg) { + output += msg + }, + }) + }) + + afterAll(() => killApp(app)) + + it.each([ + ...unsupportedFunctions.map((api) => ({ + api, + error: `${api} is not a function`, + })), + ...unsupportedClasses.map((api) => ({ + api, + error: `${api} is not a constructor`, + })), + ])(`shows error when using $api`, async ({ api, error }) => { + const res = await fetchViaHTTP(appPort, `/${api}`) + await waitFor(500) + expect(res.status).toBe(500) + expect(output) + .toContain(`NodejsRuntimeApiInMiddlewareWarning: You're using a Node.js API (${api}) which is not supported in the Edge Runtime that Middleware uses. +Learn more: https://nextjs.org/docs/api-reference/edge-runtime`) + expect(output).toContain(`TypeError: ${error}`) + }) + }) + + describe('production mode', () => { + let buildResult + + beforeAll(async () => { + buildResult = await nextBuild(appDir, undefined, { + stderr: true, + stdout: true, + }) + }) + + it.each( + [...unsupportedFunctions, ...unsupportedClasses].map((api, index) => ({ + api, + line: 5 + index * 3, + })) + )(`warns for $api during build`, ({ api, line }) => { + expect(buildResult.stderr) + .toContain(`You're using a Node.js API (${api} at line: ${line}) which is not supported in the Edge Runtime that Middleware uses. +Learn more: https://nextjs.org/docs/api-reference/edge-runtime`) + }) + }) +}) From 5172a78da7e4e5bd0886245e6632c54c4704c5ea Mon Sep 17 00:00:00 2001 From: feugy Date: Wed, 18 May 2022 14:32:00 +0200 Subject: [PATCH 2/4] refactor: aligns unsupported list with edge-runtime capabilities --- .../webpack/plugins/middleware-plugin.ts | 16 ++++++++++--- packages/next/server/web/sandbox/context.ts | 12 ++++++++++ packages/next/shared/lib/constants.ts | 12 ++++------ .../with-node.js-apis/pages/_middleware.js | 24 +++---------------- .../with-node.js-apis/test/index.test.ts | 23 +++++++++--------- 5 files changed, 45 insertions(+), 42 deletions(-) diff --git a/packages/next/build/webpack/plugins/middleware-plugin.ts b/packages/next/build/webpack/plugins/middleware-plugin.ts index 1045b4923a2f1..c122095eccebc 100644 --- a/packages/next/build/webpack/plugins/middleware-plugin.ts +++ b/packages/next/build/webpack/plugins/middleware-plugin.ts @@ -426,14 +426,23 @@ function registerUnsupportedApiHooks( ) { const { WebpackError } = compilation.compiler.webpack for (const expression of EDGE_UNSUPPORTED_NODE_APIS) { - parser.hooks.expression.for(expression).tap(NAME, (node: any) => { + const warnForUnsupportedApi = (node: any) => { if (!isInMiddlewareLayer(parser)) { return } compilation.warnings.push( - makeUnsupportedApiError(WebpackError, parser, node.name, node.loc) + makeUnsupportedApiError(WebpackError, parser, expression, node.loc) ) - }) + return true + } + parser.hooks.call.for(expression).tap(NAME, warnForUnsupportedApi) + parser.hooks.expression.for(expression).tap(NAME, warnForUnsupportedApi) + parser.hooks.callMemberChain + .for(expression) + .tap(NAME, warnForUnsupportedApi) + parser.hooks.expressionMemberChain + .for(expression) + .tap(NAME, warnForUnsupportedApi) } const warnForUnsupportedProcessApi = (node: any, [callee]: string[]) => { @@ -448,6 +457,7 @@ function registerUnsupportedApiHooks( node.loc ) ) + return true } parser.hooks.callMemberChain diff --git a/packages/next/server/web/sandbox/context.ts b/packages/next/server/web/sandbox/context.ts index 0ca80f333e7de..ade92c17a8e24 100644 --- a/packages/next/server/web/sandbox/context.ts +++ b/packages/next/server/web/sandbox/context.ts @@ -204,17 +204,20 @@ function createContext( CryptoKey: polyfills.CryptoKey, Crypto: polyfills.Crypto, crypto: new polyfills.Crypto(), + DataView, File, FormData, process: createProcessPolyfill(options), ReadableStream, setInterval, setTimeout, + queueMicrotask, TextDecoder, TextEncoder, TransformStream, URL, URLSearchParams, + WebAssembly, // Indexed collections Array, @@ -239,6 +242,15 @@ function createContext( // Structured data ArrayBuffer, SharedArrayBuffer, + + // These APIs are supported by the Edge runtime, but not by the version of Node.js we're using + // Since we'll soon replace this sandbox with the edge-runtime itself, it's not worth polyfilling. + // ReadableStreamBYOBReader, + // ReadableStreamDefaultReader, + // structuredClone, + // SubtleCrypto, + // WritableStream, + // WritableStreamDefaultWriter, } for (const name of EDGE_UNSUPPORTED_NODE_APIS) { addStub(context, name, options) diff --git a/packages/next/shared/lib/constants.ts b/packages/next/shared/lib/constants.ts index 4db53f5ef07c5..8d5e7694514ca 100644 --- a/packages/next/shared/lib/constants.ts +++ b/packages/next/shared/lib/constants.ts @@ -68,16 +68,18 @@ export const OPTIMIZED_FONT_PROVIDERS = [ export const STATIC_STATUS_PAGES = ['/500'] export const TRACE_OUTPUT_VERSION = 1 +// comparing +// https://github.com/vercel/runtimes/blob/main/packages/edge-functions-bridge/src/runtime-primitives.ts +// with +// https://nodejs.org/docs/latest/api/globals.html export const EDGE_UNSUPPORTED_NODE_APIS = [ 'clearImmediate', 'setImmediate', - 'structuredClone', - 'queueMicrotask', // TODO allow that one? 'BroadcastChannel', + 'Buffer', 'ByteLengthQueuingStrategy', 'CompressionStream', 'CountQueuingStrategy', - 'CryptoKey', 'DecompressionStream', 'DomException', 'Event', @@ -86,14 +88,10 @@ export const EDGE_UNSUPPORTED_NODE_APIS = [ 'MessageEvent', 'MessagePort', 'ReadableByteStreamController', - 'ReadableStreamBYOBReader', 'ReadableStreamBYOBRequest', 'ReadableStreamDefaultController', - 'ReadableStreamDefaultReader', - 'SubtleCrypto', 'TextDecoderStream', 'TextEncoderStream', 'TransformStreamDefaultController', 'WritableStreamDefaultController', - 'WritableStreamDefaultWriter', ] diff --git a/test/integration/middleware/with-node.js-apis/pages/_middleware.js b/test/integration/middleware/with-node.js-apis/pages/_middleware.js index 57f7ee3094345..7886e1c8ccbdb 100644 --- a/test/integration/middleware/with-node.js-apis/pages/_middleware.js +++ b/test/integration/middleware/with-node.js-apis/pages/_middleware.js @@ -3,18 +3,15 @@ import { NextResponse } from 'next/server' export default function middleware({ nextUrl: { pathname } }) { let handle + if (pathname === '/Buffer') { + Buffer.from('') + } if (pathname === '/setImmediate') { handle = setImmediate(() => {}) } if (pathname === '/clearImmediate') { clearImmediate(handle) } - if (pathname === '/structuredClone') { - structuredClone({}) - } - if (pathname === '/queueMicrotask') { - queueMicrotask(() => {}) - } if (pathname === '/process.cwd') { console.log(process.cwd()) } @@ -33,9 +30,6 @@ export default function middleware({ nextUrl: { pathname } }) { if (pathname === '/CountQueuingStrategy') { new CountQueuingStrategy() } - if (pathname === '/CryptoKey') { - new CryptoKey() - } if (pathname === '/DecompressionStream') { new DecompressionStream() } @@ -60,21 +54,12 @@ export default function middleware({ nextUrl: { pathname } }) { if (pathname === '/ReadableByteStreamController') { new ReadableByteStreamController() } - if (pathname === '/ReadableStreamBYOBReader') { - new ReadableStreamBYOBReader() - } if (pathname === '/ReadableStreamBYOBRequest') { new ReadableStreamBYOBRequest() } if (pathname === '/ReadableStreamDefaultController') { new ReadableStreamDefaultController() } - if (pathname === '/ReadableStreamDefaultReader') { - new ReadableStreamDefaultReader() - } - if (pathname === '/SubtleCrypto') { - new SubtleCrypto() - } if (pathname === '/TextDecoderStream') { new TextDecoderStream() } @@ -87,8 +72,5 @@ export default function middleware({ nextUrl: { pathname } }) { if (pathname === '/WritableStreamDefaultController') { new WritableStreamDefaultController() } - if (pathname === '/WritableStreamDefaultWriter') { - new WritableStreamDefaultWriter() - } return NextResponse.next() } diff --git a/test/integration/middleware/with-node.js-apis/test/index.test.ts b/test/integration/middleware/with-node.js-apis/test/index.test.ts index 2c0211e3b1168..3df02b533e1b0 100644 --- a/test/integration/middleware/with-node.js-apis/test/index.test.ts +++ b/test/integration/middleware/with-node.js-apis/test/index.test.ts @@ -1,5 +1,6 @@ /* eslint-env jest */ +import { remove } from 'fs-extra' import { fetchViaHTTP, findPort, @@ -15,8 +16,6 @@ jest.setTimeout(1000 * 60 * 2) const unsupportedFunctions = [ 'setImmediate', 'clearImmediate', - 'structuredClone', - 'queueMicrotask', // no need to test all of the process methods 'process.cwd', 'process.getuid', @@ -26,7 +25,6 @@ const unsupportedClasses = [ 'ByteLengthQueuingStrategy', 'CompressionStream', 'CountQueuingStrategy', - 'CryptoKey', 'DecompressionStream', 'DomException', 'Event', @@ -35,16 +33,12 @@ const unsupportedClasses = [ 'MessageEvent', 'MessagePort', 'ReadableByteStreamController', - 'ReadableStreamBYOBReader', 'ReadableStreamBYOBRequest', 'ReadableStreamDefaultController', - 'ReadableStreamDefaultReader', - 'SubtleCrypto', 'TextDecoderStream', 'TextEncoderStream', 'TransformStreamDefaultController', 'WritableStreamDefaultController', - 'WritableStreamDefaultWriter', ] describe('Middleware using Node.js API', () => { @@ -71,6 +65,10 @@ describe('Middleware using Node.js API', () => { afterAll(() => killApp(app)) it.each([ + { + api: 'Buffer', + error: `Cannot read properties of undefined (reading 'from')`, + }, ...unsupportedFunctions.map((api) => ({ api, error: `${api} is not a function`, @@ -94,6 +92,7 @@ Learn more: https://nextjs.org/docs/api-reference/edge-runtime`) let buildResult beforeAll(async () => { + await remove(join(appDir, '.next')) buildResult = await nextBuild(appDir, undefined, { stderr: true, stdout: true, @@ -101,10 +100,12 @@ Learn more: https://nextjs.org/docs/api-reference/edge-runtime`) }) it.each( - [...unsupportedFunctions, ...unsupportedClasses].map((api, index) => ({ - api, - line: 5 + index * 3, - })) + ['Buffer', ...unsupportedFunctions, ...unsupportedClasses].map( + (api, index) => ({ + api, + line: 5 + index * 3, + }) + ) )(`warns for $api during build`, ({ api, line }) => { expect(buildResult.stderr) .toContain(`You're using a Node.js API (${api} at line: ${line}) which is not supported in the Edge Runtime that Middleware uses. From 17ad7396e403c9c3615e5f8852a8ec35d863b14d Mon Sep 17 00:00:00 2001 From: feugy Date: Wed, 18 May 2022 15:34:21 +0200 Subject: [PATCH 3/4] chore: better comment --- packages/next/shared/lib/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/shared/lib/constants.ts b/packages/next/shared/lib/constants.ts index 8d5e7694514ca..490a656dd9512 100644 --- a/packages/next/shared/lib/constants.ts +++ b/packages/next/shared/lib/constants.ts @@ -69,7 +69,7 @@ export const STATIC_STATUS_PAGES = ['/500'] export const TRACE_OUTPUT_VERSION = 1 // comparing -// https://github.com/vercel/runtimes/blob/main/packages/edge-functions-bridge/src/runtime-primitives.ts +// https://nextjs.org/docs/api-reference/edge-runtime // with // https://nodejs.org/docs/latest/api/globals.html export const EDGE_UNSUPPORTED_NODE_APIS = [ From 9e2a6d10eee2649087fde28154beefd4617836f4 Mon Sep 17 00:00:00 2001 From: feugy Date: Fri, 20 May 2022 10:16:17 +0200 Subject: [PATCH 4/4] chore: trigger CI --- test/integration/middleware-with-node.js-apis/pages/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/middleware-with-node.js-apis/pages/index.js b/test/integration/middleware-with-node.js-apis/pages/index.js index 7786f48037692..993395e44ca89 100644 --- a/test/integration/middleware-with-node.js-apis/pages/index.js +++ b/test/integration/middleware-with-node.js-apis/pages/index.js @@ -1,3 +1,3 @@ -export default function Home() { +export default function HomePage() { return
A page
}