hmr error improvements (#30616)

Co-authored-by: Hannes Bornö <hannes.borno@vercel.com>
Co-authored-by: Hannes Bornö <borno.hannes@gmail.com>
This commit is contained in:
Tobias Koppers 2023-01-06 20:35:16 +01:00 committed by GitHub
parent 3c87e1b52a
commit 3b91ca98a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 231 additions and 40 deletions

View file

@ -36,3 +36,4 @@ bench/nested-deps/pages/**
bench/nested-deps/components/** bench/nested-deps/components/**
packages/next-bundle-analyzer/index.d.ts packages/next-bundle-analyzer/index.d.ts
examples/with-typescript-graphql/lib/gql/ examples/with-typescript-graphql/lib/gql/
test/development/basic/hmr/components/parse-error.js

View file

@ -22,6 +22,7 @@ packages/next-codemod/**/*.d.ts
packages/next-env/**/*.d.ts packages/next-env/**/*.d.ts
test-timings.json test-timings.json
test/**/out/** test/**/out/**
test/development/basic/hmr/components/parse-error.js
bench/nested-deps/pages/**/* bench/nested-deps/pages/**/*
bench/nested-deps/components/**/* bench/nested-deps/components/**/*
pnpm-lock.yaml pnpm-lock.yaml

View file

@ -7,6 +7,7 @@ packages/next/bundles/webpack/packages/*.runtime.js
lerna.json lerna.json
packages/next-codemod/transforms/__testfixtures__/**/* packages/next-codemod/transforms/__testfixtures__/**/*
packages/next-codemod/transforms/__tests__/**/* packages/next-codemod/transforms/__tests__/**/*
test/development/basic/hmr/components/parse-error.js
pnpm-lock.yaml pnpm-lock.yaml
.github/actions/issue-validator/index.mjs .github/actions/issue-validator/index.mjs
**/convex/_generated/** **/convex/_generated/**

View file

@ -64,7 +64,7 @@ function formatMessage(
(message.file ? stripAnsi(message.file) + '\n' : '') + (message.file ? stripAnsi(message.file) + '\n' : '') +
body + body +
(message.details && verbose ? '\n' + message.details : '') + (message.details && verbose ? '\n' + message.details : '') +
(filteredModuleTrace && filteredModuleTrace.length && verbose (filteredModuleTrace && filteredModuleTrace.length
? (importTraceNote || '\n\nImport trace for requested module:') + ? (importTraceNote || '\n\nImport trace for requested module:') +
filteredModuleTrace filteredModuleTrace
.map((trace: any) => `\n${trace.moduleName}`) .map((trace: any) => `\n${trace.moduleName}`)
@ -161,6 +161,12 @@ function formatMessage(
'' ''
) // at ... ...:x:y ) // at ... ...:x:y
message = message.replace(/^\s*at\s<anonymous>(\n|$)/gm, '') // at <anonymous> message = message.replace(/^\s*at\s<anonymous>(\n|$)/gm, '') // at <anonymous>
message = message.replace(
/File was processed with these loaders:\n(.+[\\/](next[\\/]dist[\\/].+|@next[\\/]react-refresh-utils[\\/]loader)\.js\n)*You may need an additional loader to handle the result of these loaders.\n/g,
''
)
lines = message.split('\n') lines = message.split('\n')
} }

View file

@ -192,6 +192,7 @@ export class WebpackHotMiddleware {
hash: true, hash: true,
warnings: true, warnings: true,
errors: true, errors: true,
moduleTrace: true,
}) })
this.eventStream.publish({ this.eventStream.publish({

View file

@ -111,17 +111,16 @@ const matchNextPageBundleRequest = getPathMatch(
'/_next/static/chunks/pages/:path*.js(\\.map|)' '/_next/static/chunks/pages/:path*.js(\\.map|)'
) )
// Recursively look up the issuer till it ends up at the root // Iteratively look up the issuer till it ends up at the root
function findEntryModule( function findEntryModule(
compilation: webpack.Compilation, module: webpack.Module,
issuerModule: any compilation: webpack.Compilation
): any { ): any {
const issuer = compilation.moduleGraph.getIssuer(issuerModule) for (;;) {
if (issuer) { const issuer = compilation.moduleGraph.getIssuer(module)
return findEntryModule(compilation, issuer) if (!issuer) return module
module = issuer
} }
return issuerModule
} }
function erroredPages(compilation: webpack.Compilation) { function erroredPages(compilation: webpack.Compilation) {
@ -131,7 +130,7 @@ function erroredPages(compilation: webpack.Compilation) {
continue continue
} }
const entryModule = findEntryModule(compilation, error.module) const entryModule = findEntryModule(error.module, compilation)
const { name } = entryModule const { name } = entryModule
if (!name) { if (!name) {
continue continue

View file

@ -7,7 +7,12 @@ SassError: Expected expression.
1 │ .button { font-size: :5px; } 1 │ .button { font-size: :5px; }
│ ^ │ ^
index.module.scss 1:22 root stylesheet" index.module.scss 1:22 root stylesheet
Import trace for requested module:
./index.module.scss
./index.js
./app/page.js"
`; `;
exports[`ReactRefreshLogBox app scss syntax errors 2`] = ` exports[`ReactRefreshLogBox app scss syntax errors 2`] = `

View file

@ -88,7 +88,11 @@ Error:
Caused by: Caused by:
0: failed to process input file 0: failed to process input file
1: Syntax Error" 1: Syntax Error
Import trace for requested module:
./index.js
./app/page.js"
`; `;
exports[`ReactRefreshLogBox app module init error not shown 1`] = ` exports[`ReactRefreshLogBox app module init error not shown 1`] = `
@ -165,7 +169,11 @@ Error:
Caused by: Caused by:
0: failed to process input file 0: failed to process input file
1: error was recoverable, but proceeding would result in wrong codegen 1: error was recoverable, but proceeding would result in wrong codegen
2: Syntax Error" 2: Syntax Error
Import trace for requested module:
./index.js
./app/page.js"
`; `;
exports[`ReactRefreshLogBox app syntax > runtime error 3`] = ` exports[`ReactRefreshLogBox app syntax > runtime error 3`] = `
@ -183,7 +191,11 @@ Error:
Caused by: Caused by:
0: failed to process input file 0: failed to process input file
1: error was recoverable, but proceeding would result in wrong codegen 1: error was recoverable, but proceeding would result in wrong codegen
2: Syntax Error" 2: Syntax Error
Import trace for requested module:
./index.js
./app/page.js"
`; `;
exports[`ReactRefreshLogBox app unterminated JSX 1`] = ` exports[`ReactRefreshLogBox app unterminated JSX 1`] = `
@ -209,5 +221,9 @@ Error:
Caused by: Caused by:
0: failed to process input file 0: failed to process input file
1: Syntax Error" 1: Syntax Error
Import trace for requested module:
./index.js
./app/page.js"
`; `;

View file

@ -76,7 +76,10 @@ Error:
Caused by: Caused by:
0: failed to process input file 0: failed to process input file
1: Syntax Error" 1: Syntax Error
Import trace for requested module:
./index.js"
`; `;
exports[`ReactRefreshLogBox module init error not shown 1`] = ` exports[`ReactRefreshLogBox module init error not shown 1`] = `
@ -153,7 +156,10 @@ Error:
Caused by: Caused by:
0: failed to process input file 0: failed to process input file
1: error was recoverable, but proceeding would result in wrong codegen 1: error was recoverable, but proceeding would result in wrong codegen
2: Syntax Error" 2: Syntax Error
Import trace for requested module:
./index.js"
`; `;
exports[`ReactRefreshLogBox syntax > runtime error 3`] = ` exports[`ReactRefreshLogBox syntax > runtime error 3`] = `
@ -171,7 +177,10 @@ Error:
Caused by: Caused by:
0: failed to process input file 0: failed to process input file
1: error was recoverable, but proceeding would result in wrong codegen 1: error was recoverable, but proceeding would result in wrong codegen
2: Syntax Error" 2: Syntax Error
Import trace for requested module:
./index.js"
`; `;
exports[`ReactRefreshLogBox unterminated JSX 1`] = ` exports[`ReactRefreshLogBox unterminated JSX 1`] = `
@ -197,5 +206,8 @@ Error:
Caused by: Caused by:
0: failed to process input file 0: failed to process input file
1: Syntax Error" 1: Syntax Error
Import trace for requested module:
./index.js"
`; `;

View file

@ -667,6 +667,127 @@ describe('basic HMR', () => {
} }
}) })
it('should recover after webpack parse error in an imported file', async () => {
let browser
const aboutPage = join('pages', 'hmr', 'about8.js')
const aboutContent = await next.readFile(aboutPage)
try {
browser = await webdriver(next.appPort, '/hmr/about8')
await check(() => getBrowserBodyText(browser), /This is the about page/)
await next.patchFile(
aboutPage,
aboutContent.replace(
'export default',
'import "../../components/parse-error.xyz"\nexport default'
)
)
expect(await hasRedbox(browser)).toBe(true)
expect(await getRedboxHeader(browser)).toMatchInlineSnapshot(
`"Failed to compile"`
)
expect(await getRedboxSource(browser)).toMatchInlineSnapshot(`
"./components/parse-error.xyz
Module parse failed: Unexpected token (3:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| This
| is
> }}}
| invalid
| js
Import trace for requested module:
./components/parse-error.xyz"
`)
await next.patchFile(aboutPage, aboutContent)
await check(() => getBrowserBodyText(browser), /This is the about page/)
expect(await hasRedbox(browser, false)).toBe(false)
} catch (err) {
await next.patchFile(aboutPage, aboutContent)
if (browser) {
await check(
() => getBrowserBodyText(browser),
/This is the about page/
)
}
throw err
} finally {
if (browser) {
await browser.close()
}
}
})
it('should recover after loader parse error in an imported file', async () => {
let browser
const aboutPage = join('pages', 'hmr', 'about9.js')
const aboutContent = await next.readFile(aboutPage)
try {
browser = await webdriver(next.appPort, '/hmr/about9')
await check(() => getBrowserBodyText(browser), /This is the about page/)
await next.patchFile(
aboutPage,
aboutContent.replace(
'export default',
'import "../../components/parse-error.js"\nexport default'
)
)
expect(await hasRedbox(browser)).toBe(true)
expect(await getRedboxHeader(browser)).toMatchInlineSnapshot(
`"Failed to compile"`
)
expect(await getRedboxSource(browser)).toMatchInlineSnapshot(`
"./components/parse-error.js
Error:
x Expression expected
,-[1:1]
1 | This
2 | is
3 | }}}
: ^
4 | invalid
5 | js
\`----
Caused by:
0: failed to process input file
1: Syntax Error
Import trace for requested module:
./components/parse-error.js"
`)
await next.patchFile(aboutPage, aboutContent)
await check(() => getBrowserBodyText(browser), /This is the about page/)
expect(await hasRedbox(browser, false)).toBe(false)
} catch (err) {
await next.patchFile(aboutPage, aboutContent)
if (browser) {
await check(
() => getBrowserBodyText(browser),
/This is the about page/
)
}
throw err
} finally {
if (browser) {
await browser.close()
}
}
})
it('should recover from errors in getInitialProps in client', async () => { it('should recover from errors in getInitialProps in client', async () => {
let browser let browser
const erroredPage = join('pages', 'hmr', 'error-in-gip.js') const erroredPage = join('pages', 'hmr', 'error-in-gip.js')

View file

@ -0,0 +1,5 @@
This
is
}}}
invalid
js

View file

@ -0,0 +1,5 @@
This
is
}}}
invalid
js

View file

@ -1,4 +1,4 @@
export default () => { export default function Page() {
return ( return (
<div className="hmr-about-page"> <div className="hmr-about-page">
<p>This is the about page.</p> <p>This is the about page.</p>

View file

@ -1,4 +1,4 @@
export default () => { export default function Page() {
return ( return (
<div className="hmr-about-page"> <div className="hmr-about-page">
<p>This is the about page.</p> <p>This is the about page.</p>

View file

@ -1,4 +1,4 @@
export default () => { export default function Page() {
return ( return (
<div className="hmr-about-page"> <div className="hmr-about-page">
<p>This is the about page.</p> <p>This is the about page.</p>

View file

@ -1,4 +1,4 @@
export default () => { export default function Page() {
return ( return (
<div className="hmr-about-page"> <div className="hmr-about-page">
<p>This is the about page.</p> <p>This is the about page.</p>

View file

@ -1,4 +1,4 @@
export default () => { export default function Page() {
return ( return (
<div className="hmr-about-page"> <div className="hmr-about-page">
<p>This is the about page.</p> <p>This is the about page.</p>

View file

@ -1,4 +1,4 @@
export default () => { export default function Page() {
return ( return (
<div className="hmr-about-page"> <div className="hmr-about-page">
<p>This is the about page.</p> <p>This is the about page.</p>

View file

@ -1,4 +1,4 @@
export default () => { export default function Page() {
return ( return (
<div className="hmr-about-page"> <div className="hmr-about-page">
<p>This is the about page.</p> <p>This is the about page.</p>

View file

@ -0,0 +1,7 @@
export default function Page() {
return (
<div className="hmr-about-page">
<p>This is the about page.</p>
</div>
)
}

View file

@ -0,0 +1,7 @@
export default function Page() {
return (
<div className="hmr-about-page">
<p>This is the about page.</p>
</div>
)
}

View file

@ -1,5 +1,7 @@
export default () => ( export default function Page() {
return (
<div className="hmr-contact-page"> <div className="hmr-contact-page">
<p>This is the contact page.</p> <p>This is the contact page.</p>
</div> </div>
) )
}

View file

@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
export default class extends React.Component { export default class Page extends React.Component {
static getInitialProps() { static getInitialProps() {
const error = new Error('an-expected-error-in-gip') const error = new Error('an-expected-error-in-gip')
throw error throw error

View file

@ -1,9 +1,11 @@
import Link from 'next/link' import Link from 'next/link'
export default () => ( export default function Page() {
return (
<div> <div>
<Link href="/hmr/error-in-gip" id="error-in-gip-link"> <Link href="/hmr/error-in-gip" id="error-in-gip-link">
Bad Page Bad Page
</Link> </Link>
</div> </div>
) )
}

View file

@ -3,6 +3,6 @@ import dynamic from 'next/dynamic'
const HmrDynamic = dynamic(import('../../components/hmr/dynamic')) const HmrDynamic = dynamic(import('../../components/hmr/dynamic'))
export default () => { export default function Page() {
return <HmrDynamic /> return <HmrDynamic />
} }