From e4aae7d07ecafb5b8941be4763e0831b1f0a6626 Mon Sep 17 00:00:00 2001 From: nkzawa Date: Fri, 22 Apr 2022 21:06:20 +0700 Subject: [PATCH 01/13] remove use of process polyfill --- test/integration/middleware/core/test/index.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/middleware/core/test/index.test.js b/test/integration/middleware/core/test/index.test.js index 80e86207e633..79fc52512fb5 100644 --- a/test/integration/middleware/core/test/index.test.js +++ b/test/integration/middleware/core/test/index.test.js @@ -124,7 +124,7 @@ describe('Middleware base tests', () => { }) }) - it('should contains process polyfill', async () => { + it('should contains process.env without other properties', async () => { const res = await fetchViaHTTP(context.appPort, `/global`) const json = await res.json() expect(json).toEqual({ From 0999aa6caa71dfc3c0fef711e96fbd899a9551de Mon Sep 17 00:00:00 2001 From: nkzawa Date: Fri, 22 Apr 2022 21:55:47 +0700 Subject: [PATCH 02/13] fix a test --- test/integration/middleware/core/test/index.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/middleware/core/test/index.test.js b/test/integration/middleware/core/test/index.test.js index 79fc52512fb5..80e86207e633 100644 --- a/test/integration/middleware/core/test/index.test.js +++ b/test/integration/middleware/core/test/index.test.js @@ -124,7 +124,7 @@ describe('Middleware base tests', () => { }) }) - it('should contains process.env without other properties', async () => { + it('should contains process polyfill', async () => { const res = await fetchViaHTTP(context.appPort, `/global`) const json = await res.json() expect(json).toEqual({ From 6226162d5fc086fbdce717a9256c28bbd14be858 Mon Sep 17 00:00:00 2001 From: nkzawa Date: Fri, 22 Apr 2022 20:59:30 +0700 Subject: [PATCH 03/13] fix error message for using node buildin modules on edge functions --- packages/next/build/compiler.ts | 6 +- packages/next/build/index.ts | 45 ++++++-- packages/next/build/output/store.ts | 9 -- packages/next/build/utils.ts | 48 +++++++- .../parseNotFoundError.ts | 16 ++- .../error-overlay/format-webpack-messages.js | 6 +- .../with-builtin-module/test/index.test.js | 25 +++-- .../using-child-process-on-page/index.js | 6 + .../pages/using-not-exist/_middleware.js | 7 ++ .../without-builtin-module/test/index.test.js | 105 ++++++++++++++++++ .../test/index.test.js | 3 +- 11 files changed, 244 insertions(+), 32 deletions(-) create mode 100644 test/integration/middleware/without-builtin-module/pages/using-child-process-on-page/index.js create mode 100644 test/integration/middleware/without-builtin-module/pages/using-not-exist/_middleware.js create mode 100644 test/integration/middleware/without-builtin-module/test/index.test.js diff --git a/packages/next/build/compiler.ts b/packages/next/build/compiler.ts index 074211a97dbf..41c7202ac9bf 100644 --- a/packages/next/build/compiler.ts +++ b/packages/next/build/compiler.ts @@ -5,6 +5,7 @@ import { Span } from '../trace' export type CompilerResult = { errors: webpack5.StatsError[] warnings: webpack5.StatsError[] + stats: webpack5.Stats | undefined } function generateStats( @@ -54,6 +55,7 @@ export function runCompiler( return resolve({ errors: [{ message: reason, details: (err as any).details }], warnings: [], + stats, }) } return reject(err) @@ -61,7 +63,9 @@ export function runCompiler( const result = webpackCloseSpan .traceChild('webpack-generate-error-stats') - .traceFn(() => generateStats({ errors: [], warnings: [] }, stats)) + .traceFn(() => + generateStats({ errors: [], warnings: [], stats }, stats) + ) return resolve(result) }) }) diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index af721400e21f..0a3fc5ae13e1 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -78,7 +78,7 @@ import { eventPackageUsedInGetServerSideProps, } from '../telemetry/events' import { Telemetry } from '../telemetry/storage' -import { CompilerResult, runCompiler } from './compiler' +import { runCompiler } from './compiler' import { createEntrypoints, createPagesMapping, @@ -96,11 +96,13 @@ import { PageInfo, printCustomRoutes, printTreeView, + getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage, getUnresolvedModuleFromError, copyTracedFiles, isReservedPage, isCustomErrorPage, isFlightPage, + isEdgeRuntimeCompiled, } from './utils' import getBaseWebpackConfig from './webpack-config' import { PagesManifest } from './webpack/plugins/pages-manifest-plugin' @@ -135,6 +137,16 @@ export type PrerenderManifest = { preview: __ApiPreviewProps } +type CompilerResult = { + errors: webpack.StatsError[] + warnings: webpack.StatsError[] + stats: [ + webpack.Stats | undefined, + webpack.Stats | undefined, + webpack.Stats | undefined + ] +} + export default async function build( dir: string, conf = null, @@ -164,7 +176,6 @@ export default async function build( // We enable concurrent features (Fizz-related rendering architecture) when // using React 18 or experimental. const hasReactRoot = !!process.env.__NEXT_REACT_ROOT - const hasConcurrentFeatures = hasReactRoot const hasServerComponents = hasReactRoot && !!config.experimental.serverComponents @@ -615,7 +626,11 @@ export default async function build( ignore: [] as string[], })) - let result: CompilerResult = { warnings: [], errors: [] } + let result: CompilerResult = { + warnings: [], + errors: [], + stats: [undefined, undefined, undefined], + } let webpackBuildStart let telemetryPlugin await (async () => { @@ -680,6 +695,7 @@ export default async function build( result = { warnings: [...clientResult.warnings], errors: [...clientResult.errors], + stats: [clientResult.stats, undefined, undefined], } } else { const serverResult = await runCompiler(configs[1], { @@ -700,6 +716,11 @@ export default async function build( ...serverResult.errors, ...(edgeServerResult?.errors || []), ], + stats: [ + clientResult.stats, + serverResult.stats, + edgeServerResult?.stats, + ], } } }) @@ -741,14 +762,18 @@ export default async function build( console.error(error) console.error() - // When using the web runtime, common Node.js native APIs are not available. - const moduleName = getUnresolvedModuleFromError(error) - if (hasConcurrentFeatures && moduleName) { - const err = new Error( - `Native Node.js APIs are not supported in the Edge Runtime. Found \`${moduleName}\` imported.\n\n` + const edgeRuntimeErrors = result.stats[2]?.compilation.errors ?? [] + + for (const err of edgeRuntimeErrors) { + // When using the web runtime, common Node.js native APIs are not available. + const moduleName = getUnresolvedModuleFromError(err.message) + if (!moduleName) continue + + const e = new Error( + getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage(moduleName) ) as NextError - err.code = 'EDGE_RUNTIME_UNSUPPORTED_API' - throw err + e.code = 'EDGE_RUNTIME_UNSUPPORTED_API' + throw e } if ( diff --git a/packages/next/build/output/store.ts b/packages/next/build/output/store.ts index 4c0af9268030..d2520f71f015 100644 --- a/packages/next/build/output/store.ts +++ b/packages/next/build/output/store.ts @@ -1,7 +1,6 @@ import createStore from 'next/dist/compiled/unistore' import stripAnsi from 'next/dist/compiled/strip-ansi' import { flushAllTraces } from '../../trace' -import { getUnresolvedModuleFromError } from '../utils' import * as Log from './log' @@ -90,14 +89,6 @@ store.subscribe((state) => { } } - const moduleName = getUnresolvedModuleFromError(cleanError) - if (state.hasEdgeServer && moduleName) { - console.error( - `Native Node.js APIs are not supported in the Edge Runtime. Found \`${moduleName}\` imported.\n` - ) - return - } - // Ensure traces are flushed after each compile in development mode flushAllTraces() return diff --git a/packages/next/build/utils.ts b/packages/next/build/utils.ts index 96deac92b3fb..87ef794d9f29 100644 --- a/packages/next/build/utils.ts +++ b/packages/next/build/utils.ts @@ -1,4 +1,5 @@ import type { NextConfigComplete, PageRuntime } from '../server/config-shared' +import type { webpack5 } from 'next/dist/compiled/webpack/webpack' import '../server/node-polyfill-fetch' import chalk from 'next/dist/compiled/chalk' @@ -20,6 +21,10 @@ import { SERVER_PROPS_SSG_CONFLICT, MIDDLEWARE_ROUTE, } from '../lib/constants' +import { + MIDDLEWARE_RUNTIME_WEBPACK, + MIDDLEWARE_SSR_RUNTIME_WEBPACK, +} from '../shared/lib/constants' import prettyBytes from '../lib/pretty-bytes' import { getRouteMatcher, getRouteRegex } from '../shared/lib/router/utils' import { isDynamicRoute } from '../shared/lib/router/utils/is-dynamic' @@ -52,6 +57,11 @@ const fsStatGzip = (file: string) => { return (fileGzipStats[file] = getGzipSize.file(file)) } +const WEBPACK_EDGE_RUNTIMES = new Set([ + MIDDLEWARE_RUNTIME_WEBPACK, + MIDDLEWARE_SSR_RUNTIME_WEBPACK, +]) + const fileSize = async (file: string) => (await fs.stat(file)).size const fileStats: { [k: string]: Promise | undefined } = {} @@ -1123,7 +1133,7 @@ export function getUnresolvedModuleFromError( error: string ): string | undefined { const moduleErrorRegex = new RegExp( - `Module not found: Can't resolve '(\\w+)'` + `Module not found: Error: Can't resolve '(\\w+)'` ) const [, moduleName] = error.match(moduleErrorRegex) || [] return builtinModules.find((item: string) => item === moduleName) @@ -1266,3 +1276,39 @@ export function isReservedPage(page: string) { export function isCustomErrorPage(page: string) { return page === '/404' || page === '/500' } + +// FIX ME: it does not work for non-middleware edge functions +// since chunks don't contain runtime specified somehow +export function isEdgeRuntimeCompiled( + compilation: webpack5.Compilation, + module: any +) { + let isEdgeRuntime = false + + for (const chunk of compilation.chunkGraph.getModuleChunksIterable(module)) { + let runtimes: string[] + if (typeof chunk.runtime === 'string') { + runtimes = [chunk.runtime] + } else if (chunk.runtime) { + runtimes = [...chunk.runtime] + } else { + runtimes = [] + } + + if (runtimes.some((r) => WEBPACK_EDGE_RUNTIMES.has(r))) { + isEdgeRuntime = true + break + } + } + + return isEdgeRuntime +} + +export function getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage( + name: string +) { + return ( + `You're using a Node.js module (${name}) which is not supported in the Edge Runtime that Middleware uses.\n` + + 'Learn more: https://nextjs.org/docs/api-reference/edge-runtime' + ) +} diff --git a/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts b/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts index 5536ab4dca82..fb2c2389fbeb 100644 --- a/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts +++ b/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts @@ -2,6 +2,11 @@ import Chalk from 'next/dist/compiled/chalk' import { SimpleWebpackError } from './simpleWebpackError' import { createOriginalStackFrame } from 'next/dist/compiled/@next/react-dev-overlay/middleware' import type { webpack5 } from 'next/dist/compiled/webpack/webpack' +import { + getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage, + getUnresolvedModuleFromError, + isEdgeRuntimeCompiled, +} from '../../../utils' const chalk = new Chalk.constructor({ enabled: true }) @@ -98,7 +103,7 @@ export async function getNotFoundError( const frame = result.originalCodeFrame ?? '' - const message = + let message = chalk.red.bold('Module not found') + `: ${errorMessage}` + '\n' + @@ -107,6 +112,15 @@ export async function getNotFoundError( importTrace() + '\nhttps://nextjs.org/docs/messages/module-not-found' + if (isEdgeRuntimeCompiled(compilation, input.module)) { + const moduleName = getUnresolvedModuleFromError(input.message) + if (moduleName) { + message += + '\n\n' + + getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage(moduleName) + } + } + return new SimpleWebpackError( `${chalk.cyan(fileName)}:${chalk.yellow( result.originalStackFrame.lineNumber?.toString() ?? '' 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 f073ca91da3d..54214c9e9cdb 100644 --- a/packages/next/client/dev/error-overlay/format-webpack-messages.js +++ b/packages/next/client/dev/error-overlay/format-webpack-messages.js @@ -163,7 +163,11 @@ function formatWebpackMessages(json, verbose) { const formattedWarnings = json.warnings.map(function (message) { return formatMessage(message, verbose) }) - const result = { errors: formattedErrors, warnings: formattedWarnings } + const result = { + ...json, + errors: formattedErrors, + warnings: formattedWarnings, + } if (!verbose && result.errors.some(isLikelyASyntaxError)) { // If there are any syntax errors, show just them. result.errors = result.errors.filter(isLikelyASyntaxError) diff --git a/test/integration/middleware/with-builtin-module/test/index.test.js b/test/integration/middleware/with-builtin-module/test/index.test.js index a2705422da2a..9ee83df15293 100644 --- a/test/integration/middleware/with-builtin-module/test/index.test.js +++ b/test/integration/middleware/with-builtin-module/test/index.test.js @@ -1,6 +1,7 @@ /* eslint-env jest */ import stripAnsi from 'next/dist/compiled/strip-ansi' +import { getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage } from 'next/dist/build/utils' import { join } from 'path' import { fetchViaHTTP, @@ -22,14 +23,16 @@ describe('Middleware importing Node.js built-in module', () => { return `Module not found: Can't resolve '${name}'` } - function getBuiltinApisNotSupported(name) { - return `Native Node.js APIs are not supported in the Edge Runtime. Found \`${name}\` imported.\n` + function escapeLF(s) { + return s.replaceAll('\n', '\\n') } describe('dev mode', () => { let output = '' - beforeAll(async () => { + // restart the app for every test since the latest error is not shown sometimes + beforeEach(async () => { + output = '' context.appPort = await findPort() context.app = await launchApp(context.appDir, context.appPort, { env: { __NEXT_TEST_WITH_DEVTOOL: 1 }, @@ -42,25 +45,31 @@ describe('Middleware importing Node.js built-in module', () => { }) }) - beforeEach(() => (output = '')) - afterAll(() => killApp(context.app)) + afterEach(() => killApp(context.app)) it('shows error when importing path module', async () => { const res = await fetchViaHTTP(context.appPort, '/using-path') + const text = await res.text() await waitFor(500) + const msg = getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage('path') expect(res.status).toBe(500) expect(output).toContain(getModuleNotFound('path')) - expect(output).toContain(getBuiltinApisNotSupported('path')) + expect(output).toContain(msg) + expect(text).toContain(escapeLF(msg)) expect(stripAnsi(output)).toContain("import { basename } from 'path'") expect(output).not.toContain(WEBPACK_BREAKING_CHANGE) }) it('shows error when importing child_process module', async () => { const res = await fetchViaHTTP(context.appPort, '/using-child-process') + const text = await res.text() await waitFor(500) + const msg = + getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage('child_process') expect(res.status).toBe(500) expect(output).toContain(getModuleNotFound('child_process')) - expect(output).toContain(getBuiltinApisNotSupported('child_process')) + expect(output).toContain(msg) + expect(text).toContain(escapeLF(msg)) expect(stripAnsi(output)).toContain( "import { spawn } from 'child_process'" ) @@ -82,7 +91,7 @@ describe('Middleware importing Node.js built-in module', () => { expect(buildResult.stderr).toContain(getModuleNotFound('child_process')) expect(buildResult.stderr).toContain(getModuleNotFound('path')) expect(buildResult.stderr).toContain( - getBuiltinApisNotSupported('child_process') + getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage('child_process') ) expect(buildResult.stderr).not.toContain(WEBPACK_BREAKING_CHANGE) }) diff --git a/test/integration/middleware/without-builtin-module/pages/using-child-process-on-page/index.js b/test/integration/middleware/without-builtin-module/pages/using-child-process-on-page/index.js new file mode 100644 index 000000000000..6cc59d6d2c7e --- /dev/null +++ b/test/integration/middleware/without-builtin-module/pages/using-child-process-on-page/index.js @@ -0,0 +1,6 @@ +import { spawn } from 'child_process' + +export default function Page() { + console.log(spawn('ls', ['-lh', '/usr'])) + return
ok
+} diff --git a/test/integration/middleware/without-builtin-module/pages/using-not-exist/_middleware.js b/test/integration/middleware/without-builtin-module/pages/using-not-exist/_middleware.js new file mode 100644 index 000000000000..c9383b5d8440 --- /dev/null +++ b/test/integration/middleware/without-builtin-module/pages/using-not-exist/_middleware.js @@ -0,0 +1,7 @@ +import { NextResponse } from 'next/server' +import NotExist from 'not-exist' + +export async function middleware(request) { + console.log(new NotExist()) + return NextResponse.next() +} diff --git a/test/integration/middleware/without-builtin-module/test/index.test.js b/test/integration/middleware/without-builtin-module/test/index.test.js new file mode 100644 index 000000000000..1a7c2f8a8f13 --- /dev/null +++ b/test/integration/middleware/without-builtin-module/test/index.test.js @@ -0,0 +1,105 @@ +/* eslint-env jest */ + +import stripAnsi from 'next/dist/compiled/strip-ansi' +import { getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage } from 'next/dist/build/utils' +import { join } from 'path' +import { + fetchViaHTTP, + findPort, + killApp, + launchApp, + nextBuild, + waitFor, +} from 'next-test-utils' + +const context = {} + +jest.setTimeout(1000 * 60 * 2) +context.appDir = join(__dirname, '../') + +describe('Middleware importing Node.js built-in module', () => { + function getModuleNotFound(name) { + return `Module not found: Can't resolve '${name}'` + } + + function escapeLF(s) { + return s.replaceAll('\n', '\\n') + } + + describe('dev mode', () => { + let output = '' + + // restart the app for every test since the latest error is not shown sometimes + beforeEach(async () => { + output = '' + 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 + }, + }) + }) + + afterEach(() => killApp(context.app)) + + it('does not show the not-supported error when importing non-node-builtin module', async () => { + const res = await fetchViaHTTP(context.appPort, '/using-not-exist') + expect(res.status).toBe(500) + + const text = await res.text() + await waitFor(500) + const msg = + getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage('not-exist') + expect(output).toContain(getModuleNotFound('not-exist')) + expect(output).not.toContain(msg) + expect(text).not.toContain(escapeLF(msg)) + }) + + it('does not show the not-supported error when importing child_process module on a page', async () => { + await fetchViaHTTP(context.appPort, '/using-child-process-on-page') + + // Need to request twice + // See: https://github.com/vercel/next.js/issues/36387 + const res = await fetchViaHTTP( + context.appPort, + '/using-child-process-on-page' + ) + + expect(res.status).toBe(500) + + const text = await res.text() + await waitFor(500) + const msg = + getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage('child_process') + expect(output).toContain(getModuleNotFound('child_process')) + expect(output).not.toContain(msg) + expect(text).not.toContain(escapeLF(msg)) + }) + }) + + describe('production mode', () => { + let buildResult + + beforeAll(async () => { + buildResult = await nextBuild(context.appDir, undefined, { + stderr: true, + stdout: true, + }) + }) + + it('should not have middleware error during build', () => { + expect(buildResult.stderr).toContain(getModuleNotFound('not-exist')) + expect(buildResult.stderr).toContain(getModuleNotFound('child_process')) + expect(buildResult.stderr).not.toContain( + getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage('not-exist') + ) + expect(buildResult.stderr).not.toContain( + getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage('child_process') + ) + }) + }) +}) diff --git a/test/integration/react-streaming-and-server-components/test/index.test.js b/test/integration/react-streaming-and-server-components/test/index.test.js index 0d361be78ee2..2e4e62b8acd6 100644 --- a/test/integration/react-streaming-and-server-components/test/index.test.js +++ b/test/integration/react-streaming-and-server-components/test/index.test.js @@ -25,6 +25,7 @@ import rsc from './rsc' import streaming from './streaming' import basic from './basic' import runtime from './runtime' +import { getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage } from 'next/dist/build/utils' const rscAppPage = ` import Container from '../components/container.server' @@ -52,7 +53,7 @@ export default function Page500() { describe('Edge runtime - errors', () => { it('should warn user that native node APIs are not supported', async () => { const fsImportedErrorMessage = - 'Native Node.js APIs are not supported in the Edge Runtime. Found `dns` imported.' + getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage('dns') const { stderr } = await nextBuild(nativeModuleTestAppDir, [], { stderr: true, }) From 41fcdf5d5c14b25a9ab02c9958a8a3862f769a33 Mon Sep 17 00:00:00 2001 From: nkzawa Date: Mon, 25 Apr 2022 16:59:46 +0700 Subject: [PATCH 04/13] fix for lint --- .../middleware/without-builtin-module/test/index.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/integration/middleware/without-builtin-module/test/index.test.js b/test/integration/middleware/without-builtin-module/test/index.test.js index 1a7c2f8a8f13..b0671ae7b4c9 100644 --- a/test/integration/middleware/without-builtin-module/test/index.test.js +++ b/test/integration/middleware/without-builtin-module/test/index.test.js @@ -1,6 +1,5 @@ /* eslint-env jest */ -import stripAnsi from 'next/dist/compiled/strip-ansi' import { getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage } from 'next/dist/build/utils' import { join } from 'path' import { From f89b2a5823418d4fc6856376adb14b057ae2f36d Mon Sep 17 00:00:00 2001 From: nkzawa Date: Mon, 25 Apr 2022 17:04:53 +0700 Subject: [PATCH 05/13] do not use replaceAll --- .../middleware/with-builtin-module/test/index.test.js | 2 +- .../middleware/without-builtin-module/test/index.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/middleware/with-builtin-module/test/index.test.js b/test/integration/middleware/with-builtin-module/test/index.test.js index 9ee83df15293..959ec61ba9c2 100644 --- a/test/integration/middleware/with-builtin-module/test/index.test.js +++ b/test/integration/middleware/with-builtin-module/test/index.test.js @@ -24,7 +24,7 @@ describe('Middleware importing Node.js built-in module', () => { } function escapeLF(s) { - return s.replaceAll('\n', '\\n') + return s.replace(/\n/g, '\\n') } describe('dev mode', () => { diff --git a/test/integration/middleware/without-builtin-module/test/index.test.js b/test/integration/middleware/without-builtin-module/test/index.test.js index b0671ae7b4c9..818838cd7606 100644 --- a/test/integration/middleware/without-builtin-module/test/index.test.js +++ b/test/integration/middleware/without-builtin-module/test/index.test.js @@ -22,7 +22,7 @@ describe('Middleware importing Node.js built-in module', () => { } function escapeLF(s) { - return s.replaceAll('\n', '\\n') + return s.replace(/\n/g, '\\n') } describe('dev mode', () => { From 7cd047b1ad3a1ee5acca47dd9ff7a045c9843595 Mon Sep 17 00:00:00 2001 From: nkzawa Date: Tue, 26 Apr 2022 19:25:34 +0700 Subject: [PATCH 06/13] add null check --- packages/next/build/utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/next/build/utils.ts b/packages/next/build/utils.ts index 87ef794d9f29..0bece09eb70d 100644 --- a/packages/next/build/utils.ts +++ b/packages/next/build/utils.ts @@ -1284,6 +1284,7 @@ export function isEdgeRuntimeCompiled( module: any ) { let isEdgeRuntime = false + if (!module) return isEdgeRuntimeCompiled for (const chunk of compilation.chunkGraph.getModuleChunksIterable(module)) { let runtimes: string[] From 1fcdf63b49b8fb7d6af769712c2ac7625e541ce3 Mon Sep 17 00:00:00 2001 From: nkzawa Date: Wed, 27 Apr 2022 15:54:27 +0700 Subject: [PATCH 07/13] fix error message --- packages/next/build/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/build/utils.ts b/packages/next/build/utils.ts index 0bece09eb70d..01501a3d232d 100644 --- a/packages/next/build/utils.ts +++ b/packages/next/build/utils.ts @@ -1309,7 +1309,7 @@ export function getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage( name: string ) { return ( - `You're using a Node.js module (${name}) which is not supported in the Edge Runtime that Middleware uses.\n` + + `You're using a Node.js module (${name}) which is not supported in the Edge Runtime.\n` + 'Learn more: https://nextjs.org/docs/api-reference/edge-runtime' ) } From 539bb2eba50b6a12cf914945eb6df3f5faadae8f Mon Sep 17 00:00:00 2001 From: nkzawa Date: Wed, 27 Apr 2022 17:52:03 +0700 Subject: [PATCH 08/13] fix runtime name to check --- packages/next/build/utils.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/next/build/utils.ts b/packages/next/build/utils.ts index 01501a3d232d..361ead7cf637 100644 --- a/packages/next/build/utils.ts +++ b/packages/next/build/utils.ts @@ -21,10 +21,7 @@ import { SERVER_PROPS_SSG_CONFLICT, MIDDLEWARE_ROUTE, } from '../lib/constants' -import { - MIDDLEWARE_RUNTIME_WEBPACK, - MIDDLEWARE_SSR_RUNTIME_WEBPACK, -} from '../shared/lib/constants' +import { EDGE_RUNTIME_WEBPACK } from '../shared/lib/constants' import prettyBytes from '../lib/pretty-bytes' import { getRouteMatcher, getRouteRegex } from '../shared/lib/router/utils' import { isDynamicRoute } from '../shared/lib/router/utils/is-dynamic' @@ -57,11 +54,6 @@ const fsStatGzip = (file: string) => { return (fileGzipStats[file] = getGzipSize.file(file)) } -const WEBPACK_EDGE_RUNTIMES = new Set([ - MIDDLEWARE_RUNTIME_WEBPACK, - MIDDLEWARE_SSR_RUNTIME_WEBPACK, -]) - const fileSize = async (file: string) => (await fs.stat(file)).size const fileStats: { [k: string]: Promise | undefined } = {} @@ -1296,7 +1288,7 @@ export function isEdgeRuntimeCompiled( runtimes = [] } - if (runtimes.some((r) => WEBPACK_EDGE_RUNTIMES.has(r))) { + if (runtimes.some((r) => r === EDGE_RUNTIME_WEBPACK)) { isEdgeRuntime = true break } From e8d9faa06333a422386c39da77b7f18356280ed3 Mon Sep 17 00:00:00 2001 From: nkzawa Date: Wed, 27 Apr 2022 19:30:34 +0700 Subject: [PATCH 09/13] fix test, remove irrelevant code --- packages/next/build/index.ts | 1 - packages/next/build/utils.ts | 3 ++- .../pages/using-child-process-on-page/index.js | 2 +- .../pages/using-not-exist/_middleware.js | 2 +- .../middleware/without-builtin-module/test/index.test.js | 4 ---- 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index 0a3fc5ae13e1..7b7fd9775572 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -102,7 +102,6 @@ import { isReservedPage, isCustomErrorPage, isFlightPage, - isEdgeRuntimeCompiled, } from './utils' import getBaseWebpackConfig from './webpack-config' import { PagesManifest } from './webpack/plugins/pages-manifest-plugin' diff --git a/packages/next/build/utils.ts b/packages/next/build/utils.ts index 361ead7cf637..3f8a4088e575 100644 --- a/packages/next/build/utils.ts +++ b/packages/next/build/utils.ts @@ -1275,8 +1275,9 @@ export function isEdgeRuntimeCompiled( compilation: webpack5.Compilation, module: any ) { + if (!module) return false + let isEdgeRuntime = false - if (!module) return isEdgeRuntimeCompiled for (const chunk of compilation.chunkGraph.getModuleChunksIterable(module)) { let runtimes: string[] diff --git a/test/integration/middleware/without-builtin-module/pages/using-child-process-on-page/index.js b/test/integration/middleware/without-builtin-module/pages/using-child-process-on-page/index.js index 6cc59d6d2c7e..7c9f7f8b91bf 100644 --- a/test/integration/middleware/without-builtin-module/pages/using-child-process-on-page/index.js +++ b/test/integration/middleware/without-builtin-module/pages/using-child-process-on-page/index.js @@ -1,6 +1,6 @@ import { spawn } from 'child_process' export default function Page() { - console.log(spawn('ls', ['-lh', '/usr'])) + spawn('ls', ['-lh', '/usr']) return
ok
} diff --git a/test/integration/middleware/without-builtin-module/pages/using-not-exist/_middleware.js b/test/integration/middleware/without-builtin-module/pages/using-not-exist/_middleware.js index c9383b5d8440..b1699a1acb2b 100644 --- a/test/integration/middleware/without-builtin-module/pages/using-not-exist/_middleware.js +++ b/test/integration/middleware/without-builtin-module/pages/using-not-exist/_middleware.js @@ -2,6 +2,6 @@ import { NextResponse } from 'next/server' import NotExist from 'not-exist' export async function middleware(request) { - console.log(new NotExist()) + new NotExist() return NextResponse.next() } diff --git a/test/integration/middleware/without-builtin-module/test/index.test.js b/test/integration/middleware/without-builtin-module/test/index.test.js index 818838cd7606..b31c60fc5671 100644 --- a/test/integration/middleware/without-builtin-module/test/index.test.js +++ b/test/integration/middleware/without-builtin-module/test/index.test.js @@ -91,11 +91,7 @@ describe('Middleware importing Node.js built-in module', () => { }) it('should not have middleware error during build', () => { - expect(buildResult.stderr).toContain(getModuleNotFound('not-exist')) expect(buildResult.stderr).toContain(getModuleNotFound('child_process')) - expect(buildResult.stderr).not.toContain( - getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage('not-exist') - ) expect(buildResult.stderr).not.toContain( getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage('child_process') ) From d6c74cce8f7e5838c075a1287b4d3c25cc35e8be Mon Sep 17 00:00:00 2001 From: nkzawa Date: Wed, 27 Apr 2022 22:52:22 +0700 Subject: [PATCH 10/13] detect runtime using getPageRuntime --- packages/next/build/utils.ts | 16 ++++++++++++---- packages/next/build/webpack-config.ts | 2 +- .../plugins/wellknown-errors-plugin/index.ts | 10 +++++++++- .../parseNotFoundError.ts | 6 ++++-- .../webpackModuleError.ts | 7 +++++-- 5 files changed, 31 insertions(+), 10 deletions(-) diff --git a/packages/next/build/utils.ts b/packages/next/build/utils.ts index 3f8a4088e575..703bb5145efe 100644 --- a/packages/next/build/utils.ts +++ b/packages/next/build/utils.ts @@ -1,4 +1,8 @@ -import type { NextConfigComplete, PageRuntime } from '../server/config-shared' +import type { + NextConfig, + NextConfigComplete, + PageRuntime, +} from '../server/config-shared' import type { webpack5 } from 'next/dist/compiled/webpack/webpack' import '../server/node-polyfill-fetch' @@ -44,6 +48,7 @@ import isError from '../lib/is-error' import { recursiveDelete } from '../lib/recursive-delete' import { Sema } from 'next/dist/compiled/async-sema' import { MiddlewareManifest } from './webpack/plugins/middleware-plugin' +import { getPageRuntime } from './entries' const { builtinModules } = require('module') const RESERVED_PAGE = /^\/(_app|_error|_document|api(\/|$))/ @@ -1271,9 +1276,10 @@ export function isCustomErrorPage(page: string) { // FIX ME: it does not work for non-middleware edge functions // since chunks don't contain runtime specified somehow -export function isEdgeRuntimeCompiled( +export async function isEdgeRuntimeCompiled( compilation: webpack5.Compilation, - module: any + module: any, + config: NextConfig ) { if (!module) return false @@ -1295,7 +1301,9 @@ export function isEdgeRuntimeCompiled( } } - return isEdgeRuntime + return ( + isEdgeRuntime || (await getPageRuntime(module.resource, config)) === 'edge' + ) } export function getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage( diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 99fce88e38b8..fab5cbf8f8a9 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -1537,7 +1537,7 @@ export default async function getBaseWebpackConfig( isLikeServerless, }) })(), - new WellKnownErrorsPlugin(), + new WellKnownErrorsPlugin({ config }), isClient && new CopyFilePlugin({ filePath: require.resolve('./polyfills/polyfill-nomodule'), diff --git a/packages/next/build/webpack/plugins/wellknown-errors-plugin/index.ts b/packages/next/build/webpack/plugins/wellknown-errors-plugin/index.ts index c7de52cc80ac..14c1e4c774a1 100644 --- a/packages/next/build/webpack/plugins/wellknown-errors-plugin/index.ts +++ b/packages/next/build/webpack/plugins/wellknown-errors-plugin/index.ts @@ -1,7 +1,14 @@ import type { webpack5 as webpack } from 'next/dist/compiled/webpack/webpack' import { getModuleBuildError } from './webpackModuleError' +import { NextConfig } from '../../../../server/config-shared' export class WellKnownErrorsPlugin { + config: NextConfig + + constructor({ config }: { config: NextConfig }) { + this.config = config + } + apply(compiler: webpack.Compiler) { compiler.hooks.compilation.tap('WellKnownErrorsPlugin', (compilation) => { compilation.hooks.afterSeal.tapPromise( @@ -13,7 +20,8 @@ export class WellKnownErrorsPlugin { try { const moduleError = await getModuleBuildError( compilation, - err + err, + this.config ) if (moduleError !== false) { compilation.errors[i] = moduleError diff --git a/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts b/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts index fb2c2389fbeb..53fd151649f8 100644 --- a/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts +++ b/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts @@ -7,6 +7,7 @@ import { getUnresolvedModuleFromError, isEdgeRuntimeCompiled, } from '../../../utils' +import { NextConfig } from '../../../../server/config-shared' const chalk = new Chalk.constructor({ enabled: true }) @@ -53,7 +54,8 @@ function getModuleTrace(input: any, compilation: any) { export async function getNotFoundError( compilation: webpack5.Compilation, input: any, - fileName: string + fileName: string, + config: NextConfig ) { if (input.name !== 'ModuleNotFoundError') { return false @@ -112,7 +114,7 @@ export async function getNotFoundError( importTrace() + '\nhttps://nextjs.org/docs/messages/module-not-found' - if (isEdgeRuntimeCompiled(compilation, input.module)) { + if (await isEdgeRuntimeCompiled(compilation, input.module, config)) { const moduleName = getUnresolvedModuleFromError(input.message) if (moduleName) { message += diff --git a/packages/next/build/webpack/plugins/wellknown-errors-plugin/webpackModuleError.ts b/packages/next/build/webpack/plugins/wellknown-errors-plugin/webpackModuleError.ts index dc7e0e3cf36f..db45436f23a8 100644 --- a/packages/next/build/webpack/plugins/wellknown-errors-plugin/webpackModuleError.ts +++ b/packages/next/build/webpack/plugins/wellknown-errors-plugin/webpackModuleError.ts @@ -7,6 +7,7 @@ import { getScssError } from './parseScss' import { getNotFoundError } from './parseNotFoundError' import { SimpleWebpackError } from './simpleWebpackError' import isError from '../../../../lib/is-error' +import { NextConfig } from '../../../../server/config-shared' function getFileData( compilation: webpack.Compilation, @@ -42,7 +43,8 @@ function getFileData( export async function getModuleBuildError( compilation: webpack.Compilation, - input: any + input: any, + config: NextConfig ): Promise { if ( !( @@ -62,7 +64,8 @@ export async function getModuleBuildError( const notFoundError = await getNotFoundError( compilation, input, - sourceFilename + sourceFilename, + config ) if (notFoundError !== false) { return notFoundError From bf6bcd29f23310d77bcb1bf2f29370fdb1f65f38 Mon Sep 17 00:00:00 2001 From: nkzawa Date: Wed, 27 Apr 2022 22:59:06 +0700 Subject: [PATCH 11/13] add a comment --- packages/next/build/utils.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/next/build/utils.ts b/packages/next/build/utils.ts index 703bb5145efe..2bffc05d634e 100644 --- a/packages/next/build/utils.ts +++ b/packages/next/build/utils.ts @@ -1283,8 +1283,6 @@ export async function isEdgeRuntimeCompiled( ) { if (!module) return false - let isEdgeRuntime = false - for (const chunk of compilation.chunkGraph.getModuleChunksIterable(module)) { let runtimes: string[] if (typeof chunk.runtime === 'string') { @@ -1296,14 +1294,13 @@ export async function isEdgeRuntimeCompiled( } if (runtimes.some((r) => r === EDGE_RUNTIME_WEBPACK)) { - isEdgeRuntime = true - break + return true } } - return ( - isEdgeRuntime || (await getPageRuntime(module.resource, config)) === 'edge' - ) + // Check the page runtime as well since we cannot detect the runtime from + // compilation when it's for the client part of edge function + return (await getPageRuntime(module.resource, config)) === 'edge' } export function getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage( From 55eb8193739601dc3c2535d84e2765b30b1b6334 Mon Sep 17 00:00:00 2001 From: Naoyuki Kanezawa Date: Thu, 28 Apr 2022 20:40:40 +0700 Subject: [PATCH 12/13] Update packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts Co-authored-by: Tobias Koppers --- .../plugins/wellknown-errors-plugin/parseNotFoundError.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts b/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts index 53fd151649f8..c95bc508e748 100644 --- a/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts +++ b/packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts @@ -114,9 +114,9 @@ export async function getNotFoundError( importTrace() + '\nhttps://nextjs.org/docs/messages/module-not-found' - if (await isEdgeRuntimeCompiled(compilation, input.module, config)) { - const moduleName = getUnresolvedModuleFromError(input.message) - if (moduleName) { + const moduleName = getUnresolvedModuleFromError(input.message) + if (moduleName) { + if (await isEdgeRuntimeCompiled(compilation, input.module, config)) { message += '\n\n' + getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage(moduleName) From 4146511ccb00ee7f72625cf26f05397c3c533547 Mon Sep 17 00:00:00 2001 From: nkzawa Date: Fri, 29 Apr 2022 20:26:41 +0700 Subject: [PATCH 13/13] add link to the issue --- .../middleware/with-builtin-module/test/index.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/integration/middleware/with-builtin-module/test/index.test.js b/test/integration/middleware/with-builtin-module/test/index.test.js index 959ec61ba9c2..8dc72b64ff07 100644 --- a/test/integration/middleware/with-builtin-module/test/index.test.js +++ b/test/integration/middleware/with-builtin-module/test/index.test.js @@ -31,6 +31,7 @@ describe('Middleware importing Node.js built-in module', () => { let output = '' // restart the app for every test since the latest error is not shown sometimes + // See https://github.com/vercel/next.js/issues/36575 beforeEach(async () => { output = '' context.appPort = await findPort()