mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
c0e749f609
### What? Stabilizes the RSC poisoned-import error-overlay test for `proxy.js` without weakening or retrying its redbox assertion. ### Why? An initially broken proxy can emit the expected build error and then force a startup full reload. That reload can clear the overlay before the test begins observing it, causing an intermittent `Expected Redbox but found no visible one` failure even though validation worked. ### How? The proxy parameter now starts from a valid module and introduces the poisoned import only after the sandbox page has hydrated. This moves the assertion onto the live HMR path and removes the startup-reload race. Middleware and instrumentation retain their initial-compile setup because their error overlays are reliable there and instrumentation does not reliably surface this edit through Turbopack HMR. ### Verification - `pnpm test-dev-webpack test/development/acceptance-app/rsc-build-errors-poisoned-imports.test.ts` - `pnpm test-dev-turbo test/development/acceptance-app/rsc-build-errors-poisoned-imports.test.ts` - Proxy case repeated 5 times with Webpack and 3 times with Turbopack - Prettier, ESLint, and `git diff --check` <!-- NEXT_JS_LLM --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
153 lines
5.7 KiB
TypeScript
153 lines
5.7 KiB
TypeScript
import { createSandbox } from 'development-sandbox'
|
|
import { outdent } from 'outdent'
|
|
import { runRscBuildErrorsTests } from './rsc-build-errors.util'
|
|
|
|
runRscBuildErrorsTests(({ next, isTurbopack }) => {
|
|
it('should allow to use and handle rsc poisoning client-only', async () => {
|
|
await using sandbox = await createSandbox(
|
|
next,
|
|
undefined,
|
|
'/server-with-errors/client-only-in-server'
|
|
)
|
|
const { session } = sandbox
|
|
const file =
|
|
'app/server-with-errors/client-only-in-server/client-only-lib.js'
|
|
const content = await next.readFile(file)
|
|
const uncomment = content.replace(
|
|
"// import 'client-only'",
|
|
"import 'client-only'"
|
|
)
|
|
await next.patchFile(file, uncomment)
|
|
|
|
await session.waitForRedbox()
|
|
if (isTurbopack) {
|
|
// TODO: fix the issue ordering.
|
|
// turbopack emits the resolve issue first instead of the transform issue.
|
|
expect(await session.getRedboxSource()).toMatchInlineSnapshot(`
|
|
"./app/server-with-errors/client-only-in-server/client-only-lib.js (1:1)
|
|
Error: You're importing a component that imports client-only. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.
|
|
Learn more: https://nextjs.org/docs/app/building-your-application/rendering
|
|
> 1 | import 'client-only'
|
|
| ^^^^^^^^^^^^^^^^^^^^
|
|
2 |
|
|
3 | export default function ClientOnlyLib() {
|
|
4 | return 'client-only-lib'
|
|
|
|
Ecmascript file had an error
|
|
|
|
Import trace:
|
|
Server Component:
|
|
./app/server-with-errors/client-only-in-server/client-only-lib.js
|
|
./app/server-with-errors/client-only-in-server/page.js"
|
|
`)
|
|
} else {
|
|
expect(await session.getRedboxSource()).toInclude(
|
|
`You're importing a component that imports client-only. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.`
|
|
)
|
|
}
|
|
})
|
|
|
|
it('should allow to use and handle rsc poisoning server-only', async () => {
|
|
await using sandbox = await createSandbox(
|
|
next,
|
|
undefined,
|
|
'/client-with-errors/server-only-in-client'
|
|
)
|
|
const { session } = sandbox
|
|
const file =
|
|
'app/client-with-errors/server-only-in-client/server-only-lib.js'
|
|
const content = await next.readFile(file)
|
|
const uncomment = content.replace(
|
|
"// import 'server-only'",
|
|
"import 'server-only'"
|
|
)
|
|
|
|
await session.patch(file, uncomment)
|
|
|
|
await session.waitForRedbox()
|
|
expect(await session.getRedboxSource()).toInclude(
|
|
`You're importing a module that depends on "server-only" into a React Client Component module. This API is only available in Server Components but one of its parents is marked with "use client", so this module is also a Client Component.`
|
|
)
|
|
})
|
|
|
|
it('should error when catchError from next/error is used in a server component', async () => {
|
|
await using sandbox = await createSandbox(
|
|
next,
|
|
new Map([
|
|
[
|
|
'app/page.js',
|
|
outdent`
|
|
import { catchError } from 'next/error'
|
|
|
|
export default function Page() {
|
|
return 'Hello world'
|
|
}
|
|
`,
|
|
],
|
|
])
|
|
)
|
|
|
|
const { session } = sandbox
|
|
await session.waitForRedbox()
|
|
expect(await session.getRedboxSource()).toInclude(
|
|
'You\'re importing a module that depends on `catchError` into a React Server Component module. This API is only available in Client Components. To fix, mark the file (or its parent) with the `"use client"` directive.'
|
|
)
|
|
})
|
|
|
|
// In Webpack, proxy runs in the Node.js server compiler, which is also
|
|
// invalidated when the app route is added on demand. If the initial proxy
|
|
// error reaches the browser before that follow-up build, the HMR client sees
|
|
// an update after a runtime error and reloads, clearing the overlay. Defer
|
|
// that case until after hydration. Turbopack does not have this compiler
|
|
// invalidation race, so its proxy case covers both initial and HMR errors.
|
|
test.each([
|
|
['middleware.js', 'export function middleware() {}'],
|
|
['proxy.js', 'export function proxy() {}'],
|
|
['instrumentation.js', 'export function register() {}'],
|
|
])(
|
|
'should error when catchError from next/error is imported in %s',
|
|
async (entryFile, exportCode) => {
|
|
const isProxy = entryFile === 'proxy.js'
|
|
const deferPoisonedImport = !isTurbopack && isProxy
|
|
const entryContent = outdent`
|
|
import { catchError } from 'next/error'
|
|
${exportCode}
|
|
`
|
|
await using sandbox = await createSandbox(
|
|
next,
|
|
new Map([
|
|
[
|
|
'app/page.js',
|
|
outdent`
|
|
export default function Page() {
|
|
return 'Hello world'
|
|
}
|
|
`,
|
|
],
|
|
[entryFile, deferPoisonedImport ? exportCode : entryContent],
|
|
])
|
|
)
|
|
|
|
const { session } = sandbox
|
|
if (deferPoisonedImport) {
|
|
await session.write(entryFile, entryContent)
|
|
}
|
|
|
|
const expectPoisonedImportRedbox = async () => {
|
|
await session.waitForRedbox()
|
|
expect(await session.getRedboxSource()).toInclude(
|
|
'You\'re importing a module that depends on `catchError` into a React Server Component module. This API is only available in Client Components. To fix, mark the file (or its parent) with the `"use client"` directive.'
|
|
)
|
|
}
|
|
await expectPoisonedImportRedbox()
|
|
|
|
if (isTurbopack && isProxy) {
|
|
await session.patch(entryFile, exportCode)
|
|
await session.waitForNoRedbox()
|
|
await session.write(entryFile, entryContent)
|
|
await expectPoisonedImportRedbox()
|
|
}
|
|
}
|
|
)
|
|
})
|