Skip to content

Commit 286c14d

Browse files
authored
fix: add node internals stack frames to ignored list (vercel#73698)
1 parent 4c5fa1d commit 286c14d

File tree

5 files changed

+58
-44
lines changed

5 files changed

+58
-44
lines changed

packages/next/src/client/components/react-dev-overlay/internal/helpers/stack-frame.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,7 @@ function getOriginalStackFrame(
5858
}
5959

6060
// TODO: merge this section into ignoredList handling
61-
if (
62-
source.file === 'file://' ||
63-
source.file?.match(/^node:/) ||
64-
source.file?.match(/https?:\/\//)
65-
) {
61+
if (source.file === 'file://' || source.file?.match(/https?:\/\//)) {
6662
return Promise.resolve({
6763
error: false,
6864
reason: null,

packages/next/src/client/components/react-dev-overlay/server/middleware-turbopack.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ function shouldIgnorePath(modulePath: string): boolean {
2424
return (
2525
modulePath.includes('node_modules') ||
2626
// Only relevant for when Next.js is symlinked e.g. in the Next.js monorepo
27-
modulePath.includes('next/dist')
27+
modulePath.includes('next/dist') ||
28+
modulePath.startsWith('node:')
2829
)
2930
}
3031

@@ -39,8 +40,25 @@ export async function batchedTraceSource(
3940
? // TODO(veil): Why are the frames sent encoded?
4041
decodeURIComponent(frame.file)
4142
: undefined
43+
4244
if (!file) return
4345

46+
// For node internals they cannot traced the actual source code with project.traceSource,
47+
// we need an early return to indicate it's ignored to avoid the unknown scheme error from `project.traceSource`.
48+
if (file.startsWith('node:')) {
49+
return {
50+
frame: {
51+
file,
52+
lineNumber: frame.line ?? 0,
53+
column: frame.column ?? 0,
54+
methodName: frame.methodName ?? '<unknown>',
55+
ignored: true,
56+
arguments: [],
57+
},
58+
source: null,
59+
}
60+
}
61+
4462
const currentDirectoryFileUrl = pathToFileURL(process.cwd()).href
4563

4664
const sourceFrame = await project.traceSource(frame, currentDirectoryFileUrl)
@@ -51,7 +69,7 @@ export async function batchedTraceSource(
5169
lineNumber: frame.line ?? 0,
5270
column: frame.column ?? 0,
5371
methodName: frame.methodName ?? '<unknown>',
54-
ignored: shouldIgnorePath(frame.file),
72+
ignored: shouldIgnorePath(file),
5573
arguments: [],
5674
},
5775
source: null,
@@ -60,6 +78,7 @@ export async function batchedTraceSource(
6078

6179
let source = null
6280
const originalFile = sourceFrame.originalFile
81+
6382
// Don't look up source for node_modules or internals. These can often be large bundled files.
6483
const ignored =
6584
shouldIgnorePath(originalFile ?? sourceFrame.file) ||
@@ -226,7 +245,11 @@ async function nativeTraceSource(
226245
const sourceIndex = applicableSourceMap.sources.indexOf(
227246
originalPosition.source!
228247
)
229-
ignored = applicableSourceMap.ignoreList?.includes(sourceIndex) ?? false
248+
ignored =
249+
applicableSourceMap.ignoreList?.includes(sourceIndex) ??
250+
// When sourcemap is not available, fallback to checking `frame.file`.
251+
// e.g. In pages router, nextjs server code is not bundled into the page.
252+
shouldIgnorePath(frame.file)
230253
}
231254

232255
const originalStackFrame: IgnorableStackFrame = {

packages/next/src/client/components/react-dev-overlay/server/middleware-webpack.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ function shouldIgnorePath(modulePath: string): boolean {
3030
return (
3131
modulePath.includes('node_modules') ||
3232
// Only relevant for when Next.js is symlinked e.g. in the Next.js monorepo
33-
modulePath.includes('next/dist')
33+
modulePath.includes('next/dist') ||
34+
modulePath.startsWith('node:')
3435
)
3536
}
3637

test/development/acceptance/ReactRefreshLogBox.test.ts

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { createSandbox } from 'development-sandbox'
33
import { FileRef, nextTestSetup } from 'e2e-utils'
44
import {
55
describeVariants as describe,
6-
getRedboxCallStack,
6+
getStackFramesContent,
77
toggleCollapseCallStackFrames,
88
} from 'next-test-utils'
99
import path from 'path'
@@ -771,7 +771,8 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
771771
expect(callStackFrames.length).toBeGreaterThan(9)
772772
})
773773

774-
test('should show anonymous frames in stack trace', async () => {
774+
// TODO: hide the anonymous frames between 2 ignored frames
775+
test('should show anonymous frames from stack trace', async () => {
775776
await using sandbox = await createSandbox(
776777
next,
777778
new Map([
@@ -786,42 +787,49 @@ describe.each(['default', 'turbo'])('ReactRefreshLogBox %s', () => {
786787
],
787788
])
788789
)
790+
789791
const { session, browser } = sandbox
792+
790793
await session.assertHasRedbox()
791-
const texts = await getRedboxCallStack(browser)
792-
expect(texts).toMatchSnapshot()
794+
795+
const stack = await getStackFramesContent(browser)
796+
expect(stack).toMatchInlineSnapshot(`
797+
"at Array.map ()
798+
at Page (pages/index.js (2:13))"
799+
`)
793800
})
794801

795-
test('should hide unrelated frames in stack trace with node:internal calls', async () => {
802+
test('should collapse nodejs internal stack frames from stack trace', async () => {
796803
await using sandbox = await createSandbox(
797804
next,
798805
new Map([
799806
[
800807
'pages/index.js',
801-
// Node.js will throw an error about the invalid URL since it happens server-side
802808
outdent`
803-
export default function Page() {}
804-
805-
export function getServerSideProps() {
806-
new URL("/", "invalid");
807-
return { props: {} };
808-
}`,
809+
export default function Page() {}
810+
811+
function createURL() {
812+
new URL("/", "invalid")
813+
}
814+
815+
export function getServerSideProps() {
816+
createURL()
817+
return { props: {} }
818+
}`,
809819
],
810820
])
811821
)
822+
812823
const { session, browser } = sandbox
813824
await session.assertHasRedbox()
814825

815-
// Should still show the errored line in source code
816-
const source = await session.getRedboxSource()
817-
expect(source).toContain('pages/index.js')
818-
expect(source).toContain(`new URL("/", "invalid")`)
819-
820-
const callStackFrames = await browser.elementsByCss(
821-
'[data-nextjs-call-stack-frame]'
826+
const stack = await getStackFramesContent(browser)
827+
expect(stack).toMatchInlineSnapshot(
828+
`"at getServerSideProps (pages/index.js (8:3))"`
822829
)
823-
const texts = await Promise.all(callStackFrames.map((f) => f.innerText()))
824830

825-
expect(texts.filter((t) => t.includes('node:internal'))).toHaveLength(0)
831+
await toggleCollapseCallStackFrames(browser)
832+
const stackCollapsed = await getStackFramesContent(browser)
833+
expect(stackCollapsed).toContain('at new URL ()')
826834
})
827835
})

test/development/acceptance/__snapshots__/ReactRefreshLogBox.test.ts.snap

Lines changed: 0 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)