Skip to content

Commit df102ae

Browse files
authored
[Turbopack] dedupe build errors (#85062)
When we run the RSC validation transform, we might take multiple passes on the same file if the edge runtime is used: once at the RSC layer, another in the edge RSC layer. This means we'll have reported the error twice for the same file. This dedupes errors by tracking the seen, formatted errors and not re-showing them if they've already been tracked.
1 parent 98adcb3 commit df102ae

File tree

18 files changed

+134
-5
lines changed

18 files changed

+134
-5
lines changed

packages/next/src/build/utils.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,21 +200,41 @@ export function printBuildErrors(
200200
// Issues that are warnings but should not affect the running of the build
201201
const topLevelWarnings = []
202202

203+
// Track seen formatted error messages to avoid duplicates
204+
const seenFatalIssues = new Set<string>()
205+
const seenErrors = new Set<string>()
206+
const seenWarnings = new Set<string>()
207+
203208
for (const issue of entrypoints.issues) {
204209
// We only want to completely shut down the server
205210
if (issue.severity === 'fatal' || issue.severity === 'bug') {
206-
topLevelFatalIssues.push(formatIssue(issue))
211+
const formatted = formatIssue(issue)
212+
if (!seenFatalIssues.has(formatted)) {
213+
seenFatalIssues.add(formatted)
214+
topLevelFatalIssues.push(formatted)
215+
}
207216
} else if (isRelevantWarning(issue)) {
208-
topLevelWarnings.push(formatIssue(issue))
217+
const formatted = formatIssue(issue)
218+
if (!seenWarnings.has(formatted)) {
219+
seenWarnings.add(formatted)
220+
topLevelWarnings.push(formatted)
221+
}
209222
} else if (issue.severity === 'error') {
223+
const formatted = formatIssue(issue)
210224
if (isDev) {
211225
// We want to treat errors as recoverable in development
212226
// so that we can show the errors in the site and allow users
213227
// to respond to the errors when necessary. In production builds
214228
// though we want to error out and stop the build process.
215-
topLevelErrors.push(formatIssue(issue))
229+
if (!seenErrors.has(formatted)) {
230+
seenErrors.add(formatted)
231+
topLevelErrors.push(formatted)
232+
}
216233
} else {
217-
topLevelFatalIssues.push(formatIssue(issue))
234+
if (!seenFatalIssues.has(formatted)) {
235+
seenFatalIssues.add(formatted)
236+
topLevelFatalIssues.push(formatted)
237+
}
218238
}
219239
}
220240
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { nextTestSetup } from 'e2e-utils'
2+
3+
// Only Turbopack runs the transform on the layout once in edge and non-edge contexts
4+
// so we only test this on Turbopack
5+
import {
6+
assertHasRedbox,
7+
getRedboxDescription,
8+
getRedboxSource,
9+
} from 'next-test-utils'
10+
;(process.env.IS_TURBOPACK_TEST ? describe : describe.skip)(
11+
'cache-components-edge-deduplication',
12+
() => {
13+
const { next, skipped, isNextDev } = nextTestSetup({
14+
files: __dirname + '/fixtures/edge-deduplication',
15+
skipStart: true,
16+
skipDeployment: true,
17+
})
18+
19+
if (skipped) {
20+
return
21+
}
22+
23+
it('should not duplicate errors when layout is compiled for both edge and non-edge contexts', async () => {
24+
try {
25+
await next.start()
26+
} catch {
27+
// we expect the build to fail
28+
}
29+
30+
if (isNextDev) {
31+
const browser = await next.browser('/edge-with-layout/edge')
32+
assertHasRedbox(browser)
33+
const redbox = {
34+
description: await getRedboxDescription(browser),
35+
source: await getRedboxSource(browser),
36+
}
37+
expect(redbox.description).toMatchInlineSnapshot(
38+
`"Ecmascript file had an error"`
39+
)
40+
expect(redbox.source).toMatchInlineSnapshot(`
41+
"./app/edge-with-layout/edge/page.tsx (1:14)
42+
Ecmascript file had an error
43+
> 1 | export const runtime = 'edge'
44+
| ^^^^^^^
45+
2 |
46+
3 | export default function Page() {
47+
4 | return <div>Test page under app/</div>
48+
49+
Route segment config "runtime" is not compatible with \`nextConfig.cacheComponents\`. Please remove it."
50+
`)
51+
// Count occurrences of the layout error at the specific location
52+
const layoutErrorMatches = next.cliOutput.match(
53+
/\.\/app\/edge-with-layout\/layout\.tsx:1:14/g
54+
)
55+
// We don't show an error stack, just the individual error messages at each location
56+
expect(layoutErrorMatches?.length).toBe(1)
57+
} else {
58+
// Check that both the layout and edge page errors appear
59+
expect(next.cliOutput).toContain('./app/edge-with-layout/layout.tsx')
60+
expect(next.cliOutput).toContain('./app/edge-with-layout/edge/page.tsx')
61+
expect(next.cliOutput).toContain(
62+
'"dynamic" is not compatible with `nextConfig.cacheComponents`. Please remove it.'
63+
)
64+
expect(next.cliOutput).toContain(
65+
'"runtime" is not compatible with `nextConfig.cacheComponents`. Please remove it.'
66+
)
67+
// Count occurrences of the layout error at the specific location
68+
const layoutErrorMatches = next.cliOutput.match(
69+
/\.\/app\/layout\.tsx:1:14/g
70+
)
71+
72+
// Should appear exactly twice: once in the formatted error message, once in the stack trace
73+
expect(layoutErrorMatches?.length).toBe(2)
74+
}
75+
})
76+
}
77+
)

test/e2e/app-dir/cache-components-segment-configs/cache-components-segment-configs.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88

99
describe('cache-components-segment-configs', () => {
1010
const { next, skipped, isNextDev, isTurbopack } = nextTestSetup({
11-
files: __dirname,
11+
files: __dirname + '/fixtures/default',
1212
skipStart: true,
1313
skipDeployment: true,
1414
})

0 commit comments

Comments
 (0)