Skip to content

Commit 9e53af8

Browse files
authored
Fix next node buildin module error message for edge runtime (#36434)
- improve the message for importing node builtin module on edge runtime - fix to show the message on overlay of error browser with `next dev` - fix #36237 The message is NOT shown when using edge runtime (not middleware) since I cannot find a way to detect a webpack compilation is for edge runtime. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `yarn lint`
1 parent c905fda commit 9e53af8

File tree

14 files changed

+257
-38
lines changed

14 files changed

+257
-38
lines changed

packages/next/build/compiler.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Span } from '../trace'
55
export type CompilerResult = {
66
errors: webpack5.StatsError[]
77
warnings: webpack5.StatsError[]
8+
stats: webpack5.Stats | undefined
89
}
910

1011
function generateStats(
@@ -54,14 +55,17 @@ export function runCompiler(
5455
return resolve({
5556
errors: [{ message: reason, details: (err as any).details }],
5657
warnings: [],
58+
stats,
5759
})
5860
}
5961
return reject(err)
6062
} else if (!stats) throw new Error('No Stats from webpack')
6163

6264
const result = webpackCloseSpan
6365
.traceChild('webpack-generate-error-stats')
64-
.traceFn(() => generateStats({ errors: [], warnings: [] }, stats))
66+
.traceFn(() =>
67+
generateStats({ errors: [], warnings: [], stats }, stats)
68+
)
6569
return resolve(result)
6670
})
6771
})

packages/next/build/index.ts

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ import {
7878
eventPackageUsedInGetServerSideProps,
7979
} from '../telemetry/events'
8080
import { Telemetry } from '../telemetry/storage'
81-
import { CompilerResult, runCompiler } from './compiler'
81+
import { runCompiler } from './compiler'
8282
import {
8383
createEntrypoints,
8484
createPagesMapping,
@@ -96,6 +96,7 @@ import {
9696
PageInfo,
9797
printCustomRoutes,
9898
printTreeView,
99+
getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage,
99100
getUnresolvedModuleFromError,
100101
copyTracedFiles,
101102
isReservedPage,
@@ -135,6 +136,16 @@ export type PrerenderManifest = {
135136
preview: __ApiPreviewProps
136137
}
137138

139+
type CompilerResult = {
140+
errors: webpack.StatsError[]
141+
warnings: webpack.StatsError[]
142+
stats: [
143+
webpack.Stats | undefined,
144+
webpack.Stats | undefined,
145+
webpack.Stats | undefined
146+
]
147+
}
148+
138149
export default async function build(
139150
dir: string,
140151
conf = null,
@@ -164,7 +175,6 @@ export default async function build(
164175
// We enable concurrent features (Fizz-related rendering architecture) when
165176
// using React 18 or experimental.
166177
const hasReactRoot = !!process.env.__NEXT_REACT_ROOT
167-
const hasConcurrentFeatures = hasReactRoot
168178
const hasServerComponents =
169179
hasReactRoot && !!config.experimental.serverComponents
170180

@@ -619,7 +629,11 @@ export default async function build(
619629
ignore: [] as string[],
620630
}))
621631

622-
let result: CompilerResult = { warnings: [], errors: [] }
632+
let result: CompilerResult = {
633+
warnings: [],
634+
errors: [],
635+
stats: [undefined, undefined, undefined],
636+
}
623637
let webpackBuildStart
624638
let telemetryPlugin
625639
await (async () => {
@@ -684,6 +698,7 @@ export default async function build(
684698
result = {
685699
warnings: [...clientResult.warnings],
686700
errors: [...clientResult.errors],
701+
stats: [clientResult.stats, undefined, undefined],
687702
}
688703
} else {
689704
const serverResult = await runCompiler(configs[1], {
@@ -704,6 +719,11 @@ export default async function build(
704719
...serverResult.errors,
705720
...(edgeServerResult?.errors || []),
706721
],
722+
stats: [
723+
clientResult.stats,
724+
serverResult.stats,
725+
edgeServerResult?.stats,
726+
],
707727
}
708728
}
709729
})
@@ -745,14 +765,18 @@ export default async function build(
745765
console.error(error)
746766
console.error()
747767

748-
// When using the web runtime, common Node.js native APIs are not available.
749-
const moduleName = getUnresolvedModuleFromError(error)
750-
if (hasConcurrentFeatures && moduleName) {
751-
const err = new Error(
752-
`Native Node.js APIs are not supported in the Edge Runtime. Found \`${moduleName}\` imported.\n\n`
768+
const edgeRuntimeErrors = result.stats[2]?.compilation.errors ?? []
769+
770+
for (const err of edgeRuntimeErrors) {
771+
// When using the web runtime, common Node.js native APIs are not available.
772+
const moduleName = getUnresolvedModuleFromError(err.message)
773+
if (!moduleName) continue
774+
775+
const e = new Error(
776+
getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage(moduleName)
753777
) as NextError
754-
err.code = 'EDGE_RUNTIME_UNSUPPORTED_API'
755-
throw err
778+
e.code = 'EDGE_RUNTIME_UNSUPPORTED_API'
779+
throw e
756780
}
757781

758782
if (

packages/next/build/output/store.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import createStore from 'next/dist/compiled/unistore'
22
import stripAnsi from 'next/dist/compiled/strip-ansi'
33
import { flushAllTraces } from '../../trace'
4-
import { getUnresolvedModuleFromError } from '../utils'
54

65
import * as Log from './log'
76

@@ -90,14 +89,6 @@ store.subscribe((state) => {
9089
}
9190
}
9291

93-
const moduleName = getUnresolvedModuleFromError(cleanError)
94-
if (state.hasEdgeServer && moduleName) {
95-
console.error(
96-
`Native Node.js APIs are not supported in the Edge Runtime. Found \`${moduleName}\` imported.\n`
97-
)
98-
return
99-
}
100-
10192
// Ensure traces are flushed after each compile in development mode
10293
flushAllTraces()
10394
return

packages/next/build/utils.ts

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import type { NextConfigComplete, PageRuntime } from '../server/config-shared'
1+
import type {
2+
NextConfig,
3+
NextConfigComplete,
4+
PageRuntime,
5+
} from '../server/config-shared'
6+
import type { webpack5 } from 'next/dist/compiled/webpack/webpack'
27

38
import '../server/node-polyfill-fetch'
49
import chalk from 'next/dist/compiled/chalk'
@@ -20,6 +25,7 @@ import {
2025
SERVER_PROPS_SSG_CONFLICT,
2126
MIDDLEWARE_ROUTE,
2227
} from '../lib/constants'
28+
import { EDGE_RUNTIME_WEBPACK } from '../shared/lib/constants'
2329
import prettyBytes from '../lib/pretty-bytes'
2430
import { getRouteMatcher, getRouteRegex } from '../shared/lib/router/utils'
2531
import { isDynamicRoute } from '../shared/lib/router/utils/is-dynamic'
@@ -40,6 +46,7 @@ import { Sema } from 'next/dist/compiled/async-sema'
4046
import { MiddlewareManifest } from './webpack/plugins/middleware-plugin'
4147
import { denormalizePagePath } from '../shared/lib/page-path/denormalize-page-path'
4248
import { normalizePagePath } from '../shared/lib/page-path/normalize-page-path'
49+
import { getPageRuntime } from './entries'
4350

4451
const { builtinModules } = require('module')
4552
const RESERVED_PAGE = /^\/(_app|_error|_document|api(\/|$))/
@@ -1121,7 +1128,7 @@ export function getUnresolvedModuleFromError(
11211128
error: string
11221129
): string | undefined {
11231130
const moduleErrorRegex = new RegExp(
1124-
`Module not found: Can't resolve '(\\w+)'`
1131+
`Module not found: Error: Can't resolve '(\\w+)'`
11251132
)
11261133
const [, moduleName] = error.match(moduleErrorRegex) || []
11271134
return builtinModules.find((item: string) => item === moduleName)
@@ -1264,3 +1271,41 @@ export function isReservedPage(page: string) {
12641271
export function isCustomErrorPage(page: string) {
12651272
return page === '/404' || page === '/500'
12661273
}
1274+
1275+
// FIX ME: it does not work for non-middleware edge functions
1276+
// since chunks don't contain runtime specified somehow
1277+
export async function isEdgeRuntimeCompiled(
1278+
compilation: webpack5.Compilation,
1279+
module: any,
1280+
config: NextConfig
1281+
) {
1282+
if (!module) return false
1283+
1284+
for (const chunk of compilation.chunkGraph.getModuleChunksIterable(module)) {
1285+
let runtimes: string[]
1286+
if (typeof chunk.runtime === 'string') {
1287+
runtimes = [chunk.runtime]
1288+
} else if (chunk.runtime) {
1289+
runtimes = [...chunk.runtime]
1290+
} else {
1291+
runtimes = []
1292+
}
1293+
1294+
if (runtimes.some((r) => r === EDGE_RUNTIME_WEBPACK)) {
1295+
return true
1296+
}
1297+
}
1298+
1299+
// Check the page runtime as well since we cannot detect the runtime from
1300+
// compilation when it's for the client part of edge function
1301+
return (await getPageRuntime(module.resource, config)) === 'edge'
1302+
}
1303+
1304+
export function getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage(
1305+
name: string
1306+
) {
1307+
return (
1308+
`You're using a Node.js module (${name}) which is not supported in the Edge Runtime.\n` +
1309+
'Learn more: https://nextjs.org/docs/api-reference/edge-runtime'
1310+
)
1311+
}

packages/next/build/webpack-config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1537,7 +1537,7 @@ export default async function getBaseWebpackConfig(
15371537
isLikeServerless,
15381538
})
15391539
})(),
1540-
new WellKnownErrorsPlugin(),
1540+
new WellKnownErrorsPlugin({ config }),
15411541
isClient &&
15421542
new CopyFilePlugin({
15431543
filePath: require.resolve('./polyfills/polyfill-nomodule'),

packages/next/build/webpack/plugins/wellknown-errors-plugin/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import type { webpack5 as webpack } from 'next/dist/compiled/webpack/webpack'
22
import { getModuleBuildError } from './webpackModuleError'
3+
import { NextConfig } from '../../../../server/config-shared'
34

45
export class WellKnownErrorsPlugin {
6+
config: NextConfig
7+
8+
constructor({ config }: { config: NextConfig }) {
9+
this.config = config
10+
}
11+
512
apply(compiler: webpack.Compiler) {
613
compiler.hooks.compilation.tap('WellKnownErrorsPlugin', (compilation) => {
714
compilation.hooks.afterSeal.tapPromise(
@@ -13,7 +20,8 @@ export class WellKnownErrorsPlugin {
1320
try {
1421
const moduleError = await getModuleBuildError(
1522
compilation,
16-
err
23+
err,
24+
this.config
1725
)
1826
if (moduleError !== false) {
1927
compilation.errors[i] = moduleError

packages/next/build/webpack/plugins/wellknown-errors-plugin/parseNotFoundError.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ import Chalk from 'next/dist/compiled/chalk'
22
import { SimpleWebpackError } from './simpleWebpackError'
33
import { createOriginalStackFrame } from 'next/dist/compiled/@next/react-dev-overlay/middleware'
44
import type { webpack5 } from 'next/dist/compiled/webpack/webpack'
5+
import {
6+
getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage,
7+
getUnresolvedModuleFromError,
8+
isEdgeRuntimeCompiled,
9+
} from '../../../utils'
10+
import { NextConfig } from '../../../../server/config-shared'
511

612
const chalk = new Chalk.constructor({ enabled: true })
713

@@ -48,7 +54,8 @@ function getModuleTrace(input: any, compilation: any) {
4854
export async function getNotFoundError(
4955
compilation: webpack5.Compilation,
5056
input: any,
51-
fileName: string
57+
fileName: string,
58+
config: NextConfig
5259
) {
5360
if (input.name !== 'ModuleNotFoundError') {
5461
return false
@@ -98,7 +105,7 @@ export async function getNotFoundError(
98105

99106
const frame = result.originalCodeFrame ?? ''
100107

101-
const message =
108+
let message =
102109
chalk.red.bold('Module not found') +
103110
`: ${errorMessage}` +
104111
'\n' +
@@ -107,6 +114,15 @@ export async function getNotFoundError(
107114
importTrace() +
108115
'\nhttps://nextjs.org/docs/messages/module-not-found'
109116

117+
const moduleName = getUnresolvedModuleFromError(input.message)
118+
if (moduleName) {
119+
if (await isEdgeRuntimeCompiled(compilation, input.module, config)) {
120+
message +=
121+
'\n\n' +
122+
getNodeBuiltinModuleNotSupportedInEdgeRuntimeMessage(moduleName)
123+
}
124+
}
125+
110126
return new SimpleWebpackError(
111127
`${chalk.cyan(fileName)}:${chalk.yellow(
112128
result.originalStackFrame.lineNumber?.toString() ?? ''

packages/next/build/webpack/plugins/wellknown-errors-plugin/webpackModuleError.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { getScssError } from './parseScss'
77
import { getNotFoundError } from './parseNotFoundError'
88
import { SimpleWebpackError } from './simpleWebpackError'
99
import isError from '../../../../lib/is-error'
10+
import { NextConfig } from '../../../../server/config-shared'
1011

1112
function getFileData(
1213
compilation: webpack.Compilation,
@@ -42,7 +43,8 @@ function getFileData(
4243

4344
export async function getModuleBuildError(
4445
compilation: webpack.Compilation,
45-
input: any
46+
input: any,
47+
config: NextConfig
4648
): Promise<SimpleWebpackError | false> {
4749
if (
4850
!(
@@ -62,7 +64,8 @@ export async function getModuleBuildError(
6264
const notFoundError = await getNotFoundError(
6365
compilation,
6466
input,
65-
sourceFilename
67+
sourceFilename,
68+
config
6669
)
6770
if (notFoundError !== false) {
6871
return notFoundError

packages/next/client/dev/error-overlay/format-webpack-messages.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,11 @@ function formatWebpackMessages(json, verbose) {
163163
const formattedWarnings = json.warnings.map(function (message) {
164164
return formatMessage(message, verbose)
165165
})
166-
const result = { errors: formattedErrors, warnings: formattedWarnings }
166+
const result = {
167+
...json,
168+
errors: formattedErrors,
169+
warnings: formattedWarnings,
170+
}
167171
if (!verbose && result.errors.some(isLikelyASyntaxError)) {
168172
// If there are any syntax errors, show just them.
169173
result.errors = result.errors.filter(isLikelyASyntaxError)

0 commit comments

Comments
 (0)