From d98a638d50d385e829feaeadee860a141092d50e Mon Sep 17 00:00:00 2001 From: feugy Date: Tue, 5 Jul 2022 14:45:25 +0200 Subject: [PATCH 1/7] fix: edge routes have different error handling as middleware --- .../webpack/plugins/middleware-plugin.ts | 7 +- .../error-overlay/format-webpack-messages.js | 2 +- packages/next/server/dev/next-dev-server.ts | 7 +- packages/next/server/next-server.ts | 15 +- packages/next/server/web/sandbox/context.ts | 42 ++-- packages/next/server/web/sandbox/sandbox.ts | 4 +- .../lib/square.wasm | Bin .../lib/utils.js | 8 + .../lib/wasm.js | 0 .../middleware.js | 21 +- .../next.config.js | 0 .../pages/api/route.js | 26 +++ .../pages/index.js | 0 .../test/index.test.js | 183 ++++++++++++++++++ .../lib/utils.js | 47 +++++ .../middleware.js | 7 + .../pages/api/route.js | 8 + .../pages/index.js | 0 .../test/index.test.ts | 54 +++--- .../test/index.test.js | 139 ------------- .../test/index.test.ts | 6 +- .../middleware.js | 70 ------- .../index.test.ts | 158 --------------- .../middleware-with-dynamic-code/square.wasm | Bin 63 -> 0 bytes 24 files changed, 350 insertions(+), 454 deletions(-) rename test/integration/{middleware-dynamic-code => edge-runtime-dynamic-code}/lib/square.wasm (100%) rename test/integration/{middleware-dynamic-code => edge-runtime-dynamic-code}/lib/utils.js (53%) rename test/integration/{middleware-dynamic-code => edge-runtime-dynamic-code}/lib/wasm.js (100%) rename test/integration/{middleware-dynamic-code => edge-runtime-dynamic-code}/middleware.js (58%) rename test/integration/{middleware-dynamic-code => edge-runtime-dynamic-code}/next.config.js (100%) create mode 100644 test/integration/edge-runtime-dynamic-code/pages/api/route.js rename test/integration/{middleware-dynamic-code => edge-runtime-dynamic-code}/pages/index.js (100%) create mode 100644 test/integration/edge-runtime-dynamic-code/test/index.test.js create mode 100644 test/integration/edge-runtime-with-node.js-apis/lib/utils.js create mode 100644 test/integration/edge-runtime-with-node.js-apis/middleware.js create mode 100644 test/integration/edge-runtime-with-node.js-apis/pages/api/route.js rename test/integration/{middleware-with-node.js-apis => edge-runtime-with-node.js-apis}/pages/index.js (100%) rename test/integration/{middleware-with-node.js-apis => edge-runtime-with-node.js-apis}/test/index.test.ts (64%) delete mode 100644 test/integration/middleware-dynamic-code/test/index.test.js delete mode 100644 test/integration/middleware-with-node.js-apis/middleware.js delete mode 100644 test/production/middleware-with-dynamic-code/index.test.ts delete mode 100644 test/production/middleware-with-dynamic-code/square.wasm diff --git a/packages/next/build/webpack/plugins/middleware-plugin.ts b/packages/next/build/webpack/plugins/middleware-plugin.ts index eedd8d31b1c3..ebdd8a173a35 100644 --- a/packages/next/build/webpack/plugins/middleware-plugin.ts +++ b/packages/next/build/webpack/plugins/middleware-plugin.ts @@ -380,7 +380,10 @@ Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime`, } } }) - registerUnsupportedApiHooks(parser, compilation) + if (!dev) { + // do not issue compilation warning on dev: invoking code will provide details + registerUnsupportedApiHooks(parser, compilation) + } } } @@ -445,7 +448,7 @@ function getExtractMetadata(params: { compilation.errors.push( buildWebpackError({ - message: `Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Middleware ${entryName}${ + message: `Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime ${ typeof buildInfo.usingIndirectEval !== 'boolean' ? `\nUsed by ${Array.from(buildInfo.usingIndirectEval).join( ', ' diff --git a/packages/next/client/dev/error-overlay/format-webpack-messages.js b/packages/next/client/dev/error-overlay/format-webpack-messages.js index b99ea8e0692f..ed32aa2c0294 100644 --- a/packages/next/client/dev/error-overlay/format-webpack-messages.js +++ b/packages/next/client/dev/error-overlay/format-webpack-messages.js @@ -44,7 +44,7 @@ function formatMessage(message, verbose) { message.moduleTrace && message.moduleTrace.filter( (trace) => - !/next-(middleware|client-pages|flight-(client|server))-loader\.js/.test( + !/next-(middleware|client-pages|edge-function|flight-(client|server))-loader\.js/.test( trace.originName ) ) diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts index f190a610ec41..ee626ccf678e 100644 --- a/packages/next/server/dev/next-dev-server.ts +++ b/packages/next/server/dev/next-dev-server.ts @@ -714,7 +714,12 @@ export default class DevServer extends Server { page: string }) { try { - return super.runEdgeFunction(params) + return super.runEdgeFunction({ + ...params, + onWarning: (warn) => { + this.logErrorWithOriginalStack(warn, 'warning') + }, + }) } catch (error) { if (error instanceof DecodeError) { throw error diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index f3e4ec22c14b..1b0c90806f9f 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -1247,12 +1247,7 @@ export default class NextNodeServer extends BaseServer { body: originalBody?.cloneBodyStream(), }, useCache: !this.nextConfig.experimental.runtime, - onWarning: (warning: Error) => { - if (params.onWarning) { - warning.message += ` "./${middlewareInfo.name}"` - params.onWarning(warning) - } - }, + onWarning: params.onWarning, }) for (let [key, value] of result.response.headers) { @@ -1531,6 +1526,7 @@ export default class NextNodeServer extends BaseServer { query: ParsedUrlQuery params: Params | undefined page: string + onWarning?: (warning: Error) => void }): Promise { let middlewareInfo: ReturnType | undefined @@ -1584,12 +1580,7 @@ export default class NextNodeServer extends BaseServer { : requestToBodyStream(nodeReq.originalRequest), }, useCache: !this.nextConfig.experimental.runtime, - onWarning: (_warning: Error) => { - // if (params.onWarning) { - // warning.message += ` "./${middlewareInfo.name}"` - // params.onWarning(warning) - // } - }, + onWarning: params.onWarning, }) params.res.statusCode = result.response.status diff --git a/packages/next/server/web/sandbox/context.ts b/packages/next/server/web/sandbox/context.ts index f166fe7a3f96..45c8df16c4cf 100644 --- a/packages/next/server/web/sandbox/context.ts +++ b/packages/next/server/web/sandbox/context.ts @@ -120,7 +120,7 @@ async function createModuleContext(options: ModuleContextOptions) { if (!warnedEvals.has(key)) { const warning = getServerError( new Error( - `Dynamic Code Evaluation (e. g. 'eval', 'new Function') not allowed in Middleware` + `Dynamic Code Evaluation (e. g. 'eval', 'new Function') not allowed in Edge Runtime` ), 'edge-server' ) @@ -137,7 +137,7 @@ async function createModuleContext(options: ModuleContextOptions) { const key = fn.toString() if (!warnedWasmCodegens.has(key)) { const warning = getServerError( - new Error(`Dynamic WASM code generation (e. g. 'WebAssembly.compile') not allowed in Middleware. + new Error(`Dynamic WASM code generation (e. g. 'WebAssembly.compile') not allowed in Edge Runtime. Learn More: https://nextjs.org/docs/messages/middleware-dynamic-wasm-compilation`), 'edge-server' ) @@ -164,7 +164,7 @@ Learn More: https://nextjs.org/docs/messages/middleware-dynamic-wasm-compilation const key = fn.toString() if (instantiatedFromBuffer && !warnedWasmCodegens.has(key)) { const warning = getServerError( - new Error(`Dynamic WASM code generation ('WebAssembly.instantiate' with a buffer parameter) not allowed in Middleware. + new Error(`Dynamic WASM code generation ('WebAssembly.instantiate' with a buffer parameter) not allowed in Edge Runtime. Learn More: https://nextjs.org/docs/messages/middleware-dynamic-wasm-compilation`), 'edge-server' ) @@ -240,7 +240,7 @@ Learn More: https://nextjs.org/docs/messages/middleware-dynamic-wasm-compilation } for (const name of EDGE_UNSUPPORTED_NODE_APIS) { - addStub(context, name, options) + addStub(context, name) } Object.assign(context, wasm) @@ -285,9 +285,7 @@ function buildEnvironmentVariablesFrom( return env } -function createProcessPolyfill( - options: Pick -) { +function createProcessPolyfill(options: Pick) { const env = buildEnvironmentVariablesFrom(options.env) const processPolyfill = { env } @@ -296,8 +294,7 @@ function createProcessPolyfill( if (key === 'env') continue Object.defineProperty(processPolyfill, key, { get() { - emitWarning(`process.${key}`, options) - return overridenValue[key] + return overridenValue[key] ?? (() => emitError(`process.${key}`)) }, set(value) { overridenValue[key] = value @@ -308,35 +305,22 @@ function createProcessPolyfill( return processPolyfill } -const warnedAlready = new Set() - -function addStub( - context: Primitives, - name: string, - contextOptions: Pick -) { +function addStub(context: Primitives, name: string) { Object.defineProperty(context, name, { get() { - emitWarning(name, contextOptions) + emitError(name) return undefined }, enumerable: false, }) } -function emitWarning( - name: string, - contextOptions: Pick -) { - if (!warnedAlready.has(name)) { - const warning = - new Error(`A Node.js API is used (${name}) which is not supported in the Edge Runtime. +function emitError(name: string) { + const error = + new Error(`A Node.js API is used (${name}) which is not supported in the Edge Runtime. Learn more: https://nextjs.org/docs/api-reference/edge-runtime`) - warning.name = 'NodejsRuntimeApiInMiddlewareWarning' - contextOptions.onWarning(warning) - console.warn(warning.message) - warnedAlready.add(name) - } + decorateServerError(error, 'edge-server') + throw error } function decorateUnhandledError(error: any) { diff --git a/packages/next/server/web/sandbox/sandbox.ts b/packages/next/server/web/sandbox/sandbox.ts index 0390a8f12275..62f42c491f53 100644 --- a/packages/next/server/web/sandbox/sandbox.ts +++ b/packages/next/server/web/sandbox/sandbox.ts @@ -8,7 +8,7 @@ export const ErrorSource = Symbol('SandboxError') type RunnerFn = (params: { name: string env: string[] - onWarning: (warn: Error) => void + onWarning?: (warn: Error) => void paths: string[] request: RequestData useCache: boolean @@ -19,7 +19,7 @@ type RunnerFn = (params: { export const run = withTaggedErrors(async (params) => { const { runtime, evaluateInContext } = await getModuleContext({ moduleName: params.name, - onWarning: params.onWarning, + onWarning: params.onWarning ?? (() => {}), useCache: params.useCache !== false, env: params.env, edgeFunctionEntry: params.edgeFunctionEntry, diff --git a/test/integration/middleware-dynamic-code/lib/square.wasm b/test/integration/edge-runtime-dynamic-code/lib/square.wasm similarity index 100% rename from test/integration/middleware-dynamic-code/lib/square.wasm rename to test/integration/edge-runtime-dynamic-code/lib/square.wasm diff --git a/test/integration/middleware-dynamic-code/lib/utils.js b/test/integration/edge-runtime-dynamic-code/lib/utils.js similarity index 53% rename from test/integration/middleware-dynamic-code/lib/utils.js rename to test/integration/edge-runtime-dynamic-code/lib/utils.js index 60fdfbe82411..94c8b93d719b 100644 --- a/test/integration/middleware-dynamic-code/lib/utils.js +++ b/test/integration/edge-runtime-dynamic-code/lib/utils.js @@ -1,3 +1,11 @@ +export const useCases = { + eval: 'using-eval', + noEval: 'not-using-eval', + wasmCompile: 'using-webassembly-compile', + wasmInstanciate: 'using-webassembly-instantiate', + wasmBufferInstanciate: 'using-webassembly-instantiate-with-buffer', +} + export async function usingEval() { // eslint-disable-next-line no-eval return { value: eval('100') } diff --git a/test/integration/middleware-dynamic-code/lib/wasm.js b/test/integration/edge-runtime-dynamic-code/lib/wasm.js similarity index 100% rename from test/integration/middleware-dynamic-code/lib/wasm.js rename to test/integration/edge-runtime-dynamic-code/lib/wasm.js diff --git a/test/integration/middleware-dynamic-code/middleware.js b/test/integration/edge-runtime-dynamic-code/middleware.js similarity index 58% rename from test/integration/middleware-dynamic-code/middleware.js rename to test/integration/edge-runtime-dynamic-code/middleware.js index da82cfa24bbb..68cfd8b418a4 100644 --- a/test/integration/middleware-dynamic-code/middleware.js +++ b/test/integration/edge-runtime-dynamic-code/middleware.js @@ -1,4 +1,5 @@ -import { notUsingEval, usingEval } from './lib/utils' +import { NextResponse } from 'next/server' +import { useCases, notUsingEval, usingEval } from './lib/utils' import { usingWebAssemblyCompile, usingWebAssemblyInstantiate, @@ -6,37 +7,41 @@ import { } from './lib/wasm' export async function middleware(request) { - if (request.nextUrl.pathname === '/using-eval') { + if (request.nextUrl.pathname === `/${useCases.eval}`) { return new Response(null, { headers: { data: JSON.stringify(await usingEval()) }, }) } - if (request.nextUrl.pathname === '/not-using-eval') { + if (request.nextUrl.pathname === `/${useCases.noEval}`) { return new Response(null, { headers: { data: JSON.stringify(await notUsingEval()) }, }) } - if (request.nextUrl.pathname === '/using-webassembly-compile') { + if (request.nextUrl.pathname === `/${useCases.wasmCompile}`) { return new Response(null, { headers: { data: JSON.stringify(await usingWebAssemblyCompile(9)) }, }) } - if (request.nextUrl.pathname === '/using-webassembly-instantiate') { + if (request.nextUrl.pathname === `/${useCases.wasmInstanciate}`) { return new Response(null, { headers: { data: JSON.stringify(await usingWebAssemblyInstantiate(9)) }, }) } - if ( - request.nextUrl.pathname === '/using-webassembly-instantiate-with-buffer' - ) { + if (request.nextUrl.pathname === `/${useCases.wasmBufferInstanciate}`) { return new Response(null, { headers: { data: JSON.stringify(await usingWebAssemblyInstantiateWithBuffer(9)), }, }) } + + return NextResponse.next() +} + +export const config = { + matcher: Object.values(useCases).map((route) => `/${route}`), } diff --git a/test/integration/middleware-dynamic-code/next.config.js b/test/integration/edge-runtime-dynamic-code/next.config.js similarity index 100% rename from test/integration/middleware-dynamic-code/next.config.js rename to test/integration/edge-runtime-dynamic-code/next.config.js diff --git a/test/integration/edge-runtime-dynamic-code/pages/api/route.js b/test/integration/edge-runtime-dynamic-code/pages/api/route.js new file mode 100644 index 000000000000..1d73702aec60 --- /dev/null +++ b/test/integration/edge-runtime-dynamic-code/pages/api/route.js @@ -0,0 +1,26 @@ +import { useCases, notUsingEval, usingEval } from '../../lib/utils' +import { + usingWebAssemblyCompile, + usingWebAssemblyInstantiate, + usingWebAssemblyInstantiateWithBuffer, +} from '../../lib/wasm' + +export default async function handler(request) { + const useCase = request.nextUrl.searchParams.get('case') + + return Response.json( + useCase === useCases.eval + ? await usingEval() + : useCase === useCases.noEval + ? await notUsingEval() + : useCase === useCases.wasmCompile + ? await usingWebAssemblyCompile(9) + : useCase === useCases.wasmInstanciate + ? await usingWebAssemblyInstantiate(9) + : useCase === useCases.wasmBufferInstanciate + ? await usingWebAssemblyInstantiateWithBuffer(9) + : { ok: true } + ) +} + +export const config = { runtime: 'experimental-edge' } diff --git a/test/integration/middleware-dynamic-code/pages/index.js b/test/integration/edge-runtime-dynamic-code/pages/index.js similarity index 100% rename from test/integration/middleware-dynamic-code/pages/index.js rename to test/integration/edge-runtime-dynamic-code/pages/index.js diff --git a/test/integration/edge-runtime-dynamic-code/test/index.test.js b/test/integration/edge-runtime-dynamic-code/test/index.test.js new file mode 100644 index 000000000000..54c4cbc0dc21 --- /dev/null +++ b/test/integration/edge-runtime-dynamic-code/test/index.test.js @@ -0,0 +1,183 @@ +/* eslint-env jest */ + +import stripAnsi from 'next/dist/compiled/strip-ansi' +import { join } from 'path' +import { + fetchViaHTTP, + findPort, + killApp, + launchApp, + nextBuild, + renderViaHTTP, + waitFor, +} from 'next-test-utils' + +const EVAL_ERROR = `Dynamic Code Evaluation (e. g. 'eval', 'new Function') not allowed in Edge Runtime` +const DYNAMIC_CODE_ERROR = `Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime` +const WASM_COMPILE_ERROR = `Dynamic WASM code generation (e. g. 'WebAssembly.compile') not allowed in Edge Runtime` +const WASM_INSTANTIATE_ERROR = `Dynamic WASM code generation ('WebAssembly.instantiate' with a buffer parameter) not allowed in Edge Runtime` + +jest.setTimeout(1000 * 60 * 2) +const context = { + appDir: join(__dirname, '../'), +} + +describe('Page using eval in dev mode', () => { + let output = '' + + beforeAll(async () => { + context.appPort = await findPort() + context.app = await launchApp(context.appDir, context.appPort, { + env: { __NEXT_TEST_WITH_DEVTOOL: 1 }, + onStdout(msg) { + output += msg + }, + onStderr(msg) { + output += msg + }, + }) + }) + + beforeEach(() => (output = '')) + afterAll(() => killApp(context.app)) + + it('does issue Dynamic code evaluation warnings', async () => { + const html = await renderViaHTTP(context.appPort, '/') + expect(html).toMatch(/>.*?100.*?and.*?100.*?<\//) + await waitFor(500) + expect(output).not.toContain(EVAL_ERROR) + expect(output).not.toContain(DYNAMIC_CODE_ERROR) + expect(output).not.toContain(WASM_COMPILE_ERROR) + expect(output).not.toContain(WASM_INSTANTIATE_ERROR) + }) +}) + +describe.each([ + { + title: 'Middleware', + computeRoute(useCase) { + return `/${useCase}` + }, + async extractValue(response) { + return JSON.parse(response.headers.get('data')).value + }, + }, + { + title: 'Edge route', + computeRoute(useCase) { + return `/api/route?case=${useCase}` + }, + async extractValue(response) { + return (await response.json()).value + }, + }, +])( + '$title usage of dynamic code evaluation', + ({ extractValue, computeRoute }) => { + describe('dev mode', () => { + let output = '' + + beforeAll(async () => { + context.appPort = await findPort() + context.app = await launchApp(context.appDir, context.appPort, { + env: { __NEXT_TEST_WITH_DEVTOOL: 1 }, + onStdout(msg) { + output += msg + }, + onStderr(msg) { + output += msg + }, + }) + }) + + beforeEach(() => (output = '')) + afterAll(() => killApp(context.app)) + + it('shows a warning when running code with eval', async () => { + const res = await fetchViaHTTP( + context.appPort, + computeRoute('using-eval') + ) + expect(await extractValue(res)).toEqual(100) + await waitFor(500) + expect(output).toContain(EVAL_ERROR) + expect(output).toContain('DynamicCodeEvaluationWarning') + // TODO check why that has a backslash on windows + expect(output).toMatch(/lib[\\/]utils\.js/) + expect(output).toContain('usingEval') + expect(stripAnsi(output)).toContain("value: eval('100')") + }) + + it('does not show warning when no code uses eval', async () => { + const res = await fetchViaHTTP( + context.appPort, + computeRoute('not-using-eval') + ) + expect(await extractValue(res)).toEqual(100) + await waitFor(500) + expect(output).not.toContain('Dynamic Code Evaluation') + }) + + it('shows a warning when running WebAssembly.compile', async () => { + const res = await fetchViaHTTP( + context.appPort, + computeRoute('using-webassembly-compile') + ) + expect(await extractValue(res)).toEqual(81) + await waitFor(500) + expect(output).toContain(WASM_COMPILE_ERROR) + expect(output).toContain('DynamicWasmCodeGenerationWarning') + expect(output).toMatch(/lib[\\/]wasm\.js/) + expect(output).toContain('usingWebAssemblyCompile') + expect(stripAnsi(output)).toContain( + 'await WebAssembly.compile(SQUARE_WASM_BUFFER)' + ) + }) + + it('shows a warning when running WebAssembly.instantiate with a buffer parameter', async () => { + const res = await fetchViaHTTP( + context.appPort, + computeRoute('using-webassembly-instantiate-with-buffer') + ) + expect(await extractValue(res)).toEqual(81) + await waitFor(500) + expect(output).toContain(WASM_INSTANTIATE_ERROR) + expect(output).toContain('DynamicWasmCodeGenerationWarning') + expect(output).toMatch(/lib[\\/]wasm\.js/) + expect(output).toContain('usingWebAssemblyInstantiateWithBuffer') + expect(stripAnsi(output)).toContain( + 'await WebAssembly.instantiate(SQUARE_WASM_BUFFER, {})' + ) + }) + + it('does not show a warning when running WebAssembly.instantiate with a module parameter', async () => { + const res = await fetchViaHTTP( + context.appPort, + computeRoute('using-webassembly-instantiate') + ) + expect(await extractValue(res)).toEqual(81) + await waitFor(500) + expect(output).not.toContain(WASM_INSTANTIATE_ERROR) + expect(output).not.toContain('DynamicWasmCodeGenerationWarning') + }) + }) + + describe('production mode', () => { + let buildResult + + beforeAll(async () => { + buildResult = await nextBuild(context.appDir, undefined, { + stderr: true, + stdout: true, + }) + }) + + it('should have middleware warning during build', () => { + expect(buildResult.stderr).toContain(`Failed to compile`) + expect(buildResult.stderr).toContain(`Used by usingEval, usingEvalSync`) + expect(buildResult.stderr).toContain(`Used by usingWebAssemblyCompile`) + expect(buildResult.stderr).toContain(DYNAMIC_CODE_ERROR) + }) + }) + } +) diff --git a/test/integration/edge-runtime-with-node.js-apis/lib/utils.js b/test/integration/edge-runtime-with-node.js-apis/lib/utils.js new file mode 100644 index 000000000000..22dedbc0b3f6 --- /dev/null +++ b/test/integration/edge-runtime-with-node.js-apis/lib/utils.js @@ -0,0 +1,47 @@ +/* eslint-disable no-undef */ +export function invokeNodeAPI(useCase) { + let handle + if (useCase === 'Buffer') { + Buffer.from('') + } else if (useCase === 'setImmediate') { + handle = setImmediate(() => {}) + } else if (useCase === 'clearImmediate') { + clearImmediate(handle) + } else if (useCase === 'process.cwd') { + console.log(process.cwd()) + } else if (useCase === 'process.getuid') { + console.log(process.getuid()) + } else if (useCase === 'BroadcastChannel') { + new BroadcastChannel() + } else if (useCase === 'ByteLengthQueuingStrategy') { + new ByteLengthQueuingStrategy() + } else if (useCase === 'CompressionStream') { + new CompressionStream() + } else if (useCase === 'CountQueuingStrategy') { + new CountQueuingStrategy() + } else if (useCase === 'DecompressionStream') { + new DecompressionStream() + } else if (useCase === 'DomException') { + new DomException() + } else if (useCase === 'MessageChannel') { + new MessageChannel() + } else if (useCase === 'MessageEvent') { + new MessageEvent() + } else if (useCase === 'MessagePort') { + new MessagePort() + } else if (useCase === 'ReadableByteStreamController') { + new ReadableByteStreamController() + } else if (useCase === 'ReadableStreamBYOBRequest') { + new ReadableStreamBYOBRequest() + } else if (useCase === 'ReadableStreamDefaultController') { + new ReadableStreamDefaultController() + } else if (useCase === 'TextDecoderStream') { + new TextDecoderStream() + } else if (useCase === 'TextEncoderStream') { + new TextEncoderStream() + } else if (useCase === 'TransformStreamDefaultController') { + new TransformStreamDefaultController() + } else if (useCase === 'WritableStreamDefaultController') { + new WritableStreamDefaultController() + } +} diff --git a/test/integration/edge-runtime-with-node.js-apis/middleware.js b/test/integration/edge-runtime-with-node.js-apis/middleware.js new file mode 100644 index 000000000000..295f0e82cdef --- /dev/null +++ b/test/integration/edge-runtime-with-node.js-apis/middleware.js @@ -0,0 +1,7 @@ +import { NextResponse } from 'next/server' +import { invokeNodeAPI } from './lib/utils' + +export default function middleware({ nextUrl: { pathname } }) { + invokeNodeAPI(pathname.slice(1)) + return NextResponse.next() +} diff --git a/test/integration/edge-runtime-with-node.js-apis/pages/api/route.js b/test/integration/edge-runtime-with-node.js-apis/pages/api/route.js new file mode 100644 index 000000000000..8c3802625615 --- /dev/null +++ b/test/integration/edge-runtime-with-node.js-apis/pages/api/route.js @@ -0,0 +1,8 @@ +import { invokeNodeAPI } from '../../lib/utils' + +export default async function handler(request) { + invokeNodeAPI(request.nextUrl.searchParams.get('case')) + return Response.json({ ok: true }) +} + +export const config = { runtime: 'experimental-edge' } diff --git a/test/integration/middleware-with-node.js-apis/pages/index.js b/test/integration/edge-runtime-with-node.js-apis/pages/index.js similarity index 100% rename from test/integration/middleware-with-node.js-apis/pages/index.js rename to test/integration/edge-runtime-with-node.js-apis/pages/index.js diff --git a/test/integration/middleware-with-node.js-apis/test/index.test.ts b/test/integration/edge-runtime-with-node.js-apis/test/index.test.ts similarity index 64% rename from test/integration/middleware-with-node.js-apis/test/index.test.ts rename to test/integration/edge-runtime-with-node.js-apis/test/index.test.ts index 2f68e908c9d2..42507fbff9b3 100644 --- a/test/integration/middleware-with-node.js-apis/test/index.test.ts +++ b/test/integration/edge-runtime-with-node.js-apis/test/index.test.ts @@ -1,8 +1,8 @@ /* eslint-env jest */ import { remove } from 'fs-extra' +import stripAnsi from 'next/dist/compiled/strip-ansi' import { - check, fetchViaHTTP, findPort, killApp, @@ -40,7 +40,20 @@ const unsupportedClasses = [ 'WritableStreamDefaultController', ] -describe('Middleware using Node.js API', () => { +describe.each([ + { + title: 'Middleware', + computeRoute(useCase) { + return `/${useCase}` + }, + }, + { + title: 'Edge route', + computeRoute(useCase) { + return `/api/route?case=${useCase}` + }, + }, +])('$title using Node.js API', ({ computeRoute }) => { const appDir = join(__dirname, '..') describe('dev mode', () => { @@ -65,36 +78,22 @@ describe('Middleware using Node.js API', () => { afterAll(() => killApp(app)) it.each([ - { - api: 'Buffer', - error: process.version.startsWith('v16') - ? `Cannot read properties of undefined (reading 'from')` - : `Cannot read property 'from' of undefined`, - }, ...unsupportedFunctions.map((api) => ({ api, - error: `${api} is not a function`, + errorHighlight: `${api}(`, })), ...unsupportedClasses.map((api) => ({ api, - error: `${api} is not a constructor`, + errorHighlight: `new ${api}(`, })), - ])(`shows error when using $api`, async ({ api, error }) => { - const res = await fetchViaHTTP(appPort, `/${api}`) - await waitFor(500) + ])(`shows error when using $api`, async ({ api, errorHighlight }) => { + const res = await fetchViaHTTP(appPort, computeRoute(api)) expect(res.status).toBe(500) - await check( - () => - output.includes(`A Node.js API is used (${api}) which is not supported in the Edge Runtime. + await waitFor(500) + expect(output) + .toInclude(`A Node.js API is used (${api}) which is not supported in the Edge Runtime. Learn more: https://nextjs.org/docs/api-reference/edge-runtime`) - ? 'success' - : output, - 'success' - ) - await check( - () => (output.includes(`TypeError: ${error}`) ? 'success' : output), - 'success' - ) + expect(stripAnsi(output)).toInclude(errorHighlight) }) }) @@ -113,13 +112,10 @@ Learn more: https://nextjs.org/docs/api-reference/edge-runtime`) ['Buffer', ...unsupportedFunctions, ...unsupportedClasses].map( (api, index) => ({ api, - line: 5 + index * 3, }) ) - )(`warns for $api during build`, ({ api, line }) => { - expect(buildResult.stderr) - .toContain(`A Node.js API is used (${api} at line: ${line}) which is not supported in the Edge Runtime. -Learn more: https://nextjs.org/docs/api-reference/edge-runtime`) + )(`warns for $api during build`, ({ api }) => { + expect(buildResult.stderr).toContain(`A Node.js API is used (${api}`) }) }) }) diff --git a/test/integration/middleware-dynamic-code/test/index.test.js b/test/integration/middleware-dynamic-code/test/index.test.js deleted file mode 100644 index 0f113d468161..000000000000 --- a/test/integration/middleware-dynamic-code/test/index.test.js +++ /dev/null @@ -1,139 +0,0 @@ -/* eslint-env jest */ - -import stripAnsi from 'next/dist/compiled/strip-ansi' -import { join } from 'path' -import { - fetchViaHTTP, - findPort, - killApp, - launchApp, - nextBuild, - renderViaHTTP, - waitFor, -} from 'next-test-utils' - -const context = {} -const EVAL_ERROR = `Dynamic Code Evaluation (e. g. 'eval', 'new Function') not allowed in Middleware` -const DYNAMIC_CODE_ERROR = `Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Middleware` -const WASM_COMPILE_ERROR = `Dynamic WASM code generation (e. g. 'WebAssembly.compile') not allowed in Middleware` -const WASM_INSTANTIATE_ERROR = `Dynamic WASM code generation ('WebAssembly.instantiate' with a buffer parameter) not allowed in Middleware` - -jest.setTimeout(1000 * 60 * 2) -context.appDir = join(__dirname, '../') - -describe('Middleware usage of dynamic code evaluation', () => { - describe('dev mode', () => { - let output = '' - - beforeAll(async () => { - context.appPort = await findPort() - context.app = await launchApp(context.appDir, context.appPort, { - env: { __NEXT_TEST_WITH_DEVTOOL: 1 }, - onStdout(msg) { - output += msg - }, - onStderr(msg) { - output += msg - }, - }) - }) - - beforeEach(() => (output = '')) - afterAll(() => killApp(context.app)) - - it('shows a warning when running code with eval', async () => { - const res = await fetchViaHTTP(context.appPort, `/using-eval`) - const json = JSON.parse(res.headers.get('data')) - await waitFor(500) - expect(json.value).toEqual(100) - expect(output).toContain(EVAL_ERROR) - expect(output).toContain('DynamicCodeEvaluationWarning') - expect(output).toContain('./middleware') - // TODO check why that has a backslash on windows - expect(output).toMatch(/lib[\\/]utils\.js/) - expect(output).toContain('usingEval') - expect(stripAnsi(output)).toContain("value: eval('100')") - }) - - it('does not show warning when no code uses eval', async () => { - const res = await fetchViaHTTP(context.appPort, `/not-using-eval`) - const json = JSON.parse(res.headers.get('data')) - await waitFor(500) - expect(json.value).toEqual(100) - expect(output).not.toContain('Dynamic Code Evaluation') - }) - - it('does not has problems with eval in page or server code', async () => { - const html = await renderViaHTTP(context.appPort, `/`) - expect(html).toMatch(/>.*?100.*?and.*?100.*?<\//) - await waitFor(500) - expect(output).not.toContain('Dynamic Code Evaluation') - }) - - it('shows a warning when running WebAssembly.compile', async () => { - const res = await fetchViaHTTP( - context.appPort, - `/using-webassembly-compile` - ) - const json = JSON.parse(res.headers.get('data')) - await waitFor(500) - expect(json.value).toEqual(81) - expect(output).toContain(WASM_COMPILE_ERROR) - expect(output).toContain('DynamicWasmCodeGenerationWarning') - expect(output).toContain('./middleware') - expect(output).toMatch(/lib[\\/]wasm\.js/) - expect(output).toContain('usingWebAssemblyCompile') - expect(stripAnsi(output)).toContain( - 'await WebAssembly.compile(SQUARE_WASM_BUFFER)' - ) - }) - - it('shows a warning when running WebAssembly.instantiate w/ a buffer parameter', async () => { - const res = await fetchViaHTTP( - context.appPort, - `/using-webassembly-instantiate-with-buffer` - ) - const json = JSON.parse(res.headers.get('data')) - await waitFor(500) - expect(json.value).toEqual(81) - expect(output).toContain(WASM_INSTANTIATE_ERROR) - expect(output).toContain('DynamicWasmCodeGenerationWarning') - expect(output).toContain('./middleware') - expect(output).toMatch(/lib[\\/]wasm\.js/) - expect(output).toContain('usingWebAssemblyInstantiateWithBuffer') - expect(stripAnsi(output)).toContain( - 'await WebAssembly.instantiate(SQUARE_WASM_BUFFER, {})' - ) - }) - - it('does not show a warning when running WebAssembly.instantiate w/ a module parameter', async () => { - const res = await fetchViaHTTP( - context.appPort, - `/using-webassembly-instantiate` - ) - const json = JSON.parse(res.headers.get('data')) - await waitFor(500) - expect(json.value).toEqual(81) - expect(output).not.toContain(WASM_INSTANTIATE_ERROR) - expect(output).not.toContain('DynamicWasmCodeGenerationWarning') - }) - }) - - describe('production mode', () => { - let buildResult - - beforeAll(async () => { - buildResult = await nextBuild(context.appDir, undefined, { - stderr: true, - stdout: true, - }) - }) - - it('should have middleware warning during build', () => { - expect(buildResult.stderr).toContain(`Failed to compile`) - expect(buildResult.stderr).toContain(`Used by usingEval`) - expect(buildResult.stderr).toContain(`./middleware.js`) - expect(buildResult.stderr).toContain(DYNAMIC_CODE_ERROR) - }) - }) -}) 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 index 31baba32f499..c0ec255d6b1e 100644 --- a/test/integration/middleware-overrides-node.js-api/test/index.test.ts +++ b/test/integration/middleware-overrides-node.js-api/test/index.test.ts @@ -32,14 +32,14 @@ describe('Middleware overriding a Node.js API', () => { afterAll(() => killApp(context.app)) - it('shows a warning but allows overriding', async () => { + it('does not show a warning and allows overriding', async () => { const res = await fetchViaHTTP(context.appPort, '/') await waitFor(500) expect(res.status).toBe(200) - expect(output).toContain('A Node.js API is used (process.cwd') expect(output).toContain('fixed-value') expect(output).not.toContain('TypeError') - expect(output).not.toContain(`A Node.js API is used (process.env`) + expect(output).not.toContain('A Node.js API is used (process.env') + expect(output).not.toContain('A Node.js API is used (process.cwd') }) }) }) diff --git a/test/integration/middleware-with-node.js-apis/middleware.js b/test/integration/middleware-with-node.js-apis/middleware.js deleted file mode 100644 index c635eb43807a..000000000000 --- a/test/integration/middleware-with-node.js-apis/middleware.js +++ /dev/null @@ -1,70 +0,0 @@ -/* eslint-disable no-undef */ -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 === '/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 === '/DecompressionStream') { - new DecompressionStream() - } - if (pathname === '/DomException') { - new DomException() - } - if (pathname === '/MessageChannel') { - new MessageChannel() - } - if (pathname === '/MessageEvent') { - new MessageEvent() - } - if (pathname === '/MessagePort') { - new MessagePort() - } - if (pathname === '/ReadableByteStreamController') { - new ReadableByteStreamController() - } - if (pathname === '/ReadableStreamBYOBRequest') { - new ReadableStreamBYOBRequest() - } - if (pathname === '/ReadableStreamDefaultController') { - new ReadableStreamDefaultController() - } - if (pathname === '/TextDecoderStream') { - new TextDecoderStream() - } - if (pathname === '/TextEncoderStream') { - new TextEncoderStream() - } - if (pathname === '/TransformStreamDefaultController') { - new TransformStreamDefaultController() - } - if (pathname === '/WritableStreamDefaultController') { - new WritableStreamDefaultController() - } - return NextResponse.next() -} diff --git a/test/production/middleware-with-dynamic-code/index.test.ts b/test/production/middleware-with-dynamic-code/index.test.ts deleted file mode 100644 index 26c7cc9ffa4a..000000000000 --- a/test/production/middleware-with-dynamic-code/index.test.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { createNext, FileRef } from 'e2e-utils' -import { join } from 'path' -import { NextInstance } from 'test/lib/next-modes/base' - -describe('Middleware with Dynamic code invocations', () => { - const DYNAMIC_CODE_EVAL_ERROR = `Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Middleware middleware` - - let next: NextInstance - - beforeAll(async () => { - next = await createNext({ - files: { - 'lib/utils.js': '', - 'lib/square.wasm': new FileRef(join(__dirname, 'square.wasm')), - 'pages/index.js': ` - export default function () { return
Hello, world!
} - `, - 'middleware.js': ` - import './lib/utils' - export default function middleware() { - return new Response() - } - `, - }, - dependencies: { - '@apollo/react-hooks': '3.1.5', - '@aws-sdk/client-s3': 'latest', - 'apollo-client': 'latest', - graphql: 'latest', - 'graphql-tag': 'latest', - has: 'latest', - qs: 'latest', - }, - installCommand: 'yarn install', - }) - await next.stop() - }) - - afterAll(() => next.destroy()) - beforeEach(() => next.stop()) - - it('detects dynamic code nested in @apollo/react-hooks', async () => { - await next.patchFile( - 'lib/utils.js', - ` - import { useQuery } from '@apollo/react-hooks' - import gql from 'graphql-tag' - - export default function useGreeting() { - return useQuery( - gql\` - query getGreeting($language: String!) { - greeting(language: $language) { - message - } - } - \`, - { variables: { language: 'english' } } - ) - } - ` - ) - await expect(next.start()).rejects.toThrow() - expect(next.cliOutput).toContain(` -./node_modules/ts-invariant/lib/invariant.esm.js -${DYNAMIC_CODE_EVAL_ERROR}`) - }) - - it('detects dynamic code nested in has', async () => { - await next.patchFile( - 'lib/utils.js', - ` - import has from 'has' - has(Object.prototype, 'hasOwnProperty') - ` - ) - await expect(next.start()).rejects.toThrow() - expect(next.cliOutput).toContain(` -./node_modules/function-bind/implementation.js -${DYNAMIC_CODE_EVAL_ERROR}`) - expect(next.cliOutput).toContain(` -./node_modules/has/src/index.js -${DYNAMIC_CODE_EVAL_ERROR}`) - }) - - it('detects dynamic code nested in qs', async () => { - await next.patchFile( - 'lib/utils.js', - ` - import qs from 'qs' - qs.parse('a=c') - ` - ) - await expect(next.start()).rejects.toThrow() - expect(next.cliOutput).toContain(` -./node_modules/get-intrinsic/index.js -${DYNAMIC_CODE_EVAL_ERROR}`) - }) - - it('does not detects dynamic code nested in @aws-sdk/client-s3 (legit Function.bind)', async () => { - await next.patchFile( - 'lib/utils.js', - ` - import { S3Client, AbortMultipartUploadCommand } from '@aws-sdk/client-s3' - new S3Client().send(new AbortMultipartUploadCommand({})) - ` - ) - // this previously threw from a module not found error - // although this is fixed now - await next.start() - - expect(next.cliOutput).not.toContain( - `./node_modules/@aws-sdk/smithy-client/dist-es/lazy-json.js` - ) - expect(next.cliOutput).not.toContain(DYNAMIC_CODE_EVAL_ERROR) - }) - - it('does not determine WebAssembly.instantiate with a module parameter as dynamic code execution (legit)', async () => { - await next.patchFile( - 'lib/utils.js', - ` - import wasm from './square.wasm?module' - const instance = WebAssembly.instantiate(wasm) - ` - ) - await next.start() - - expect(next.cliOutput).not.toContain(DYNAMIC_CODE_EVAL_ERROR) - }) - - // Actually this causes a dynamic code evaluation however, we can't determine the type of - // first parameter of WebAssembly.instanntiate statically. - it('does not determine WebAssembly.instantiate with a buffer parameter as dynamic code execution', async () => { - await next.patchFile( - 'lib/utils.js', - ` - const instance = WebAssembly.instantiate(new Uint8Array([0, 1, 2, 3])) - ` - ) - await next.start() - - expect(next.cliOutput).not.toContain(DYNAMIC_CODE_EVAL_ERROR) - }) - - it('detects use of WebAssembly.compile', async () => { - await next.patchFile( - 'lib/utils.js', - ` - const module = WebAssembly.compile(new Uint8Array([0, 1, 2, 3])) - ` - ) - - await expect(next.start()).rejects.toThrow() - expect(next.cliOutput).toContain(` -./lib/utils.js -${DYNAMIC_CODE_EVAL_ERROR}`) - }) -}) diff --git a/test/production/middleware-with-dynamic-code/square.wasm b/test/production/middleware-with-dynamic-code/square.wasm deleted file mode 100644 index f836887dda8004a4f54c3897c1d349b676293119..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63 zcmWN Date: Wed, 6 Jul 2022 10:52:13 +0200 Subject: [PATCH 2/7] chore: streamlines console format for edge-runtime errors in dev --- packages/next/server/dev/next-dev-server.ts | 9 +++------ .../edge-runtime-dynamic-code/test/index.test.js | 3 --- .../middleware-dev-errors/test/index.test.js | 12 ++++++------ 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts index ee626ccf678e..71651f25710f 100644 --- a/packages/next/server/dev/next-dev-server.ts +++ b/packages/next/server/dev/next-dev-server.ts @@ -843,12 +843,9 @@ export default class DevServer extends Server { `${file} (${lineNumber}:${column}) @ ${methodName}` ) if (src === 'edge-server') { - console[type === 'warning' ? 'warn' : 'error']( - `${(type === 'warning' ? chalk.yellow : chalk.red)( - err.name - )}: ${err.message}` - ) - } else if (type === 'warning') { + err = err.message + } + if (type === 'warning') { Log.warn(err) } else if (type) { Log.error(`${type}:`, err) diff --git a/test/integration/edge-runtime-dynamic-code/test/index.test.js b/test/integration/edge-runtime-dynamic-code/test/index.test.js index 54c4cbc0dc21..ae5b7f6d363e 100644 --- a/test/integration/edge-runtime-dynamic-code/test/index.test.js +++ b/test/integration/edge-runtime-dynamic-code/test/index.test.js @@ -101,7 +101,6 @@ describe.each([ expect(await extractValue(res)).toEqual(100) await waitFor(500) expect(output).toContain(EVAL_ERROR) - expect(output).toContain('DynamicCodeEvaluationWarning') // TODO check why that has a backslash on windows expect(output).toMatch(/lib[\\/]utils\.js/) expect(output).toContain('usingEval') @@ -126,7 +125,6 @@ describe.each([ expect(await extractValue(res)).toEqual(81) await waitFor(500) expect(output).toContain(WASM_COMPILE_ERROR) - expect(output).toContain('DynamicWasmCodeGenerationWarning') expect(output).toMatch(/lib[\\/]wasm\.js/) expect(output).toContain('usingWebAssemblyCompile') expect(stripAnsi(output)).toContain( @@ -142,7 +140,6 @@ describe.each([ expect(await extractValue(res)).toEqual(81) await waitFor(500) expect(output).toContain(WASM_INSTANTIATE_ERROR) - expect(output).toContain('DynamicWasmCodeGenerationWarning') expect(output).toMatch(/lib[\\/]wasm\.js/) expect(output).toContain('usingWebAssemblyInstantiateWithBuffer') expect(stripAnsi(output)).toContain( diff --git a/test/integration/middleware-dev-errors/test/index.test.js b/test/integration/middleware-dev-errors/test/index.test.js index 1045179f897f..1d8f32ee27b0 100644 --- a/test/integration/middleware-dev-errors/test/index.test.js +++ b/test/integration/middleware-dev-errors/test/index.test.js @@ -58,7 +58,7 @@ describe('Middleware development errors', () => { const output = stripAnsi(context.logs.output) expect(output).toMatch( new RegExp( - `error - \\(middleware\\)/middleware.js \\(\\d+:\\d+\\) @ Object.__WEBPACK_DEFAULT_EXPORT__ \\[as handler\\]\nError: boom`, + `error - \\(middleware\\)/middleware.js \\(\\d+:\\d+\\) @ Object.__WEBPACK_DEFAULT_EXPORT__ \\[as handler\\]\nerror - boom`, 'm' ) ) @@ -93,7 +93,7 @@ describe('Middleware development errors', () => { const output = stripAnsi(context.logs.output) expect(output).toMatch( new RegExp( - `error - \\(middleware\\)/middleware.js \\(\\d+:\\d+\\) @ throwError\nError: async boom!`, + `error - \\(middleware\\)/middleware.js \\(\\d+:\\d+\\) @ throwError\nerror - unhandledRejection: async boom!`, 'm' ) ) @@ -124,7 +124,7 @@ describe('Middleware development errors', () => { const output = stripAnsi(context.logs.output) expect(output).toMatch( new RegExp( - `error - \\(middleware\\)/middleware.js \\(\\d+:\\d+\\) @ eval\nReferenceError: test is not defined`, + `error - \\(middleware\\)/middleware.js \\(\\d+:\\d+\\) @ eval\nerror - test is not defined`, 'm' ) ) @@ -157,7 +157,7 @@ describe('Middleware development errors', () => { const output = stripAnsi(context.logs.output) expect(output).toMatch( new RegExp( - `error - \\(middleware\\)/middleware.js \\(\\d+:\\d+\\) @ \nError: booooom!`, + `error - \\(middleware\\)/middleware.js \\(\\d+:\\d+\\) @ \nerror - booooom!`, 'm' ) ) @@ -195,7 +195,7 @@ describe('Middleware development errors', () => { const output = stripAnsi(context.logs.output) expect(output).toMatch( new RegExp( - `error - \\(middleware\\)/middleware.js \\(\\d+:\\d+\\) @ eval\nError: you shall see me`, + `error - \\(middleware\\)/middleware.js \\(\\d+:\\d+\\) @ eval\nerror - unhandledRejection: you shall see me`, 'm' ) ) @@ -227,7 +227,7 @@ describe('Middleware development errors', () => { const output = stripAnsi(context.logs.output) expect(output).toMatch( new RegExp( - `error - \\(middleware\\)/lib/unhandled.js \\(\\d+:\\d+\\) @ Timeout.eval \\[as _onTimeout\\]\nError: This file asynchronously fails while loading`, + `error - \\(middleware\\)/lib/unhandled.js \\(\\d+:\\d+\\) @ Timeout.eval \\[as _onTimeout\\]\nerror - uncaughtException: This file asynchronously fails while loading`, 'm' ) ) From e0164c080fc7dec4e138b99747106a7b49b222c8 Mon Sep 17 00:00:00 2001 From: feugy Date: Wed, 6 Jul 2022 10:52:36 +0200 Subject: [PATCH 3/7] fix: can not open middleware file from react-dev-overlay --- packages/react-dev-overlay/src/middleware.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/react-dev-overlay/src/middleware.ts b/packages/react-dev-overlay/src/middleware.ts index 7f91741f2e54..32bbc67daf3f 100644 --- a/packages/react-dev-overlay/src/middleware.ts +++ b/packages/react-dev-overlay/src/middleware.ts @@ -344,7 +344,11 @@ function getOverlayMiddleware(options: OverlayMiddlewareOptions) { return res.end() } - const filePath = path.resolve(options.rootDirectory, frameFile) + // frame files may start with their webpack layer, like (middleware)/middleware.js + const filePath = path.resolve( + options.rootDirectory, + frameFile.replace(/^\([^)]+\)\//, '') + ) const fileExists = await fs.access(filePath, FS.F_OK).then( () => true, () => false From de9b5865a86380455f68319148ef513855c9e0d2 Mon Sep 17 00:00:00 2001 From: feugy Date: Thu, 7 Jul 2022 10:19:54 +0200 Subject: [PATCH 4/7] chore: fixes lint --- packages/next/server/dev/next-dev-server.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts index 71651f25710f..a289abd3ea12 100644 --- a/packages/next/server/dev/next-dev-server.ts +++ b/packages/next/server/dev/next-dev-server.ts @@ -13,7 +13,6 @@ import type { RoutingItem } from '../base-server' import crypto from 'crypto' import fs from 'fs' -import chalk from 'next/dist/compiled/chalk' import { Worker } from 'next/dist/compiled/jest-worker' import findUp from 'next/dist/compiled/find-up' import { join as pathJoin, relative, resolve as pathResolve, sep } from 'path' From 74cbd95c1dff6ad69ae84bed19970a39a19d9997 Mon Sep 17 00:00:00 2001 From: feugy Date: Thu, 7 Jul 2022 12:20:50 +0200 Subject: [PATCH 5/7] feat: does not throw when using process properties in Edge runtime --- packages/next/server/web/sandbox/context.ts | 15 ++++++++--- .../lib/utils.js | 6 +++++ .../middleware.js | 8 +++--- .../test/index.test.ts | 25 ++++++++++++++++++- 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/packages/next/server/web/sandbox/context.ts b/packages/next/server/web/sandbox/context.ts index 45c8df16c4cf..969135521116 100644 --- a/packages/next/server/web/sandbox/context.ts +++ b/packages/next/server/web/sandbox/context.ts @@ -294,7 +294,13 @@ function createProcessPolyfill(options: Pick) { if (key === 'env') continue Object.defineProperty(processPolyfill, key, { get() { - return overridenValue[key] ?? (() => emitError(`process.${key}`)) + if (overridenValue[key]) { + return overridenValue[key] + } + if (typeof (process as any)[key] === 'function') { + return () => throwUnsupportedAPIError(`process.${key}`) + } + return undefined }, set(value) { overridenValue[key] = value @@ -308,14 +314,15 @@ function createProcessPolyfill(options: Pick) { function addStub(context: Primitives, name: string) { Object.defineProperty(context, name, { get() { - emitError(name) - return undefined + return function () { + throwUnsupportedAPIError(name) + } }, enumerable: false, }) } -function emitError(name: string) { +function throwUnsupportedAPIError(name: string) { const error = new Error(`A Node.js API is used (${name}) which is not supported in the Edge Runtime. Learn more: https://nextjs.org/docs/api-reference/edge-runtime`) diff --git a/test/integration/edge-runtime-with-node.js-apis/lib/utils.js b/test/integration/edge-runtime-with-node.js-apis/lib/utils.js index 22dedbc0b3f6..9206e7a9086f 100644 --- a/test/integration/edge-runtime-with-node.js-apis/lib/utils.js +++ b/test/integration/edge-runtime-with-node.js-apis/lib/utils.js @@ -11,6 +11,8 @@ export function invokeNodeAPI(useCase) { console.log(process.cwd()) } else if (useCase === 'process.getuid') { console.log(process.getuid()) + } else if (useCase === 'process.cpuUsage') { + console.log(process.cpuUsage()) } else if (useCase === 'BroadcastChannel') { new BroadcastChannel() } else if (useCase === 'ByteLengthQueuingStrategy') { @@ -43,5 +45,9 @@ export function invokeNodeAPI(useCase) { new TransformStreamDefaultController() } else if (useCase === 'WritableStreamDefaultController') { new WritableStreamDefaultController() + } else if (useCase === 'process.version') { + console.log(process.version) + } else if (useCase === 'process.arch') { + console.log(process.arch) } } diff --git a/test/integration/edge-runtime-with-node.js-apis/middleware.js b/test/integration/edge-runtime-with-node.js-apis/middleware.js index 295f0e82cdef..01aa356a4723 100644 --- a/test/integration/edge-runtime-with-node.js-apis/middleware.js +++ b/test/integration/edge-runtime-with-node.js-apis/middleware.js @@ -1,7 +1,9 @@ import { NextResponse } from 'next/server' import { invokeNodeAPI } from './lib/utils' -export default function middleware({ nextUrl: { pathname } }) { - invokeNodeAPI(pathname.slice(1)) - return NextResponse.next() +export default function middleware({ nextUrl }) { + invokeNodeAPI(nextUrl.pathname.slice(1)) + return nextUrl.pathname.startsWith('/api') + ? NextResponse.next() + : NextResponse.rewrite(new URL('/', nextUrl)) } diff --git a/test/integration/edge-runtime-with-node.js-apis/test/index.test.ts b/test/integration/edge-runtime-with-node.js-apis/test/index.test.ts index 42507fbff9b3..090395b2afd4 100644 --- a/test/integration/edge-runtime-with-node.js-apis/test/index.test.ts +++ b/test/integration/edge-runtime-with-node.js-apis/test/index.test.ts @@ -19,8 +19,14 @@ const unsupportedFunctions = [ 'clearImmediate', // no need to test all of the process methods 'process.cwd', + 'process.cpuUsage', 'process.getuid', ] +const undefinedPropertoes = [ + // no need to test all of the process properties + 'process.arch', + 'process.version', +] const unsupportedClasses = [ 'BroadcastChannel', 'ByteLengthQueuingStrategy', @@ -77,6 +83,16 @@ describe.each([ afterAll(() => killApp(app)) + it.each(undefinedPropertoes.map((api) => ({ api })))( + 'does not throw on using $api', + async ({ api }) => { + const res = await fetchViaHTTP(appPort, computeRoute(api)) + expect(res.status).toBe(200) + await waitFor(500) + expect(output).not.toInclude(`A Node.js API is used (${api})`) + } + ) + it.each([ ...unsupportedFunctions.map((api) => ({ api, @@ -86,7 +102,7 @@ describe.each([ api, errorHighlight: `new ${api}(`, })), - ])(`shows error when using $api`, async ({ api, errorHighlight }) => { + ])(`throws error when using $api`, async ({ api, errorHighlight }) => { const res = await fetchViaHTTP(appPort, computeRoute(api)) expect(res.status).toBe(500) await waitFor(500) @@ -117,5 +133,12 @@ Learn more: https://nextjs.org/docs/api-reference/edge-runtime`) )(`warns for $api during build`, ({ api }) => { expect(buildResult.stderr).toContain(`A Node.js API is used (${api}`) }) + + it.each(undefinedPropertoes.map((api) => ({ api })))( + 'does not warn on using $api', + ({ api }) => { + expect(buildResult.stderr).toContain(`A Node.js API is used (${api}`) + } + ) }) }) From ef114de8e8a7c5f38f4c4b554a508699717ca10d Mon Sep 17 00:00:00 2001 From: feugy Date: Fri, 8 Jul 2022 10:28:21 +0200 Subject: [PATCH 6/7] chore: PR remarks --- test/integration/edge-runtime-dynamic-code/test/index.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/edge-runtime-dynamic-code/test/index.test.js b/test/integration/edge-runtime-dynamic-code/test/index.test.js index ae5b7f6d363e..f9b41e549b34 100644 --- a/test/integration/edge-runtime-dynamic-code/test/index.test.js +++ b/test/integration/edge-runtime-dynamic-code/test/index.test.js @@ -41,7 +41,7 @@ describe('Page using eval in dev mode', () => { beforeEach(() => (output = '')) afterAll(() => killApp(context.app)) - it('does issue Dynamic code evaluation warnings', async () => { + it('does issue dynamic code evaluation warnings', async () => { const html = await renderViaHTTP(context.appPort, '/') expect(html).toMatch(/>.*?100.*?and.*?100.*?<\//) await waitFor(500) From 1c05503c3abe3fe5e8e3c1faec98132f1cab9a94 Mon Sep 17 00:00:00 2001 From: feugy Date: Wed, 20 Jul 2022 18:12:48 +0200 Subject: [PATCH 7/7] chore: fix stack traces in dev after a recent change --- packages/next/server/dev/next-dev-server.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts index a289abd3ea12..5594c252cbd4 100644 --- a/packages/next/server/dev/next-dev-server.ts +++ b/packages/next/server/dev/next-dev-server.ts @@ -801,7 +801,9 @@ export default class DevServer extends Server { const frames = parseStack(err.stack!) const frame = frames.find( ({ file }) => - !file?.startsWith('eval') && !file?.includes('web/adapter') + !file?.startsWith('eval') && + !file?.includes('web/adapter') && + !file?.includes('sandbox/context') )! if (frame.lineNumber && frame?.file) {