Skip to content

Commit

Permalink
extract the helper
Browse files Browse the repository at this point in the history
  • Loading branch information
huozhi committed Jul 10, 2024
1 parent b855033 commit 3f47c94
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 90 deletions.
30 changes: 22 additions & 8 deletions packages/next/src/client/app-index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,28 @@ import {
createMutableActionQueue,
} from '../shared/lib/router/action-queue'
import { HMR_ACTIONS_SENT_TO_BROWSER } from '../server/dev/hot-reloader-types'
import { isNextRouterError } from './components/is-next-router-error'
import { handleClientError } from './components/react-dev-overlay/internal/helpers/use-error-handler'

// Patch console.error to collect information about hydration errors
const origConsoleError = window.console.error
window.console.error = (...args) => {
// See https://github.com/facebook/react/blob/d50323eb845c5fde0d720cae888bf35dedd05506/packages/react-reconciler/src/ReactFiberErrorLogger.js#L78
const error = process.env.NODE_ENV !== 'production' ? args[1] : args[0]
if (!isNextRouterError(error)) {
if (process.env.NODE_ENV !== 'production') {
const storeHydrationErrorStateFromConsoleArgs =
require('./components/react-dev-overlay/internal/helpers/hydration-error-info')
.storeHydrationErrorStateFromConsoleArgs as typeof import('./components/react-dev-overlay/internal/helpers/hydration-error-info').storeHydrationErrorStateFromConsoleArgs
storeHydrationErrorStateFromConsoleArgs()

storeHydrationErrorStateFromConsoleArgs(...args)
handleClientError(error)
}

origConsoleError.apply(window.console, args)
}
}

/// <reference types="react-dom/experimental" />

Expand Down Expand Up @@ -181,14 +203,6 @@ export function hydrate() {
const isError =
document.documentElement.id === '__next_error__' || hasMissingTags

if (process.env.NODE_ENV !== 'production') {
// Patch console.error to collect information about hydration errors
const patchConsoleError =
require('./components/react-dev-overlay/internal/helpers/hydration-error-info')
.patchConsoleError as typeof import('./components/react-dev-overlay/internal/helpers/hydration-error-info').patchConsoleError
patchConsoleError()
}

if (isError) {
if (process.env.NODE_ENV !== 'production') {
// if an error is thrown while rendering an RSC stream, this will catch it in dev
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,22 +65,18 @@ export const getReactHydrationDiffSegments = (msg: NullableText) => {
* When the hydration runtime error is thrown, the message and component stack are added to the error.
* This results in a more helpful error message in the error overlay.
*/
export function patchConsoleError() {
const prev = console.error
console.error = function (msg, serverContent, clientContent, componentStack) {
if (isKnownHydrationWarning(msg)) {
hydrationErrorState.warning = [
// remove the last %s from the message
msg,
serverContent,
clientContent,
]
hydrationErrorState.componentStack = componentStack
hydrationErrorState.serverContent = serverContent
hydrationErrorState.clientContent = clientContent
}

// @ts-expect-error argument is defined
prev.apply(console, arguments)
export function storeHydrationErrorStateFromConsoleArgs(...args: any[]) {
const [msg, serverContent, clientContent, componentStack] = args
if (isKnownHydrationWarning(msg)) {
hydrationErrorState.warning = [
// remove the last %s from the message
msg,
serverContent,
clientContent,
]
hydrationErrorState.componentStack = componentStack
hydrationErrorState.serverContent = serverContent
hydrationErrorState.clientContent = clientContent
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,67 +24,63 @@ const rejectionQueue: Array<Error> = []
const errorHandlers: Array<ErrorHandler> = []
const rejectionHandlers: Array<ErrorHandler> = []

if (typeof window !== 'undefined') {
function handleError(error: unknown) {
if (
!error ||
!(error instanceof Error) ||
typeof error.stack !== 'string'
) {
// A non-error was thrown, we don't have anything to show. :-(
return
}
export function handleClientError(error: unknown) {
if (!error || !(error instanceof Error) || typeof error.stack !== 'string') {
// A non-error was thrown, we don't have anything to show. :-(
return
}

const isCausedByHydrationFailure = isHydrationError(error)
if (
isHydrationError(error) &&
!error.message.includes(
'https://nextjs.org/docs/messages/react-hydration-error'
)
) {
const reactHydrationDiffSegments = getReactHydrationDiffSegments(
error.message
)
let parsedHydrationErrorState: typeof hydrationErrorState = {}
if (reactHydrationDiffSegments) {
const isCausedByHydrationFailure = isHydrationError(error)
if (
isHydrationError(error) &&
!error.message.includes(
'https://nextjs.org/docs/messages/react-hydration-error'
)
) {
const reactHydrationDiffSegments = getReactHydrationDiffSegments(
error.message
)
let parsedHydrationErrorState: typeof hydrationErrorState = {}
if (reactHydrationDiffSegments) {
parsedHydrationErrorState = {
...(error as any).details,
...hydrationErrorState,
warning: hydrationErrorState.warning || [
getDefaultHydrationErrorMessage(),
],
notes: reactHydrationDiffSegments[0],
reactOutputComponentDiff: reactHydrationDiffSegments[1],
}
} else {
// If there's any extra information in the error message to display,
// append it to the error message details property
if (hydrationErrorState.warning) {
// The patched console.error found hydration errors logged by React
// Append the logged warning to the error message
parsedHydrationErrorState = {
...(error as any).details,
// It contains the warning, component stack, server and client tag names
...hydrationErrorState,
warning: hydrationErrorState.warning || [
getDefaultHydrationErrorMessage(),
],
notes: reactHydrationDiffSegments[0],
reactOutputComponentDiff: reactHydrationDiffSegments[1],
}
} else {
// If there's any extra information in the error message to display,
// append it to the error message details property
if (hydrationErrorState.warning) {
// The patched console.error found hydration errors logged by React
// Append the logged warning to the error message
parsedHydrationErrorState = {
...(error as any).details,
// It contains the warning, component stack, server and client tag names
...hydrationErrorState,
}
}
error.message +=
'\nSee more info here: https://nextjs.org/docs/messages/react-hydration-error'
}
;(error as any).details = parsedHydrationErrorState
error.message +=
'\nSee more info here: https://nextjs.org/docs/messages/react-hydration-error'
}
;(error as any).details = parsedHydrationErrorState
}

// Only queue one hydration every time
if (isCausedByHydrationFailure) {
if (!hasHydrationError) {
errorQueue.push(error)
}
hasHydrationError = true
}
for (const handler of errorHandlers) {
handler(error)
// Only queue one hydration every time
if (isCausedByHydrationFailure) {
if (!hasHydrationError) {
errorQueue.push(error)
}
hasHydrationError = true
}
for (const handler of errorHandlers) {
handler(error)
}
}
if (typeof window !== 'undefined') {
// These event handlers must be added outside of the hook because there is no
// guarantee that the hook will be alive in a mounted component in time to
// when the errors occur.
Expand All @@ -96,19 +92,10 @@ if (typeof window !== 'undefined') {
event.preventDefault()
return false
}
handleError(event.error)
handleClientError(event.error)
}
)
// caught errors go through console.error
const origConsoleError = window.console.error
window.console.error = (...args) => {
// See https://github.com/facebook/react/blob/d50323eb845c5fde0d720cae888bf35dedd05506/packages/react-reconciler/src/ReactFiberErrorLogger.js#L78
const error = process.env.NODE_ENV !== 'production' ? args[1] : args[0]
if (!isNextRouterError(error)) {
handleError(error)
origConsoleError.apply(window.console, args)
}
}

window.addEventListener(
'unhandledrejection',
(ev: WindowEventMap['unhandledrejection']): void => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { parseComponentStack } from '../internal/helpers/parse-component-stack'
import {
getReactHydrationDiffSegments,
hydrationErrorState,
patchConsoleError,
storeHydrationErrorStateFromConsoleArgs,
} from '../internal/helpers/hydration-error-info'
import {
ACTION_BEFORE_REFRESH,
Expand All @@ -21,9 +21,6 @@ import {
isHydrationError,
} from '../../is-hydration-error'

// Patch console.error to collect information about hydration errors
patchConsoleError()

let isRegistered = false
let stackTraceLimit: number | undefined = undefined

Expand Down Expand Up @@ -93,6 +90,7 @@ let origConsoleError = console.error
function nextJsHandleConsoleError(...args: any[]) {
// See https://github.com/facebook/react/blob/d50323eb845c5fde0d720cae888bf35dedd05506/packages/react-reconciler/src/ReactFiberErrorLogger.js#L78
const error = process.env.NODE_ENV !== 'production' ? args[1] : args[0]
storeHydrationErrorStateFromConsoleArgs(...args)
handleError(error)
origConsoleError.apply(window.console, args)
}
Expand Down

0 comments on commit 3f47c94

Please sign in to comment.