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:
parent
3c87e1b52a
commit
3b91ca98a3
25 changed files with 231 additions and 40 deletions
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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/**
|
||||||
|
|
|
@ -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')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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`] = `
|
||||||
|
|
|
@ -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"
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -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"
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -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')
|
||||||
|
|
5
test/development/basic/hmr/components/parse-error.js
Normal file
5
test/development/basic/hmr/components/parse-error.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
This
|
||||||
|
is
|
||||||
|
}}}
|
||||||
|
invalid
|
||||||
|
js
|
5
test/development/basic/hmr/components/parse-error.xyz
Normal file
5
test/development/basic/hmr/components/parse-error.xyz
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
This
|
||||||
|
is
|
||||||
|
}}}
|
||||||
|
invalid
|
||||||
|
js
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
7
test/development/basic/hmr/pages/hmr/about8.js
Normal file
7
test/development/basic/hmr/pages/hmr/about8.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<div className="hmr-about-page">
|
||||||
|
<p>This is the about page.</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
7
test/development/basic/hmr/pages/hmr/about9.js
Normal file
7
test/development/basic/hmr/pages/hmr/about9.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<div className="hmr-about-page">
|
||||||
|
<p>This is the about page.</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -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 />
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue