From b4c60cd4e401a03e0cbed7dca1ff88902df1d235 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Wed, 23 Nov 2022 17:10:01 +0100 Subject: [PATCH 01/18] Show error overlay source when error is thrown during RSC/HTML rendering in app directory --- .../internal/helpers/getErrorByType.ts | 2 - .../internal/helpers/stack-frame.ts | 8 +- packages/next/server/dev/next-dev-server.ts | 23 ++-- .../src/internal/helpers/getErrorByType.ts | 2 - .../src/internal/helpers/stack-frame.ts | 8 +- packages/react-dev-overlay/src/middleware.ts | 118 ++++++++++++++---- .../fixtures/default-template/index.js | 9 +- .../ReactRefreshLogBox-app-doc.test.ts | 2 +- .../ReactRefreshLogBox-builtins.test.ts | 3 +- .../ReactRefreshLogBox-scss.test.ts | 2 +- .../acceptance/ReactRefreshLogBoxMisc.test.ts | 2 +- 11 files changed, 122 insertions(+), 57 deletions(-) diff --git a/packages/next/client/components/react-dev-overlay/internal/helpers/getErrorByType.ts b/packages/next/client/components/react-dev-overlay/internal/helpers/getErrorByType.ts index c569f931c3e4..3216b238115a 100644 --- a/packages/next/client/components/react-dev-overlay/internal/helpers/getErrorByType.ts +++ b/packages/next/client/components/react-dev-overlay/internal/helpers/getErrorByType.ts @@ -3,7 +3,6 @@ import { ACTION_UNHANDLED_REJECTION, } from '../error-overlay-reducer' import { SupportedErrorEvent } from '../container/Errors' -import { getErrorSource } from './nodeStackFrames' import { getOriginalStackFrames, OriginalStackFrame } from './stack-frame' export type ReadyRuntimeError = { @@ -26,7 +25,6 @@ export async function getErrorByType( error: event.reason, frames: await getOriginalStackFrames( event.frames, - getErrorSource(event.reason), event.reason.toString() ), } diff --git a/packages/next/client/components/react-dev-overlay/internal/helpers/stack-frame.ts b/packages/next/client/components/react-dev-overlay/internal/helpers/stack-frame.ts index a8349ba16b49..5ce1a3978cfc 100644 --- a/packages/next/client/components/react-dev-overlay/internal/helpers/stack-frame.ts +++ b/packages/next/client/components/react-dev-overlay/internal/helpers/stack-frame.ts @@ -30,15 +30,12 @@ export type OriginalStackFrame = originalCodeFrame: null } -export function getOriginalStackFrame( +function getOriginalStackFrame( source: StackFrame, - type: 'server' | 'edge-server' | null, errorMessage: string ): Promise { async function _getOriginalStackFrame(): Promise { const params = new URLSearchParams() - params.append('isServer', String(type === 'server')) - params.append('isEdgeServer', String(type === 'edge-server')) params.append('errorMessage', errorMessage) for (const key in source) { params.append(key, ((source as any)[key] ?? '').toString()) @@ -109,11 +106,10 @@ export function getOriginalStackFrame( export function getOriginalStackFrames( frames: StackFrame[], - type: 'server' | 'edge-server' | null, errorMessage: string ) { return Promise.all( - frames.map((frame) => getOriginalStackFrame(frame, type, errorMessage)) + frames.map((frame) => getOriginalStackFrame(frame, errorMessage)) ) } diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts index a372f49a336e..a82edaf17896 100644 --- a/packages/next/server/dev/next-dev-server.ts +++ b/packages/next/server/dev/next-dev-server.ts @@ -1063,18 +1063,24 @@ export default class DevServer extends Server { ) const src = getErrorSource(err as Error) - const compilation = ( - src === COMPILER_NAMES.edgeServer - ? this.hotReloader?.edgeServerStats?.compilation - : this.hotReloader?.serverStats?.compilation - )! - const source = await getSourceById( + // Try to get source from server compilation + let source = await getSourceById( !!frame.file?.startsWith(sep) || !!frame.file?.startsWith('file:'), moduleId, - compilation + this.hotReloader?.serverStats?.compilation ) + // If the source can't be found in the server compilation the edge compilation will be checked. + if (source === null) { + source = await getSourceById( + !!frame.file?.startsWith(sep) || + !!frame.file?.startsWith('file:'), + moduleId, + this.hotReloader?.edgeServerStats?.compilation + ) + } + const originalFrame = await createOriginalStackFrame({ line: frame.lineNumber!, column: frame.column, @@ -1083,7 +1089,8 @@ export default class DevServer extends Server { modulePath: moduleId, rootDirectory: this.dir, errorMessage: err.message, - compilation, + serverCompilation: this.hotReloader?.serverStats?.compilation, + edgeCompilation: this.hotReloader?.edgeServerStats?.compilation, }) if (originalFrame) { diff --git a/packages/react-dev-overlay/src/internal/helpers/getErrorByType.ts b/packages/react-dev-overlay/src/internal/helpers/getErrorByType.ts index 52bca422378f..12602e41af93 100644 --- a/packages/react-dev-overlay/src/internal/helpers/getErrorByType.ts +++ b/packages/react-dev-overlay/src/internal/helpers/getErrorByType.ts @@ -1,6 +1,5 @@ import { TYPE_UNHANDLED_ERROR, TYPE_UNHANDLED_REJECTION } from '../bus' import { SupportedErrorEvent } from '../container/Errors' -import { getErrorSource } from './nodeStackFrames' import { getOriginalStackFrames, OriginalStackFrame } from './stack-frame' export type ReadyRuntimeError = { @@ -23,7 +22,6 @@ export async function getErrorByType( error: event.reason, frames: await getOriginalStackFrames( event.frames, - getErrorSource(event.reason), event.reason.toString() ), } diff --git a/packages/react-dev-overlay/src/internal/helpers/stack-frame.ts b/packages/react-dev-overlay/src/internal/helpers/stack-frame.ts index 36f326d1b54e..bf9488022205 100644 --- a/packages/react-dev-overlay/src/internal/helpers/stack-frame.ts +++ b/packages/react-dev-overlay/src/internal/helpers/stack-frame.ts @@ -30,15 +30,12 @@ export type OriginalStackFrame = originalCodeFrame: null } -export function getOriginalStackFrame( +function getOriginalStackFrame( source: StackFrame, - type: 'server' | 'edge-server' | null, errorMessage: string ): Promise { async function _getOriginalStackFrame(): Promise { const params = new URLSearchParams() - params.append('isServer', String(type === 'server')) - params.append('isEdgeServer', String(type === 'edge-server')) params.append('errorMessage', errorMessage) for (const key in source) { params.append(key, ((source as any)[key] ?? '').toString()) @@ -109,11 +106,10 @@ export function getOriginalStackFrame( export function getOriginalStackFrames( frames: StackFrame[], - type: 'server' | 'edge-server' | null, errorMessage: string ) { return Promise.all( - frames.map((frame) => getOriginalStackFrame(frame, type, errorMessage)) + frames.map((frame) => getOriginalStackFrame(frame, errorMessage)) ) } diff --git a/packages/react-dev-overlay/src/middleware.ts b/packages/react-dev-overlay/src/middleware.ts index 9320a2fa8480..eab14f482e46 100644 --- a/packages/react-dev-overlay/src/middleware.ts +++ b/packages/react-dev-overlay/src/middleware.ts @@ -41,9 +41,10 @@ function getModuleById( id: string | undefined, compilation: webpack.Compilation ) { - return [...compilation.modules].find( - (searchModule) => getModuleId(compilation, searchModule) === id - ) + return [...compilation.modules].find((searchModule) => { + const moduleId = getModuleId(compilation, searchModule) + return moduleId === id + }) } function findModuleNotFoundFromError(errorMessage: string | undefined) { @@ -124,33 +125,66 @@ export async function createOriginalStackFrame({ line, column, source, + moduleId, modulePath, rootDirectory, frame, errorMessage, - compilation, + clientCompilation, + serverCompilation, + edgeCompilation, }: { line: number column: number | null source: any + moduleId?: string modulePath?: string rootDirectory: string frame: any errorMessage?: string - compilation?: webpack.Compilation + clientCompilation?: webpack.Compilation + serverCompilation?: webpack.Compilation + edgeCompilation?: webpack.Compilation }): Promise { const moduleNotFound = findModuleNotFoundFromError(errorMessage) - const result = - moduleNotFound && compilation - ? findOriginalSourcePositionAndContentFromCompilation( - modulePath, - moduleNotFound, - compilation - ) - : await findOriginalSourcePositionAndContent(source, { - line, - column, - }) + const result = await (async () => { + if (moduleNotFound) { + let moduleNotFoundResult = null + + if (clientCompilation) { + moduleNotFoundResult = + findOriginalSourcePositionAndContentFromCompilation( + moduleId, + moduleNotFound, + clientCompilation + ) + } + + if (moduleNotFoundResult === null && serverCompilation) { + moduleNotFoundResult = + findOriginalSourcePositionAndContentFromCompilation( + moduleId, + moduleNotFound, + serverCompilation + ) + } + + if (moduleNotFoundResult === null && edgeCompilation) { + moduleNotFoundResult = + findOriginalSourcePositionAndContentFromCompilation( + moduleId, + moduleNotFound, + edgeCompilation + ) + } + + return moduleNotFoundResult + } + return await findOriginalSourcePositionAndContent(source, { + line, + column, + }) + })() if (result === null) { return null @@ -164,7 +198,7 @@ export async function createOriginalStackFrame({ const filePath = path.resolve( rootDirectory, - modulePath || getSourcePath(sourcePosition.source) + getSourcePath(sourcePosition.source || modulePath) ) const originalFrame: StackFrame = { @@ -173,7 +207,11 @@ export async function createOriginalStackFrame({ : sourcePosition.source, lineNumber: sourcePosition.line, column: sourcePosition.column, - methodName: frame.methodName, // TODO: resolve original method name (?) + methodName: + sourcePosition.name || + // default is not a valid identifier in JS so webpack uses a custom variable when it's an unnamed default export + // Resolve it back to `default` for the method name if the source position didn't have the method. + frame.methodName.replace('__WEBPACK_DEFAULT_EXPORT__', 'default'), arguments: [], } @@ -268,20 +306,43 @@ function getOverlayMiddleware(options: OverlayMiddlewareOptions) { /^(webpack-internal:\/\/\/|file:\/\/)/, '' ) + const modulePath = frame.file.replace( + /^(webpack-internal:\/\/\/|file:\/\/)(\(.*\)\/)?/, + '' + ) let source: Source - const compilation = - frame.isEdgeServer === 'true' - ? options.edgeServerStats()?.compilation - : frame.isServer === 'true' - ? options.serverStats()?.compilation - : options.stats()?.compilation + const clientCompilation = options.stats()?.compilation + const serverCompilation = options.serverStats()?.compilation + const edgeCompilation = options.edgeServerStats()?.compilation try { + // Try Client Compilation first + // In `pages` the majority of modules can be resolved from there + // In `app` it depends on if it's a server / client component and when the code throws. E.g. during HTML rendering it's the server/edge compilation. source = await getSourceById( frame.file.startsWith('file:'), moduleId, - compilation + clientCompilation ) + // Try Server Compilation + // In `pages` this could be something imported in getServerSideProps/getStaticProps as the code for those is tree-shaken. + // In `app` this finds server components and code that was imported from a server component. It also covers when client component code throws during HTML rendering. + if (source === null) { + source = await getSourceById( + frame.file.startsWith('file:'), + moduleId, + serverCompilation + ) + } + // Try Edge Server Compilation + // Both cases are the same as Server Compilation, main difference is that it covers `runtime: 'edge'` pages/app routes. + if (source === null) { + source = await getSourceById( + frame.file.startsWith('file:'), + moduleId, + edgeCompilation + ) + } } catch (err) { console.log('Failed to get source map:', err) res.statusCode = 500 @@ -310,10 +371,13 @@ function getOverlayMiddleware(options: OverlayMiddlewareOptions) { column: frameColumn, source, frame, - modulePath: moduleId, + moduleId, + modulePath, rootDirectory: options.rootDirectory, errorMessage: frame.errorMessage, - compilation, + clientCompilation, + serverCompilation, + edgeCompilation, }) if (originalStackFrameResponse === null) { diff --git a/test/development/acceptance-app/fixtures/default-template/index.js b/test/development/acceptance-app/fixtures/default-template/index.js index 31fd86d55937..15a0d1907202 100644 --- a/test/development/acceptance-app/fixtures/default-template/index.js +++ b/test/development/acceptance-app/fixtures/default-template/index.js @@ -1 +1,8 @@ -export default () => 'new sandbox' +import Comp from 'b' +export default function Oops() { + return ( +
+ lol +
+ ) +} diff --git a/test/development/acceptance/ReactRefreshLogBox-app-doc.test.ts b/test/development/acceptance/ReactRefreshLogBox-app-doc.test.ts index 755ecdb416d2..0f9d9c1d14a8 100644 --- a/test/development/acceptance/ReactRefreshLogBox-app-doc.test.ts +++ b/test/development/acceptance/ReactRefreshLogBox-app-doc.test.ts @@ -2,7 +2,7 @@ import { sandbox } from './helpers' import { createNext } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' -describe('ReactRefreshLogBox', () => { +describe('ReactRefreshLogBox app doc', () => { let next: NextInstance beforeAll(async () => { diff --git a/test/development/acceptance/ReactRefreshLogBox-builtins.test.ts b/test/development/acceptance/ReactRefreshLogBox-builtins.test.ts index 87a2439ea3ad..afed99e7ecdb 100644 --- a/test/development/acceptance/ReactRefreshLogBox-builtins.test.ts +++ b/test/development/acceptance/ReactRefreshLogBox-builtins.test.ts @@ -2,7 +2,7 @@ import { sandbox } from './helpers' import { createNext } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' -describe('ReactRefreshLogBox', () => { +describe('ReactRefreshLogBox builtins', () => { let next: NextInstance beforeAll(async () => { @@ -13,7 +13,6 @@ describe('ReactRefreshLogBox', () => { }) afterAll(() => next.destroy()) - // Module trace is only available with webpack 5 test('Node.js builtins', async () => { const { session, cleanup } = await sandbox( next, diff --git a/test/development/acceptance/ReactRefreshLogBox-scss.test.ts b/test/development/acceptance/ReactRefreshLogBox-scss.test.ts index 93fcaf5cba6d..66499dca4bb3 100644 --- a/test/development/acceptance/ReactRefreshLogBox-scss.test.ts +++ b/test/development/acceptance/ReactRefreshLogBox-scss.test.ts @@ -5,7 +5,7 @@ import { NextInstance } from 'test/lib/next-modes/base' // TODO: figure out why snapshots mismatch on GitHub actions // specifically but work in docker and locally -describe.skip('ReactRefreshLogBox', () => { +describe.skip('ReactRefreshLogBox scss', () => { let next: NextInstance beforeAll(async () => { diff --git a/test/development/acceptance/ReactRefreshLogBoxMisc.test.ts b/test/development/acceptance/ReactRefreshLogBoxMisc.test.ts index 738e7a5f79b9..44503cae8f95 100644 --- a/test/development/acceptance/ReactRefreshLogBoxMisc.test.ts +++ b/test/development/acceptance/ReactRefreshLogBoxMisc.test.ts @@ -4,7 +4,7 @@ import { NextInstance } from 'test/lib/next-modes/base' // TODO: re-enable these tests after figuring out what is causing // them to be so unreliable in CI -describe.skip('ReactRefreshLogBox', () => { +describe.skip('ReactRefreshLogBox misc', () => { let next: NextInstance beforeAll(async () => { From 1baf39ee3a3659ce4b01fe01ce4ea5ebb57a4fa4 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Wed, 23 Nov 2022 17:18:33 +0100 Subject: [PATCH 02/18] Fix ModuleNotFound handling --- packages/react-dev-overlay/src/middleware.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-dev-overlay/src/middleware.ts b/packages/react-dev-overlay/src/middleware.ts index eab14f482e46..dc79d49482e2 100644 --- a/packages/react-dev-overlay/src/middleware.ts +++ b/packages/react-dev-overlay/src/middleware.ts @@ -211,7 +211,7 @@ export async function createOriginalStackFrame({ sourcePosition.name || // default is not a valid identifier in JS so webpack uses a custom variable when it's an unnamed default export // Resolve it back to `default` for the method name if the source position didn't have the method. - frame.methodName.replace('__WEBPACK_DEFAULT_EXPORT__', 'default'), + frame.methodName?.replace('__WEBPACK_DEFAULT_EXPORT__', 'default'), arguments: [], } From e7ff01cc09a4acf937af4b89ae309892bb7e8aab Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Wed, 23 Nov 2022 17:31:25 +0100 Subject: [PATCH 03/18] Revert test name changes as it affects snapshots --- test/development/acceptance/ReactRefreshLogBox-app-doc.test.ts | 2 +- .../development/acceptance/ReactRefreshLogBox-builtins.test.ts | 3 ++- test/development/acceptance/ReactRefreshLogBox-scss.test.ts | 2 +- test/development/acceptance/ReactRefreshLogBoxMisc.test.ts | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/test/development/acceptance/ReactRefreshLogBox-app-doc.test.ts b/test/development/acceptance/ReactRefreshLogBox-app-doc.test.ts index 0f9d9c1d14a8..755ecdb416d2 100644 --- a/test/development/acceptance/ReactRefreshLogBox-app-doc.test.ts +++ b/test/development/acceptance/ReactRefreshLogBox-app-doc.test.ts @@ -2,7 +2,7 @@ import { sandbox } from './helpers' import { createNext } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' -describe('ReactRefreshLogBox app doc', () => { +describe('ReactRefreshLogBox', () => { let next: NextInstance beforeAll(async () => { diff --git a/test/development/acceptance/ReactRefreshLogBox-builtins.test.ts b/test/development/acceptance/ReactRefreshLogBox-builtins.test.ts index afed99e7ecdb..87a2439ea3ad 100644 --- a/test/development/acceptance/ReactRefreshLogBox-builtins.test.ts +++ b/test/development/acceptance/ReactRefreshLogBox-builtins.test.ts @@ -2,7 +2,7 @@ import { sandbox } from './helpers' import { createNext } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' -describe('ReactRefreshLogBox builtins', () => { +describe('ReactRefreshLogBox', () => { let next: NextInstance beforeAll(async () => { @@ -13,6 +13,7 @@ describe('ReactRefreshLogBox builtins', () => { }) afterAll(() => next.destroy()) + // Module trace is only available with webpack 5 test('Node.js builtins', async () => { const { session, cleanup } = await sandbox( next, diff --git a/test/development/acceptance/ReactRefreshLogBox-scss.test.ts b/test/development/acceptance/ReactRefreshLogBox-scss.test.ts index 66499dca4bb3..93fcaf5cba6d 100644 --- a/test/development/acceptance/ReactRefreshLogBox-scss.test.ts +++ b/test/development/acceptance/ReactRefreshLogBox-scss.test.ts @@ -5,7 +5,7 @@ import { NextInstance } from 'test/lib/next-modes/base' // TODO: figure out why snapshots mismatch on GitHub actions // specifically but work in docker and locally -describe.skip('ReactRefreshLogBox scss', () => { +describe.skip('ReactRefreshLogBox', () => { let next: NextInstance beforeAll(async () => { diff --git a/test/development/acceptance/ReactRefreshLogBoxMisc.test.ts b/test/development/acceptance/ReactRefreshLogBoxMisc.test.ts index 44503cae8f95..738e7a5f79b9 100644 --- a/test/development/acceptance/ReactRefreshLogBoxMisc.test.ts +++ b/test/development/acceptance/ReactRefreshLogBoxMisc.test.ts @@ -4,7 +4,7 @@ import { NextInstance } from 'test/lib/next-modes/base' // TODO: re-enable these tests after figuring out what is causing // them to be so unreliable in CI -describe.skip('ReactRefreshLogBox misc', () => { +describe.skip('ReactRefreshLogBox', () => { let next: NextInstance beforeAll(async () => { From 37c78e7fb1ef7949711ac5326af5de4afd2811a4 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Wed, 23 Nov 2022 18:49:34 +0100 Subject: [PATCH 04/18] Update snapshot --- .../acceptance/__snapshots__/ReactRefreshLogBox.test.ts.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/development/acceptance/__snapshots__/ReactRefreshLogBox.test.ts.snap b/test/development/acceptance/__snapshots__/ReactRefreshLogBox.test.ts.snap index cbcc7f18ef1d..6f59ee031abe 100644 --- a/test/development/acceptance/__snapshots__/ReactRefreshLogBox.test.ts.snap +++ b/test/development/acceptance/__snapshots__/ReactRefreshLogBox.test.ts.snap @@ -127,7 +127,7 @@ exports[`ReactRefreshLogBox stuck error 1`] = ` `; exports[`ReactRefreshLogBox syntax > runtime error 1`] = ` -"index.js (6:16) @ eval +"index.js (6:16) @ Error 4 | setInterval(() => { 5 | i++ From 3fe49a514a805171a553f0e2dda4ad4d66a85405 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Wed, 23 Nov 2022 19:02:47 +0100 Subject: [PATCH 05/18] Update snapshot test --- .../__snapshots__/ReactRefreshLogBox.test.ts.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox.test.ts.snap b/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox.test.ts.snap index 488a0c937654..031f532d4b6d 100644 --- a/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox.test.ts.snap +++ b/test/development/acceptance-app/__snapshots__/ReactRefreshLogBox.test.ts.snap @@ -115,7 +115,7 @@ exports[`ReactRefreshLogBox app stuck error 1`] = ` `; exports[`ReactRefreshLogBox app syntax > runtime error 1`] = ` -"index.js (6:16) @ eval +"index.js (6:16) @ Error 4 | setInterval(() => { 5 | i++ From 6a75d624528138a7b43a78b4c6ddd2ec0b354a44 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Thu, 24 Nov 2022 12:06:13 +0100 Subject: [PATCH 06/18] Increase output target for dev overlay --- packages/next/taskfile.js | 2 +- packages/react-dev-overlay/tsconfig.json | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/next/taskfile.js b/packages/next/taskfile.js index c47ef9ed019f..46e0ec32552f 100644 --- a/packages/next/taskfile.js +++ b/packages/next/taskfile.js @@ -389,7 +389,7 @@ export async function ncc_next__react_dev_overlay(task, opts) { precompiled: false, packageName: '@next/react-dev-overlay', externals: overlayExternals, - target: 'es5', + target: 'es2018', }) .target('dist/compiled/@next/react-dev-overlay/dist') diff --git a/packages/react-dev-overlay/tsconfig.json b/packages/react-dev-overlay/tsconfig.json index 7bb540dee0f1..551d3124364c 100644 --- a/packages/react-dev-overlay/tsconfig.json +++ b/packages/react-dev-overlay/tsconfig.json @@ -4,14 +4,15 @@ "sourceMap": true, "strict": true, "esModuleInterop": true, - "target": "es3", + "target": "es2020", "lib": ["dom"], "downlevelIteration": true, "preserveWatchOutput": true, "outDir": "dist", "jsx": "react", "noFallthroughCasesInSwitch": true, - "skipLibCheck": true + "skipLibCheck": true, + "moduleResolution": "Node16" }, "include": ["src/**/*.ts", "src/**/*.tsx"], "exclude": ["node_modules"] From 21da90aea8bf6e852a771d601e0ed33b0473a34a Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Thu, 24 Nov 2022 12:07:01 +0100 Subject: [PATCH 07/18] Remove code that does the same thing --- test/development/acceptance/ReactRefreshLogBox.test.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/development/acceptance/ReactRefreshLogBox.test.ts b/test/development/acceptance/ReactRefreshLogBox.test.ts index 45ed9991c360..d9aced58dba6 100644 --- a/test/development/acceptance/ReactRefreshLogBox.test.ts +++ b/test/development/acceptance/ReactRefreshLogBox.test.ts @@ -358,11 +358,7 @@ describe('ReactRefreshLogBox', () => { ) expect(await session.hasRedbox(true)).toBe(true) - if (process.platform === 'win32') { - expect(await session.getRedboxSource()).toMatchSnapshot() - } else { - expect(await session.getRedboxSource()).toMatchSnapshot() - } + expect(await session.getRedboxSource()).toMatchSnapshot() await cleanup() }) From 51bb1c65b56b66b2b8372af59d7babdb39bce3ee Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Thu, 24 Nov 2022 12:07:28 +0100 Subject: [PATCH 08/18] Remove __WEBPACK_DEFAULT_EXPORT__ references in tests --- test/development/basic-basepath/hmr.test.ts | 7 +----- test/development/basic/hmr.test.ts | 7 +----- .../middleware-dev-errors/test/index.test.js | 2 +- test/lib/next-test-utils.js | 22 +++++++++---------- 4 files changed, 13 insertions(+), 25 deletions(-) diff --git a/test/development/basic-basepath/hmr.test.ts b/test/development/basic-basepath/hmr.test.ts index f4cba53e3e09..8f80697a64d6 100644 --- a/test/development/basic-basepath/hmr.test.ts +++ b/test/development/basic-basepath/hmr.test.ts @@ -536,12 +536,7 @@ describe('basic HMR', () => { expect(await hasRedbox(browser)).toBe(true) // TODO: Replace this when webpack 5 is the default - expect( - (await getRedboxHeader(browser)).replace( - '__WEBPACK_DEFAULT_EXPORT__', - 'Unknown' - ) - ).toMatch( + expect(await getRedboxHeader(browser)).toMatch( `Objects are not valid as a React child (found: ${ isReact17 ? '/search/' : '[object RegExp]' }). If you meant to render a collection of children, use an array instead.` diff --git a/test/development/basic/hmr.test.ts b/test/development/basic/hmr.test.ts index 75fea4552e34..4bfd3ab66c1c 100644 --- a/test/development/basic/hmr.test.ts +++ b/test/development/basic/hmr.test.ts @@ -601,12 +601,7 @@ describe('basic HMR', () => { expect(await hasRedbox(browser)).toBe(true) // TODO: Replace this when webpack 5 is the default - expect( - (await getRedboxHeader(browser)).replace( - '__WEBPACK_DEFAULT_EXPORT__', - 'Unknown' - ) - ).toMatch( + expect(await getRedboxHeader(browser)).toMatch( `Objects are not valid as a React child (found: ${ isReact17 ? '/search/' : '[object RegExp]' }). If you meant to render a collection of children, use an array instead.` diff --git a/test/integration/middleware-dev-errors/test/index.test.js b/test/integration/middleware-dev-errors/test/index.test.js index 1d8f32ee27b0..b4cd39e44076 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.default \\[as handler\\]\nerror - boom`, 'm' ) ) diff --git a/test/lib/next-test-utils.js b/test/lib/next-test-utils.js index 3cdda7813ab5..aa7d8d0632be 100644 --- a/test/lib/next-test-utils.js +++ b/test/lib/next-test-utils.js @@ -647,13 +647,13 @@ export async function getRedboxHeader(browser) { evaluate(browser, () => { const portal = [].slice .call(document.querySelectorAll('nextjs-portal')) - .find((p) => p.shadowRoot.querySelector('[data-nextjs-dialog-header')) + .find((p) => + p.shadowRoot.querySelector('[data-nextjs-dialog-header]') + ) const root = portal.shadowRoot - return root - .querySelector('[data-nextjs-dialog-header]') - .innerText.replace(/__WEBPACK_DEFAULT_EXPORT__/, 'Unknown') + return root.querySelector('[data-nextjs-dialog-header]').innerText }), - 3000, + 10000, 500, 'getRedboxHeader' ) @@ -671,11 +671,11 @@ export async function getRedboxSource(browser) { ) ) const root = portal.shadowRoot - return root - .querySelector('[data-nextjs-codeframe], [data-nextjs-terminal]') - .innerText.replace(/__WEBPACK_DEFAULT_EXPORT__/, 'Unknown') + return root.querySelector( + '[data-nextjs-codeframe], [data-nextjs-terminal]' + ).innerText }), - 3000, + 10000, 500, 'getRedboxSource' ) @@ -691,9 +691,7 @@ export async function getRedboxDescription(browser) { p.shadowRoot.querySelector('[data-nextjs-dialog-header]') ) const root = portal.shadowRoot - return root - .querySelector('#nextjs__container_errors_desc') - .innerText.replace(/__WEBPACK_DEFAULT_EXPORT__/, 'Unknown') + return root.querySelector('#nextjs__container_errors_desc').innerText }), 3000, 500, From 409234a0795de716810cff733939c6d0a492a5dd Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Thu, 24 Nov 2022 12:10:44 +0100 Subject: [PATCH 09/18] Revert fixture change --- .../acceptance-app/fixtures/default-template/index.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/test/development/acceptance-app/fixtures/default-template/index.js b/test/development/acceptance-app/fixtures/default-template/index.js index 15a0d1907202..31fd86d55937 100644 --- a/test/development/acceptance-app/fixtures/default-template/index.js +++ b/test/development/acceptance-app/fixtures/default-template/index.js @@ -1,8 +1 @@ -import Comp from 'b' -export default function Oops() { - return ( -
- lol -
- ) -} +export default () => 'new sandbox' From 8f7b411785178089e08c0f82bc76f3fa7cab5e9a Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Thu, 24 Nov 2022 13:33:46 +0100 Subject: [PATCH 10/18] Revert checks for server/edge compiler as module ids conflict (same id across compilers) --- .../internal/helpers/getErrorByType.ts | 2 ++ .../internal/helpers/stack-frame.ts | 9 +++-- packages/next/server/dev/next-dev-server.ts | 33 +++++++++-------- .../src/internal/helpers/getErrorByType.ts | 2 ++ .../src/internal/helpers/stack-frame.ts | 8 +++-- packages/react-dev-overlay/src/middleware.ts | 36 +++++++++++-------- 6 files changed, 55 insertions(+), 35 deletions(-) diff --git a/packages/next/client/components/react-dev-overlay/internal/helpers/getErrorByType.ts b/packages/next/client/components/react-dev-overlay/internal/helpers/getErrorByType.ts index 3216b238115a..c569f931c3e4 100644 --- a/packages/next/client/components/react-dev-overlay/internal/helpers/getErrorByType.ts +++ b/packages/next/client/components/react-dev-overlay/internal/helpers/getErrorByType.ts @@ -3,6 +3,7 @@ import { ACTION_UNHANDLED_REJECTION, } from '../error-overlay-reducer' import { SupportedErrorEvent } from '../container/Errors' +import { getErrorSource } from './nodeStackFrames' import { getOriginalStackFrames, OriginalStackFrame } from './stack-frame' export type ReadyRuntimeError = { @@ -25,6 +26,7 @@ export async function getErrorByType( error: event.reason, frames: await getOriginalStackFrames( event.frames, + getErrorSource(event.reason), event.reason.toString() ), } diff --git a/packages/next/client/components/react-dev-overlay/internal/helpers/stack-frame.ts b/packages/next/client/components/react-dev-overlay/internal/helpers/stack-frame.ts index 5ce1a3978cfc..b26cc1642c1c 100644 --- a/packages/next/client/components/react-dev-overlay/internal/helpers/stack-frame.ts +++ b/packages/next/client/components/react-dev-overlay/internal/helpers/stack-frame.ts @@ -30,12 +30,16 @@ export type OriginalStackFrame = originalCodeFrame: null } -function getOriginalStackFrame( +export function getOriginalStackFrame( source: StackFrame, + type: 'server' | 'edge-server' | null, errorMessage: string ): Promise { async function _getOriginalStackFrame(): Promise { const params = new URLSearchParams() + params.append('isServer', String(type === 'server')) + params.append('isEdgeServer', String(type === 'edge-server')) + params.append('isAppDirectory', 'true') params.append('errorMessage', errorMessage) for (const key in source) { params.append(key, ((source as any)[key] ?? '').toString()) @@ -106,10 +110,11 @@ function getOriginalStackFrame( export function getOriginalStackFrames( frames: StackFrame[], + type: 'server' | 'edge-server' | null, errorMessage: string ) { return Promise.all( - frames.map((frame) => getOriginalStackFrame(frame, errorMessage)) + frames.map((frame) => getOriginalStackFrame(frame, type, errorMessage)) ) } diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts index a82edaf17896..41a20d8d2886 100644 --- a/packages/next/server/dev/next-dev-server.ts +++ b/packages/next/server/dev/next-dev-server.ts @@ -1063,24 +1063,19 @@ export default class DevServer extends Server { ) const src = getErrorSource(err as Error) - - // Try to get source from server compilation - let source = await getSourceById( + const isEdgeCompiler = src === COMPILER_NAMES.edgeServer + const compilation = ( + isEdgeCompiler + ? this.hotReloader?.edgeServerStats?.compilation + : this.hotReloader?.serverStats?.compilation + )! + + const source = await getSourceById( !!frame.file?.startsWith(sep) || !!frame.file?.startsWith('file:'), moduleId, - this.hotReloader?.serverStats?.compilation + compilation ) - // If the source can't be found in the server compilation the edge compilation will be checked. - if (source === null) { - source = await getSourceById( - !!frame.file?.startsWith(sep) || - !!frame.file?.startsWith('file:'), - moduleId, - this.hotReloader?.edgeServerStats?.compilation - ) - } - const originalFrame = await createOriginalStackFrame({ line: frame.lineNumber!, column: frame.column, @@ -1089,8 +1084,12 @@ export default class DevServer extends Server { modulePath: moduleId, rootDirectory: this.dir, errorMessage: err.message, - serverCompilation: this.hotReloader?.serverStats?.compilation, - edgeCompilation: this.hotReloader?.edgeServerStats?.compilation, + serverCompilation: isEdgeCompiler + ? undefined + : this.hotReloader?.serverStats?.compilation, + edgeCompilation: isEdgeCompiler + ? this.hotReloader?.edgeServerStats?.compilation + : undefined, }) if (originalFrame) { @@ -1100,7 +1099,7 @@ export default class DevServer extends Server { Log[type === 'warning' ? 'warn' : 'error']( `${file} (${lineNumber}:${column}) @ ${methodName}` ) - if (src === COMPILER_NAMES.edgeServer) { + if (isEdgeCompiler) { err = err.message } if (type === 'warning') { diff --git a/packages/react-dev-overlay/src/internal/helpers/getErrorByType.ts b/packages/react-dev-overlay/src/internal/helpers/getErrorByType.ts index 12602e41af93..52bca422378f 100644 --- a/packages/react-dev-overlay/src/internal/helpers/getErrorByType.ts +++ b/packages/react-dev-overlay/src/internal/helpers/getErrorByType.ts @@ -1,5 +1,6 @@ import { TYPE_UNHANDLED_ERROR, TYPE_UNHANDLED_REJECTION } from '../bus' import { SupportedErrorEvent } from '../container/Errors' +import { getErrorSource } from './nodeStackFrames' import { getOriginalStackFrames, OriginalStackFrame } from './stack-frame' export type ReadyRuntimeError = { @@ -22,6 +23,7 @@ export async function getErrorByType( error: event.reason, frames: await getOriginalStackFrames( event.frames, + getErrorSource(event.reason), event.reason.toString() ), } diff --git a/packages/react-dev-overlay/src/internal/helpers/stack-frame.ts b/packages/react-dev-overlay/src/internal/helpers/stack-frame.ts index bf9488022205..36f326d1b54e 100644 --- a/packages/react-dev-overlay/src/internal/helpers/stack-frame.ts +++ b/packages/react-dev-overlay/src/internal/helpers/stack-frame.ts @@ -30,12 +30,15 @@ export type OriginalStackFrame = originalCodeFrame: null } -function getOriginalStackFrame( +export function getOriginalStackFrame( source: StackFrame, + type: 'server' | 'edge-server' | null, errorMessage: string ): Promise { async function _getOriginalStackFrame(): Promise { const params = new URLSearchParams() + params.append('isServer', String(type === 'server')) + params.append('isEdgeServer', String(type === 'edge-server')) params.append('errorMessage', errorMessage) for (const key in source) { params.append(key, ((source as any)[key] ?? '').toString()) @@ -106,10 +109,11 @@ function getOriginalStackFrame( export function getOriginalStackFrames( frames: StackFrame[], + type: 'server' | 'edge-server' | null, errorMessage: string ) { return Promise.all( - frames.map((frame) => getOriginalStackFrame(frame, errorMessage)) + frames.map((frame) => getOriginalStackFrame(frame, type, errorMessage)) ) } diff --git a/packages/react-dev-overlay/src/middleware.ts b/packages/react-dev-overlay/src/middleware.ts index dc79d49482e2..20cecc41f806 100644 --- a/packages/react-dev-overlay/src/middleware.ts +++ b/packages/react-dev-overlay/src/middleware.ts @@ -288,8 +288,14 @@ function getOverlayMiddleware(options: OverlayMiddlewareOptions) { const frame = query as unknown as StackFrame & { isEdgeServer: 'true' | 'false' isServer: 'true' | 'false' + isAppDirectory: 'true' | 'false' errorMessage: string | undefined } + const isAppDirectory = frame.isAppDirectory === 'true' + const isServerError = frame.isServer === 'true' + const isEdgeServerError = frame.isEdgeServer === 'true' + const isClientError = !isServerError && !isEdgeServerError + if ( !( (frame.file?.startsWith('webpack-internal:///') || @@ -311,23 +317,25 @@ function getOverlayMiddleware(options: OverlayMiddlewareOptions) { '' ) - let source: Source + let source: Source = null const clientCompilation = options.stats()?.compilation const serverCompilation = options.serverStats()?.compilation const edgeCompilation = options.edgeServerStats()?.compilation try { - // Try Client Compilation first - // In `pages` the majority of modules can be resolved from there - // In `app` it depends on if it's a server / client component and when the code throws. E.g. during HTML rendering it's the server/edge compilation. - source = await getSourceById( - frame.file.startsWith('file:'), - moduleId, - clientCompilation - ) + if (isClientError || isAppDirectory) { + // Try Client Compilation first + // In `pages` we leverage `isClientError` to check + // In `app` it depends on if it's a server / client component and when the code throws. E.g. during HTML rendering it's the server/edge compilation. + source = await getSourceById( + frame.file.startsWith('file:'), + moduleId, + clientCompilation + ) + } // Try Server Compilation // In `pages` this could be something imported in getServerSideProps/getStaticProps as the code for those is tree-shaken. // In `app` this finds server components and code that was imported from a server component. It also covers when client component code throws during HTML rendering. - if (source === null) { + if ((isServerError || isAppDirectory) && source === null) { source = await getSourceById( frame.file.startsWith('file:'), moduleId, @@ -336,7 +344,7 @@ function getOverlayMiddleware(options: OverlayMiddlewareOptions) { } // Try Edge Server Compilation // Both cases are the same as Server Compilation, main difference is that it covers `runtime: 'edge'` pages/app routes. - if (source === null) { + if ((isEdgeServerError || isAppDirectory) && source === null) { source = await getSourceById( frame.file.startsWith('file:'), moduleId, @@ -375,9 +383,9 @@ function getOverlayMiddleware(options: OverlayMiddlewareOptions) { modulePath, rootDirectory: options.rootDirectory, errorMessage: frame.errorMessage, - clientCompilation, - serverCompilation, - edgeCompilation, + clientCompilation: isClientError ? clientCompilation : undefined, + serverCompilation: isServerError ? serverCompilation : undefined, + edgeCompilation: isEdgeServerError ? edgeCompilation : undefined, }) if (originalStackFrameResponse === null) { From aa4a06af06193e48de98866857265f293a79435f Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Thu, 24 Nov 2022 14:16:09 +0100 Subject: [PATCH 11/18] Fix test --- .../acceptance-app/ReactRefreshRegression.test.ts | 13 +++++-------- test/development/acceptance-app/helpers.ts | 1 + 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/test/development/acceptance-app/ReactRefreshRegression.test.ts b/test/development/acceptance-app/ReactRefreshRegression.test.ts index c296c66b24cf..826a8ed9b2b0 100644 --- a/test/development/acceptance-app/ReactRefreshRegression.test.ts +++ b/test/development/acceptance-app/ReactRefreshRegression.test.ts @@ -3,6 +3,7 @@ import { sandbox } from './helpers' import { createNext, FileRef } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' import path from 'path' +import browser from 'test/integration/export/test/browser' describe('ReactRefreshRegression app', () => { if (process.env.NEXT_TEST_REACT_VERSION === '^17') { @@ -270,19 +271,15 @@ describe('ReactRefreshRegression app', () => { // https://github.com/vercel/next.js/issues/11504 // TODO-APP: fix case where error is not resolved to source correctly. - test.skip('shows an overlay for a server-side error', async () => { - const { session, cleanup } = await sandbox(next) + test('shows an overlay for anonymous server-side error', async () => { + const { session, browser, cleanup } = await sandbox(next) await session.patch( - 'app/page.js', - `export default function () { throw new Error('pre boom'); }` - ) - - const didNotReload = await session.patch( 'app/page.js', `export default function () { throw new Error('boom'); }` ) - expect(didNotReload).toBe(false) + + await browser.refresh() expect(await session.hasRedbox(true)).toBe(true) diff --git a/test/development/acceptance-app/helpers.ts b/test/development/acceptance-app/helpers.ts index 0802671de7ea..f0787fb84b76 100644 --- a/test/development/acceptance-app/helpers.ts +++ b/test/development/acceptance-app/helpers.ts @@ -22,6 +22,7 @@ export async function sandbox( await next.start() const browser = await webdriver(next.appPort, '/') return { + browser, session: { async write(filename, content) { // Update the file on filesystem From 045436cc241bfbf07e264b57f3df6cd526b8d81e Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Thu, 24 Nov 2022 14:23:27 +0100 Subject: [PATCH 12/18] Add additional tests --- .../ReactRefreshRegression.test.ts | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/test/development/acceptance-app/ReactRefreshRegression.test.ts b/test/development/acceptance-app/ReactRefreshRegression.test.ts index 826a8ed9b2b0..b1b6f02ff676 100644 --- a/test/development/acceptance-app/ReactRefreshRegression.test.ts +++ b/test/development/acceptance-app/ReactRefreshRegression.test.ts @@ -271,7 +271,7 @@ describe('ReactRefreshRegression app', () => { // https://github.com/vercel/next.js/issues/11504 // TODO-APP: fix case where error is not resolved to source correctly. - test('shows an overlay for anonymous server-side error', async () => { + test('shows an overlay for anonymous function server-side error', async () => { const { session, browser, cleanup } = await sandbox(next) await session.patch( @@ -292,6 +292,50 @@ describe('ReactRefreshRegression app', () => { await cleanup() }) + test('shows an overlay for server-side error in server component', async () => { + const { session, browser, cleanup } = await sandbox(next) + + await session.patch( + 'app/page.js', + `export default function Page() { throw new Error('boom'); }` + ) + + await browser.refresh() + + expect(await session.hasRedbox(true)).toBe(true) + + const source = await session.getRedboxSource() + expect(source.split(/\r?\n/g).slice(2).join('\n')).toMatchInlineSnapshot(` + "> 1 | export default function Page() { throw new Error('boom'); } + | ^" + `) + + await cleanup() + }) + + test('shows an overlay for server-side error in client component', async () => { + const { session, browser, cleanup } = await sandbox(next) + + await session.patch( + 'app/page.js', + `'use client' + export default function Page() { throw new Error('boom'); }` + ) + + await browser.refresh() + + expect(await session.hasRedbox(true)).toBe(true) + + const source = await session.getRedboxSource() + expect(source.split(/\r?\n/g).slice(2).join('\n')).toMatchInlineSnapshot(` + " 1 | 'use client' + > 2 | export default function Page() { throw new Error('boom'); } + | ^" + `) + + await cleanup() + }) + // https://github.com/vercel/next.js/issues/13574 test('custom loader mdx should have Fast Refresh enabled', async () => { const files = new Map() From d1b0d0936e2937c344a9cbbf1b6761025b832337 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Thu, 24 Nov 2022 17:03:49 +0100 Subject: [PATCH 13/18] Fix incorrect test output --- test/development/api-route-errors/index.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/development/api-route-errors/index.test.ts b/test/development/api-route-errors/index.test.ts index 09950e59d885..b086f8b10dca 100644 --- a/test/development/api-route-errors/index.test.ts +++ b/test/development/api-route-errors/index.test.ts @@ -24,7 +24,7 @@ describe('api-route-errors cli output', () => { const output = stripAnsi(next.cliOutput.slice(outputIndex)) // Location - expect(output).toContain('error - (api)/pages/api/error.js (2:8) @ error') + expect(output).toContain('error - pages/api/error.js (2:8) @ error') // Stack expect(output).toContain('pages/api/error.js:6:11') // Source code @@ -39,7 +39,7 @@ describe('api-route-errors cli output', () => { const output = stripAnsi(next.cliOutput.slice(outputIndex)) // Location expect(output).toContain( - 'error - (api)/pages/api/uncaught-exception.js (3:10) @ Timeout' + 'error - pages/api/uncaught-exception.js (3:10) @ Timeout' ) // Stack expect(output).toContain('pages/api/uncaught-exception.js:7:15') @@ -57,7 +57,7 @@ describe('api-route-errors cli output', () => { const output = stripAnsi(next.cliOutput.slice(outputIndex)) // Location expect(output).toContain( - 'error - (api)/pages/api/unhandled-rejection.js (2:17) @ unhandledRejection' + 'error - pages/api/unhandled-rejection.js (2:17) @ unhandledRejection' ) // Stack expect(output).toContain('pages/api/unhandled-rejection.js:6:20') From 096189be4965d493f65312e695f70f1de5de0191 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Thu, 24 Nov 2022 17:09:03 +0100 Subject: [PATCH 14/18] Fix incorrect test assertion --- .../middleware-dev-errors/test/index.test.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/integration/middleware-dev-errors/test/index.test.js b/test/integration/middleware-dev-errors/test/index.test.js index b4cd39e44076..ff02a97e37f1 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.default \\[as handler\\]\nerror - boom`, + `error - middleware.js \\(\\d+:\\d+\\) @ Object.default \\[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 - unhandledRejection: async boom!`, + `error - 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\nerror - test is not defined`, + `error - 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.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 - unhandledRejection: you shall see me`, + `error - 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 - uncaughtException: This file asynchronously fails while loading`, + `error - lib/unhandled.js \\(\\d+:\\d+\\) @ Timeout.eval \\[as _onTimeout\\]\nerror - uncaughtException: This file asynchronously fails while loading`, 'm' ) ) From aa4238226cc7882fd855d75018994c74d8097d02 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Thu, 24 Nov 2022 19:04:55 +0100 Subject: [PATCH 15/18] Remove auto-imported file --- test/development/acceptance-app/ReactRefreshRegression.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/development/acceptance-app/ReactRefreshRegression.test.ts b/test/development/acceptance-app/ReactRefreshRegression.test.ts index b1b6f02ff676..88c19bf0cd53 100644 --- a/test/development/acceptance-app/ReactRefreshRegression.test.ts +++ b/test/development/acceptance-app/ReactRefreshRegression.test.ts @@ -3,7 +3,6 @@ import { sandbox } from './helpers' import { createNext, FileRef } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' import path from 'path' -import browser from 'test/integration/export/test/browser' describe('ReactRefreshRegression app', () => { if (process.env.NEXT_TEST_REACT_VERSION === '^17') { From e44e89faae78c9813b09d401c4fe15425a3cf7ad Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Fri, 25 Nov 2022 10:23:53 +0100 Subject: [PATCH 16/18] Fix middleware filename in overlay --- packages/react-dev-overlay/src/middleware.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/react-dev-overlay/src/middleware.ts b/packages/react-dev-overlay/src/middleware.ts index 20cecc41f806..dfdafe6db717 100644 --- a/packages/react-dev-overlay/src/middleware.ts +++ b/packages/react-dev-overlay/src/middleware.ts @@ -198,7 +198,12 @@ export async function createOriginalStackFrame({ const filePath = path.resolve( rootDirectory, - getSourcePath(sourcePosition.source || modulePath) + getSourcePath( + // When path is loader path the modulePath is generally better. + (sourcePosition.source.includes('|') + ? modulePath + : sourcePosition.source) || modulePath + ) ) const originalFrame: StackFrame = { From 99422dacb31a526e55b8e440e943b6a71dc96882 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Fri, 25 Nov 2022 10:24:41 +0100 Subject: [PATCH 17/18] Clarify comment --- packages/react-dev-overlay/src/middleware.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-dev-overlay/src/middleware.ts b/packages/react-dev-overlay/src/middleware.ts index dfdafe6db717..cf5e79d75f24 100644 --- a/packages/react-dev-overlay/src/middleware.ts +++ b/packages/react-dev-overlay/src/middleware.ts @@ -199,7 +199,7 @@ export async function createOriginalStackFrame({ const filePath = path.resolve( rootDirectory, getSourcePath( - // When path is loader path the modulePath is generally better. + // When sourcePosition.source is the loader path the modulePath is generally better. (sourcePosition.source.includes('|') ? modulePath : sourcePosition.source) || modulePath From c228d74d214c97fec9620d9d29ef7d3172eead76 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Fri, 25 Nov 2022 10:43:29 +0100 Subject: [PATCH 18/18] Fix console sourcemap resolving --- packages/next/server/dev/next-dev-server.ts | 10 ++++++++-- packages/react-dev-overlay/src/middleware.ts | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/next/server/dev/next-dev-server.ts b/packages/next/server/dev/next-dev-server.ts index 41a20d8d2886..b19817fb19cd 100644 --- a/packages/next/server/dev/next-dev-server.ts +++ b/packages/next/server/dev/next-dev-server.ts @@ -1053,7 +1053,8 @@ export default class DevServer extends Server { ({ file }) => !file?.startsWith('eval') && !file?.includes('web/adapter') && - !file?.includes('sandbox/context') + !file?.includes('sandbox/context') && + !file?.includes('') )! if (frame.lineNumber && frame?.file) { @@ -1061,6 +1062,10 @@ export default class DevServer extends Server { /^(webpack-internal:\/\/\/|file:\/\/)/, '' ) + const modulePath = frame.file.replace( + /^(webpack-internal:\/\/\/|file:\/\/)(\(.*\)\/)?/, + '' + ) const src = getErrorSource(err as Error) const isEdgeCompiler = src === COMPILER_NAMES.edgeServer @@ -1081,7 +1086,8 @@ export default class DevServer extends Server { column: frame.column, source, frame, - modulePath: moduleId, + moduleId, + modulePath, rootDirectory: this.dir, errorMessage: err.message, serverCompilation: isEdgeCompiler diff --git a/packages/react-dev-overlay/src/middleware.ts b/packages/react-dev-overlay/src/middleware.ts index cf5e79d75f24..1fbd3508a0a5 100644 --- a/packages/react-dev-overlay/src/middleware.ts +++ b/packages/react-dev-overlay/src/middleware.ts @@ -113,11 +113,11 @@ async function findOriginalSourcePositionAndContent( } function findOriginalSourcePositionAndContentFromCompilation( - modulePath: string | undefined, + moduleId: string | undefined, importedModule: string, compilation: webpack.Compilation ) { - const module = getModuleById(modulePath, compilation) + const module = getModuleById(moduleId, compilation) return module?.buildInfo?.importLocByPath?.get(importedModule) ?? null }