feat(error-overlay): version staleness in Pages Router (#62942)

This commit is contained in:
Balázs Orbán 2024-03-06 17:00:16 +01:00 committed by GitHub
parent 4c62c3002e
commit 0a73e89880
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 47 additions and 38 deletions

View file

@ -9,6 +9,7 @@ import { ErrorBoundary } from './ErrorBoundary'
import { Base } from '../internal/styles/Base'
import { ComponentStyles } from '../internal/styles/ComponentStyles'
import { CssReset } from '../internal/styles/CssReset'
import type { VersionInfo } from '../../../../server/dev/parse-version-info'
type RefreshState =
| {
@ -27,6 +28,7 @@ type OverlayState = {
buildError: string | null
errors: SupportedErrorEvent[]
refreshState: RefreshState
versionInfo: VersionInfo
}
function pushErrorFilterDuplicates(
@ -102,6 +104,9 @@ function reducer(state: OverlayState, ev: Bus.BusEvent): OverlayState {
return state
}
}
case Bus.TYPE_VERSION_INFO: {
return { ...state, versionInfo: ev.versionInfo }
}
default: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _: never = ev
@ -139,6 +144,7 @@ const ReactDevOverlay: React.FunctionComponent<ReactDevOverlayProps> =
refreshState: {
type: 'idle',
},
versionInfo: { installed: '0.0.0', staleness: 'unknown' },
})
React.useEffect(() => {
@ -182,12 +188,16 @@ const ReactDevOverlay: React.FunctionComponent<ReactDevOverlayProps> =
<ComponentStyles />
{displayPrevented ? null : hasBuildError ? (
<BuildError message={state.buildError!} />
<BuildError
message={state.buildError!}
versionInfo={state.versionInfo}
/>
) : hasRuntimeErrors ? (
<Errors
isAppDir={false}
errors={state.errors}
initialDisplayState={'fullscreen'}
versionInfo={state.versionInfo}
/>
) : undefined}
</ShadowPortal>

View file

@ -1,5 +1,6 @@
import type { StackFrame } from 'next/dist/compiled/stacktrace-parser'
import type { ComponentStackFrame } from '../internal/helpers/parse-component-stack'
import type { VersionInfo } from '../../../../server/dev/parse-version-info'
export const TYPE_BUILD_OK = 'build-ok'
export const TYPE_BUILD_ERROR = 'build-error'
@ -7,6 +8,7 @@ export const TYPE_REFRESH = 'fast-refresh'
export const TYPE_BEFORE_REFRESH = 'before-fast-refresh'
export const TYPE_UNHANDLED_ERROR = 'unhandled-error'
export const TYPE_UNHANDLED_REJECTION = 'unhandled-rejection'
export const TYPE_VERSION_INFO = 'version-info'
export type BuildOk = { type: typeof TYPE_BUILD_OK }
export type BuildError = {
@ -26,6 +28,12 @@ export type UnhandledRejection = {
reason: Error
frames: StackFrame[]
}
export type VersionInfoEvent = {
type: typeof TYPE_VERSION_INFO
versionInfo: VersionInfo
}
export type BusEvent =
| BuildOk
| BuildError
@ -33,6 +41,7 @@ export type BusEvent =
| BeforeFastRefresh
| UnhandledError
| UnhandledRejection
| VersionInfoEvent
export type BusEventHandler = (ev: BusEvent) => void

View file

@ -5,6 +5,7 @@ import {
hydrationErrorState,
patchConsoleError,
} from '../internal/helpers/hydration-error-info'
import type { VersionInfo } from '../../../../server/dev/parse-version-info'
// Patch console.error to collect information about hydration errors
patchConsoleError()
@ -121,6 +122,10 @@ function onBeforeRefresh() {
Bus.emit({ type: Bus.TYPE_BEFORE_REFRESH })
}
function onVersionInfo(versionInfo: VersionInfo) {
Bus.emit({ type: Bus.TYPE_VERSION_INFO, versionInfo })
}
export { getErrorByType } from '../internal/helpers/getErrorByType'
export { getServerError } from '../internal/helpers/nodeStackFrames'
export { default as ReactDevOverlay } from './ReactDevOverlay'
@ -131,4 +136,5 @@ export {
unregister,
onBeforeRefresh,
onRefresh,
onVersionInfo,
}

View file

@ -34,6 +34,7 @@ import {
onBuildOk,
onBeforeRefresh,
onRefresh,
onVersionInfo,
} from '../../components/react-dev-overlay/pages/client'
import stripAnsi from 'next/dist/compiled/strip-ansi'
import { addMessageListener, sendMessage } from './websocket'
@ -278,6 +279,10 @@ function processMessage(obj: HMR_ACTION_TYPES) {
}
const { errors, warnings } = obj
// Is undefined when it's a 'built' event
if ('versionInfo' in obj) onVersionInfo(obj.versionInfo)
const hasErrors = Boolean(errors && errors.length)
if (hasErrors) {
sendMessage(

View file

@ -5,6 +5,7 @@ import {
check,
getBrowserBodyText,
getRedboxHeader,
getRedboxDescription,
getRedboxSource,
hasRedbox,
renderViaHTTP,
@ -727,14 +728,9 @@ describe.each([[''], ['/docs']])(
)
expect(await hasRedbox(browser)).toBe(true)
expect(await getRedboxHeader(browser)).toMatchInlineSnapshot(`
"1 of 1 unhandled error
Server Error
Error: The default export is not a React Component in page: "/hmr/about5"
This error happened while generating the page. Any console logs will be displayed in the terminal window."
`)
expect(await getRedboxDescription(browser)).toMatchInlineSnapshot(
`"Error: The default export is not a React Component in page: "/hmr/about5""`
)
await next.patchFile(aboutPage, aboutContent)
@ -830,14 +826,9 @@ describe.each([[''], ['/docs']])(
)
expect(await hasRedbox(browser)).toBe(true)
expect(await getRedboxHeader(browser)).toMatchInlineSnapshot(`
"1 of 1 unhandled error
Server Error
Error: The default export is not a React Component in page: "/hmr/about7"
This error happened while generating the page. Any console logs will be displayed in the terminal window."
`)
expect(await getRedboxDescription(browser)).toMatchInlineSnapshot(
`"Error: The default export is not a React Component in page: "/hmr/about7""`
)
await next.patchFile(aboutPage, aboutContent)
@ -885,9 +876,7 @@ describe.each([[''], ['/docs']])(
)
expect(await hasRedbox(browser)).toBe(true)
expect(await getRedboxHeader(browser)).toMatchInlineSnapshot(
`"Failed to compile"`
)
expect(await getRedboxHeader(browser)).toMatch('Failed to compile')
expect(await getRedboxSource(browser)).toMatchInlineSnapshot(`
"./components/parse-error.xyz
Module parse failed: Unexpected token (3:0)
@ -949,9 +938,7 @@ describe.each([[''], ['/docs']])(
)
expect(await hasRedbox(browser)).toBe(true)
expect(await getRedboxHeader(browser)).toMatchInlineSnapshot(
`"Failed to compile"`
)
expect(await getRedboxHeader(browser)).toMatch('Failed to compile')
let redboxSource = await getRedboxSource(browser)
redboxSource = redboxSource.replace(`${next.testDir}`, '.')
@ -1023,12 +1010,9 @@ describe.each([[''], ['/docs']])(
await browser.elementByCss('#error-in-gip-link').click()
expect(await hasRedbox(browser)).toBe(true)
expect(await getRedboxHeader(browser)).toMatchInlineSnapshot(`
"1 of 1 unhandled error
Unhandled Runtime Error
Error: an-expected-error-in-gip"
`)
expect(await getRedboxDescription(browser)).toMatchInlineSnapshot(
`"Error: an-expected-error-in-gip"`
)
await next.patchFile(
erroredPage,
@ -1067,14 +1051,9 @@ describe.each([[''], ['/docs']])(
browser = await webdriver(next.url, basePath + '/hmr/error-in-gip')
expect(await hasRedbox(browser)).toBe(true)
expect(await getRedboxHeader(browser)).toMatchInlineSnapshot(`
"1 of 1 unhandled error
Server Error
Error: an-expected-error-in-gip
This error happened while generating the page. Any console logs will be displayed in the terminal window."
`)
expect(await getRedboxDescription(browser)).toMatchInlineSnapshot(
`"Error: an-expected-error-in-gip"`
)
const erroredPage = join('pages', 'hmr', 'error-in-gip.js')

View file

@ -23,7 +23,7 @@ function runTests({ isDev }) {
if (isDev) {
const browser = await webdriver(appPort, '/')
expect(await hasRedbox(browser)).toBe(true)
expect(await getRedboxHeader(browser)).toBe('Failed to compile')
expect(await getRedboxHeader(browser)).toMatch('Failed to compile')
const source = await getRedboxSource(browser)
if (process.env.TURBOPACK) {
expect(source).toMatchInlineSnapshot(`