Improve RSC compiler error in external module (#46953)

When the RSC compiler error was caused by an external package, make the
error message display which import caused the error. Also don't show
node_module files in the import trace.

Continuation of https://github.com/vercel/next.js/pull/45484

Before

![image](https://user-images.githubusercontent.com/25056922/224032476-6811a1d5-d690-48be-9602-781f459edc70.png)

After

![image](https://user-images.githubusercontent.com/25056922/224032177-2d0b2977-098f-46bd-8e30-9e6bc21b9153.png)

Updates the format of the files, from `app/page.js` to `./app/page.js`
to align it with other import traces.

![image](https://user-images.githubusercontent.com/25056922/224030420-1d3ff0ba-5747-4ed3-8b0b-9c4deace54ea.png)


Closes NEXT-523
This commit is contained in:
Hannes Bornö 2023-03-09 21:27:57 +01:00 committed by GitHub
parent 09d21d6e99
commit 9a41ba9ac4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 137 additions and 73 deletions

View file

@ -1,18 +1,59 @@
import type { webpack } from 'next/dist/compiled/webpack/webpack'
import loaderUtils from 'next/dist/compiled/loader-utils3'
import { relative } from 'path'
export function formatModule(compiler: webpack.Compiler, module: any) {
return relative(compiler.context, module.resource).replace(/\?.+$/, '')
function formatModule(compiler: webpack.Compiler, module: any) {
const relativePath = relative(compiler.context, module.resource).replace(
/\?.+$/,
''
)
return loaderUtils.isUrlRequest(relativePath)
? loaderUtils.urlToRequest(relativePath)
: relativePath
}
export function getImportTraceForOverlay(
export function formatModuleTrace(
compiler: webpack.Compiler,
moduleTrace: any[]
) {
return moduleTrace
.map((m) => (m.resource ? ' ' + formatModule(compiler, m) : ''))
.filter(Boolean)
.join('\n')
let importTrace: string[] = []
let firstExternalModule: any
for (let i = moduleTrace.length - 1; i >= 0; i--) {
const mod = moduleTrace[i]
if (!mod.resource) continue
if (!mod.resource.includes('node_modules/')) {
importTrace.unshift(formatModule(compiler, mod))
} else {
firstExternalModule = mod
break
}
}
let invalidImportMessage = ''
if (firstExternalModule) {
const firstExternalPackageName =
firstExternalModule.resourceResolveData?.descriptionFileData?.name
if (firstExternalPackageName === 'styled-jsx') {
invalidImportMessage += `\n\nThe error was caused by using 'styled-jsx' in '${importTrace[0]}'. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.`
} else {
let formattedExternalFile =
firstExternalModule.resource.split('node_modules')
formattedExternalFile =
formattedExternalFile[formattedExternalFile.length - 1]
invalidImportMessage += `\n\nThe error was caused by importing '${formattedExternalFile.slice(
1
)}' in '${importTrace[0]}'.`
}
}
return {
lastInternalFileName: importTrace[0],
invalidImportMessage,
formattedModuleTrace: `${importTrace.map((mod) => ' ' + mod).join('\n')}`,
}
}
export function getModuleTrace(

View file

@ -1,5 +1,5 @@
import type { webpack } from 'next/dist/compiled/webpack/webpack'
import { formatModule, getModuleTrace } from './getModuleTrace'
import { formatModuleTrace, getModuleTrace } from './getModuleTrace'
import { SimpleWebpackError } from './simpleWebpackError'
export function getNextInvalidImportError(
@ -18,49 +18,15 @@ export function getNextInvalidImportError(
}
const { moduleTrace } = getModuleTrace(module, compilation, compiler)
let importTrace: string[] = []
let firstExternalModule: any
for (let i = moduleTrace.length - 1; i >= 0; i--) {
const mod = moduleTrace[i]
if (!mod.resource) continue
if (!mod.resource.includes('node_modules/')) {
importTrace.unshift(formatModule(compiler, mod))
} else {
firstExternalModule = mod
break
}
}
let invalidImportMessage = ''
if (firstExternalModule) {
const firstExternalPackageName =
firstExternalModule.resourceResolveData?.descriptionFileData?.name
if (firstExternalPackageName === 'styled-jsx') {
invalidImportMessage += `\n\nThe error was caused by using 'styled-jsx' in '${importTrace[0]}'. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.`
} else {
let formattedExternalFile =
firstExternalModule.resource.split('node_modules')
formattedExternalFile =
formattedExternalFile[formattedExternalFile.length - 1]
invalidImportMessage += `\n\nThe error was caused by importing '${formattedExternalFile.slice(
1
)}' in '${importTrace[0]}'.`
}
}
const { formattedModuleTrace, lastInternalFileName, invalidImportMessage } =
formatModuleTrace(compiler, moduleTrace)
return new SimpleWebpackError(
importTrace[0],
lastInternalFileName,
err.message +
invalidImportMessage +
(importTrace.length > 0
? `\n\nImport trace for requested module:\n${importTrace
.map((mod) => ' ' + mod)
.join('\n')}`
: '')
'\n\nImport trace for requested module:\n' +
formattedModuleTrace
)
} catch {
return false

View file

@ -1,6 +1,6 @@
import type { webpack } from 'next/dist/compiled/webpack/webpack'
import { getModuleTrace, getImportTraceForOverlay } from './getModuleTrace'
import { getModuleTrace, formatModuleTrace } from './getModuleTrace'
import { SimpleWebpackError } from './simpleWebpackError'
function formatRSCErrorMessage(
@ -141,12 +141,16 @@ export function getRscError(
fileName
)
const { formattedModuleTrace, lastInternalFileName, invalidImportMessage } =
formatModuleTrace(compiler, moduleTrace)
const error = new SimpleWebpackError(
fileName,
lastInternalFileName,
'ReactServerComponentsError:\n' +
formattedError[0] +
invalidImportMessage +
formattedError[1] +
getImportTraceForOverlay(compiler, moduleTrace)
formattedModuleTrace
)
// Delete the stack because it's created here.

View file

@ -67,15 +67,15 @@ createNextDescribe(
expect(await session.hasRedbox(true)).toBe(true)
expect(await session.getRedboxSource()).toMatchInlineSnapshot(`
"app/comp2.js
"./app/comp2.js
'client-only' cannot be imported from a Server Component module. It should only be used from a Client Component.
The error was caused by using 'styled-jsx' in 'app/comp2.js'. It only works in a Client Component but none of its parents are marked with \\"use client\\", so they're Server Components by default.
The error was caused by using 'styled-jsx' in './app/comp2.js'. It only works in a Client Component but none of its parents are marked with \\"use client\\", so they're Server Components by default.
Import trace for requested module:
app/comp2.js
app/comp1.js
app/page.js"
./app/comp2.js
./app/comp1.js
./app/page.js"
`)
await cleanup()
@ -142,15 +142,15 @@ createNextDescribe(
expect(await session.hasRedbox(true)).toBe(true)
expect(await session.getRedboxSource()).toMatchInlineSnapshot(`
"app/comp2.js
"./app/comp2.js
'client-only' cannot be imported from a Server Component module. It should only be used from a Client Component.
The error was caused by importing 'client-only-package/index.js' in 'app/comp2.js'.
The error was caused by importing 'client-only-package/index.js' in './app/comp2.js'.
Import trace for requested module:
app/comp2.js
app/comp1.js
app/page.js"
./app/comp2.js
./app/comp1.js
./app/page.js"
`)
await cleanup()
@ -215,15 +215,15 @@ createNextDescribe(
expect(await session.hasRedbox(true)).toBe(true)
expect(await session.getRedboxSource()).toMatchInlineSnapshot(`
"app/comp2.js
"./app/comp2.js
'server-only' cannot be imported from a Client Component module. It should only be used from a Server Component.
The error was caused by importing 'server-only-package/index.js' in 'app/comp2.js'.
The error was caused by importing 'server-only-package/index.js' in './app/comp2.js'.
Import trace for requested module:
app/comp2.js
app/comp1.js
app/page.js"
./app/comp2.js
./app/comp1.js
./app/page.js"
`)
await cleanup()

View file

@ -284,7 +284,7 @@ createNextDescribe(
\`----
Import path:
app/server-with-errors/error-file/error.js"
./app/server-with-errors/error-file/error.js"
`)
await cleanup()
@ -314,7 +314,7 @@ createNextDescribe(
\`----
Import path:
app/server-with-errors/error-file/error.js"
./app/server-with-errors/error-file/error.js"
`)
await cleanup()
@ -363,8 +363,8 @@ createNextDescribe(
\`----
Maybe one of these should be marked as a client entry with \\"use client\\":
app/editor-links/component.js
app/editor-links/page.js"
./app/editor-links/component.js
./app/editor-links/page.js"
`)
await browser.waitForElementByCss('[data-with-open-in-editor-link]')
@ -413,5 +413,58 @@ createNextDescribe(
await cleanup()
})
it('should show which import caused an error in node_modules', async () => {
const { session, cleanup } = await sandbox(
next,
new Map([
[
'node_modules/client-package/module2.js',
"import { useState } from 'react'",
],
['node_modules/client-package/module1.js', "import './module2.js'"],
['node_modules/client-package/index.js', "import './module1.js'"],
[
'node_modules/client-package/package.json',
`
{
"name": "client-package",
"version": "0.0.1"
}
`,
],
['app/Component.js', "import 'client-package'"],
[
'app/page.js',
`
import './Component.js'
export default function Page() {
return <p>Hello world</p>
}`,
],
])
)
expect(await session.hasRedbox(true)).toBe(true)
expect(await session.getRedboxSource()).toMatchInlineSnapshot(`
"./app/Component.js
ReactServerComponentsError:
You're importing a component that needs useState. It only works in a Client Component but none of its parents are marked with \\"use client\\", so they're Server Components by default.
,----
1 | import { useState } from 'react'
: ^^^^^^^^
\`----
The error was caused by importing 'client-package/index.js' in './app/Component.js'.
Maybe one of these should be marked as a client entry with \\"use client\\":
./app/Component.js
./app/page.js"
`)
await cleanup()
})
}
)

View file

@ -64,8 +64,8 @@ createNextDescribe(
\`----
Import trace for requested module:
components/Comp.js
pages/index.js"
./components/Comp.js
./pages/index.js"
`)
await cleanup()
@ -106,8 +106,8 @@ createNextDescribe(
\`----
Import trace for requested module:
components/Comp.js
pages/index.js"
./components/Comp.js
./pages/index.js"
`)
await cleanup()