From 89df91ee905dd01988a9b79dd80c400e042b6744 Mon Sep 17 00:00:00 2001 From: natew Date: Thu, 10 Feb 2022 13:10:55 -0800 Subject: [PATCH] feat(react-dev-overlay): Report full parsed runtime errors over Bus + accepts preventDisplay prop to avoid showing error messages inline, while still reporting errors over the bus --- .../src/internal/ReactDevOverlay.tsx | 8 ++++++- .../react-dev-overlay/src/internal/bus.ts | 15 ++++++++++++ .../src/internal/container/Errors.tsx | 19 +++++++++++++-- .../src/internal/container/RuntimeError.tsx | 23 +++++++++++++++++++ 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/packages/react-dev-overlay/src/internal/ReactDevOverlay.tsx b/packages/react-dev-overlay/src/internal/ReactDevOverlay.tsx index 60023315c9c2c3f..a5f2787c0346424 100644 --- a/packages/react-dev-overlay/src/internal/ReactDevOverlay.tsx +++ b/packages/react-dev-overlay/src/internal/ReactDevOverlay.tsx @@ -50,6 +50,8 @@ function reducer(state: OverlayState, ev: Bus.BusEvent): OverlayState { errors: [...state.errors, { id: state.nextId, event: ev }], } } + case Bus.TYPE_UNHANDLED_ERROR_FULL: + return { ...state } default: { // eslint-disable-next-line @typescript-eslint/no-unused-vars const _: never = ev @@ -60,6 +62,10 @@ function reducer(state: OverlayState, ev: Bus.BusEvent): OverlayState { const ReactDevOverlay: React.FunctionComponent = function ReactDevOverlay({ children, + preventDisplay, +}: { + children?: React.ReactNode + preventDisplay?: boolean }) { const [state, dispatch] = React.useReducer< React.Reducer @@ -104,7 +110,7 @@ const ReactDevOverlay: React.FunctionComponent = function ReactDevOverlay({ {hasBuildError ? ( ) : hasRuntimeErrors ? ( - + ) : isAboutToFullRefresh ? ( ) : undefined} diff --git a/packages/react-dev-overlay/src/internal/bus.ts b/packages/react-dev-overlay/src/internal/bus.ts index 1e7dfa8b6b8fd6d..31312a13c2ddf53 100644 --- a/packages/react-dev-overlay/src/internal/bus.ts +++ b/packages/react-dev-overlay/src/internal/bus.ts @@ -1,4 +1,5 @@ import { StackFrame } from 'stacktrace-parser' +import { OriginalStackFrame } from './helpers/stack-frame' export const TYPE_BUILD_OK = 'build-ok' export const TYPE_BUILD_ERROR = 'build-error' @@ -6,6 +7,7 @@ export const TYPE_FULL_REFRESH_NEEDED = 'full-refresh-needed' export const TYPE_REFRESH = 'fast-refresh' export const TYPE_UNHANDLED_ERROR = 'unhandled-error' export const TYPE_UNHANDLED_REJECTION = 'unhandled-rejection' +export const TYPE_UNHANDLED_ERROR_FULL = 'unhandled-error-full' export type BuildOk = { type: typeof TYPE_BUILD_OK } export type BuildError = { @@ -22,6 +24,18 @@ export type UnhandledError = { reason: Error frames: StackFrame[] } +export type UnhandledErrorFull = { + type: typeof TYPE_UNHANDLED_ERROR_FULL + error: { + name: Error['name'] + message: Error['message'] + stack?: Error['stack'] + } + id: number + firstFrame: OriginalStackFrame + leadingFrames: any[] + visibleCallStackFrames: any[] +} export type UnhandledRejection = { type: typeof TYPE_UNHANDLED_REJECTION reason: Error @@ -34,6 +48,7 @@ export type BusEvent = | FullRefreshNeeded | UnhandledError | UnhandledRejection + | UnhandledErrorFull export type BusEventHandler = (ev: BusEvent) => void diff --git a/packages/react-dev-overlay/src/internal/container/Errors.tsx b/packages/react-dev-overlay/src/internal/container/Errors.tsx index c77339b0c6b36a7..6ea52257975fd89 100644 --- a/packages/react-dev-overlay/src/internal/container/Errors.tsx +++ b/packages/react-dev-overlay/src/internal/container/Errors.tsx @@ -26,7 +26,10 @@ export type SupportedErrorEvent = { id: number event: UnhandledError | UnhandledRejection } -export type ErrorsProps = { errors: SupportedErrorEvent[] } +export type ErrorsProps = { + errors: SupportedErrorEvent[] + preventDisplay?: boolean +} export type ReadyRuntimeError = { id: number @@ -108,7 +111,10 @@ async function getErrorByType( throw new Error('type system invariant violation') } -export const Errors: React.FC = function Errors({ errors }) { +export const Errors: React.FC = function Errors({ + errors, + preventDisplay, +}) { const [lookups, setLookups] = React.useState( {} as { [eventId: string]: ReadyErrorEvent } ) @@ -226,6 +232,15 @@ export const Errors: React.FC = function Errors({ errors }) { return } + if (preventDisplay) { + // so error processing still happens and can report + return ( +
+ +
+ ) + } + if (isMinimized) { return ( diff --git a/packages/react-dev-overlay/src/internal/container/RuntimeError.tsx b/packages/react-dev-overlay/src/internal/container/RuntimeError.tsx index e1b82ca5d7b2328..8d28b39fd00e0c7 100644 --- a/packages/react-dev-overlay/src/internal/container/RuntimeError.tsx +++ b/packages/react-dev-overlay/src/internal/container/RuntimeError.tsx @@ -5,6 +5,8 @@ import { noop as css } from '../helpers/noop-template' import { getFrameSource, OriginalStackFrame } from '../helpers/stack-frame' import { ReadyRuntimeError } from './Errors' +import * as Bus from '../bus' + export type RuntimeErrorProps = { error: ReadyRuntimeError } const CallStackFrame: React.FC<{ @@ -122,6 +124,27 @@ const RuntimeError: React.FC = function RuntimeError({ visibleCallStackFrames.length, ]) + React.useEffect( + () => { + if (firstFrame) { + Bus.emit({ + type: Bus.TYPE_UNHANDLED_ERROR_FULL, + error: { + name: error.error.name, + message: error.error.message, + stack: error.error.stack, + }, + id: error.id, + firstFrame, + leadingFrames, + visibleCallStackFrames, + }) + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [firstFrame] + ) + return ( {firstFrame ? (