-
Notifications
You must be signed in to change notification settings - Fork 27.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
27 changed files
with
568 additions
and
112 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 4 additions & 2 deletions
6
packages/next/src/client/components/globals/intercept-console-error.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
packages/next/src/client/components/react-dev-overlay/internal/helpers/stitched-error.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import React from 'react' | ||
|
||
const captureOwnerStack = process.env.__NEXT_REACT_OWNER_STACK | ||
? (React as any).captureOwnerStack | ||
: () => '' | ||
|
||
const REACT_ERROR_STACK_BOTTOM_FRAME = 'react-stack-bottom-frame' | ||
const REACT_ERROR_STACK_BOTTOM_FRAME_REGEX = new RegExp( | ||
`(at ${REACT_ERROR_STACK_BOTTOM_FRAME} )|(${REACT_ERROR_STACK_BOTTOM_FRAME}\\@)` | ||
) | ||
|
||
export function getReactStitchedError<T = unknown>(err: T): Error { | ||
if (!process.env.__NEXT_REACT_OWNER_STACK) { | ||
return err as any | ||
} | ||
|
||
const isErrorInstance = err instanceof Error | ||
const originStack = isErrorInstance ? err.stack || '' : '' | ||
const originMessage = isErrorInstance ? err.message : '' | ||
const stackLines = originStack.split('\n') | ||
const indexOfSplit = stackLines.findIndex((line) => | ||
REACT_ERROR_STACK_BOTTOM_FRAME_REGEX.test(line) | ||
) | ||
const isOriginalReactError = indexOfSplit >= 0 // has the react-stack-bottom-frame | ||
let newStack = isOriginalReactError | ||
? stackLines.slice(0, indexOfSplit).join('\n') | ||
: originStack | ||
|
||
const newError = new Error(originMessage) | ||
// Copy all enumerable properties, e.g. digest | ||
Object.assign(newError, err) | ||
newError.stack = newStack | ||
|
||
// Avoid duplicate overriding stack frames | ||
const ownerStack = captureOwnerStack() | ||
if (ownerStack && newStack.endsWith(ownerStack) === false) { | ||
newStack += ownerStack | ||
// Override stack | ||
newError.stack = newStack | ||
} | ||
|
||
return newError | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
85 changes: 85 additions & 0 deletions
85
packages/next/src/client/react-client-callbacks/app-router.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// This file is only used in app router due to the specific error state handling. | ||
|
||
import type { HydrationOptions } from 'react-dom/client' | ||
import { getReactStitchedError } from '../components/react-dev-overlay/internal/helpers/stitched-error' | ||
import { handleClientError } from '../components/react-dev-overlay/internal/helpers/use-error-handler' | ||
import { isNextRouterError } from '../components/is-next-router-error' | ||
import { isBailoutToCSRError } from '../../shared/lib/lazy-dynamic/bailout-to-csr' | ||
import { reportGlobalError } from './report-global-error' | ||
import isError from '../../lib/is-error' | ||
import { originConsoleError } from '../components/globals/intercept-console-error' | ||
|
||
export const onCaughtError: HydrationOptions['onCaughtError'] = ( | ||
err, | ||
errorInfo | ||
) => { | ||
// Skip certain custom errors which are not expected to be reported on client | ||
if (isBailoutToCSRError(err) || isNextRouterError(err)) return | ||
|
||
const stitchedError = getReactStitchedError(err) | ||
|
||
if (process.env.NODE_ENV === 'development') { | ||
const errorBoundaryComponent = errorInfo?.errorBoundary?.constructor | ||
const errorBoundaryName = | ||
// read react component displayName | ||
(errorBoundaryComponent as any)?.displayName || | ||
errorBoundaryComponent?.name || | ||
'Unknown' | ||
|
||
const componentThatErroredFrame = errorInfo?.componentStack?.split('\n')[1] | ||
|
||
// Match chrome or safari stack trace | ||
const matches = | ||
componentThatErroredFrame?.match(/\s+at (\w+)\s+|(\w+)@/) ?? [] | ||
const componentThatErroredName = matches[1] || matches[2] || 'Unknown' | ||
|
||
// In development mode, pass along the component stack to the error | ||
if (process.env.NODE_ENV === 'development' && errorInfo.componentStack) { | ||
;(stitchedError as any)._componentStack = errorInfo.componentStack | ||
} | ||
|
||
// Create error location with errored component and error boundary, to match the behavior of default React onCaughtError handler. | ||
const errorLocation = `The above error occurred in the <${componentThatErroredName}> component. It was handled by the <${errorBoundaryName}> error boundary.` | ||
|
||
const originErrorStack = isError(err) ? err.stack || '' : '' | ||
|
||
// Always log the modified error instance so the console.error interception side can pick it up easily without constructing an error again. | ||
originConsoleError(originErrorStack + '\n\n' + errorLocation) | ||
handleClientError(stitchedError) | ||
} else { | ||
originConsoleError(err) | ||
} | ||
} | ||
|
||
export const onUncaughtError: HydrationOptions['onUncaughtError'] = ( | ||
err, | ||
errorInfo | ||
) => { | ||
// Skip certain custom errors which are not expected to be reported on client | ||
if (isBailoutToCSRError(err) || isNextRouterError(err)) return | ||
|
||
const stitchedError = getReactStitchedError(err) | ||
|
||
if (process.env.NODE_ENV === 'development') { | ||
const componentThatErroredFrame = errorInfo?.componentStack?.split('\n')[1] | ||
|
||
// Match chrome or safari stack trace | ||
const matches = | ||
componentThatErroredFrame?.match(/\s+at (\w+)\s+|(\w+)@/) ?? [] | ||
const componentThatErroredName = matches[1] || matches[2] || 'Unknown' | ||
|
||
// In development mode, pass along the component stack to the error | ||
if (process.env.NODE_ENV === 'development' && errorInfo.componentStack) { | ||
;(stitchedError as any)._componentStack = errorInfo.componentStack | ||
} | ||
|
||
// Create error location with errored component and error boundary, to match the behavior of default React onCaughtError handler. | ||
const errorLocation = `The above error occurred in the <${componentThatErroredName}> component.` | ||
|
||
originConsoleError(stitchedError.stack + '\n\n' + errorLocation) | ||
// Always log the modified error instance so the console.error interception side can pick it up easily without constructing an error again. | ||
reportGlobalError(stitchedError) | ||
} else { | ||
reportGlobalError(err) | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
packages/next/src/client/react-client-callbacks/report-global-error.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export const reportGlobalError = | ||
typeof reportError === 'function' | ||
? // In modern browsers, reportError will dispatch an error event, | ||
// emulating an uncaught JavaScript error. | ||
reportError | ||
: (error: any) => { | ||
window.console.error(error) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// This module can be shared between both pages router and app router | ||
|
||
import type { HydrationOptions } from 'react-dom/client' | ||
import { isBailoutToCSRError } from '../../shared/lib/lazy-dynamic/bailout-to-csr' | ||
import { reportGlobalError } from './report-global-error' | ||
|
||
export const onRecoverableError: HydrationOptions['onRecoverableError'] = ( | ||
err, | ||
errorInfo | ||
) => { | ||
const stitchedError = err // getReactStitchedError(err) | ||
// In development mode, pass along the component stack to the error | ||
if (process.env.NODE_ENV === 'development' && errorInfo.componentStack) { | ||
;(stitchedError as any)._componentStack = errorInfo.componentStack | ||
} | ||
// Skip certain custom errors which are not expected to be reported on client | ||
if (isBailoutToCSRError(err)) return | ||
|
||
reportGlobalError(stitchedError) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
import type { NextConfig } from '../server/config-shared' | ||
|
||
export function needsExperimentalReact(config: NextConfig) { | ||
return Boolean(config.experimental?.ppr || config.experimental?.taint) | ||
const { ppr, taint, reactOwnerStack } = config.experimental || {} | ||
return Boolean(ppr || taint || reactOwnerStack) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.