From f476b7f030f2dd427ca655fcea36f4933a4b4da0 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 17 Jul 2024 10:05:09 +0800 Subject: [PATCH] feat(runtime-core): add app.config.throwUnhandledErrorInProduction close #7876 --- .../__tests__/apiCreateApp.spec.ts | 17 +++++++++++++ packages/runtime-core/src/apiCreateApp.ts | 7 ++++++ packages/runtime-core/src/errorHandling.ts | 25 +++++++++++-------- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/packages/runtime-core/__tests__/apiCreateApp.spec.ts b/packages/runtime-core/__tests__/apiCreateApp.spec.ts index 0d7c3380311..d8576faa898 100644 --- a/packages/runtime-core/__tests__/apiCreateApp.spec.ts +++ b/packages/runtime-core/__tests__/apiCreateApp.spec.ts @@ -538,6 +538,23 @@ describe('api: createApp', () => { expect(serializeInner(root)).toBe('hello') }) + test('config.throwUnhandledErrorInProduction', () => { + __DEV__ = false + try { + const err = new Error() + const app = createApp({ + setup() { + throw err + }, + }) + app.config.throwUnhandledErrorInProduction = true + const root = nodeOps.createElement('div') + expect(() => app.mount(root)).toThrow(err) + } finally { + __DEV__ = true + } + }) + test('return property "_" should not overwrite "ctx._", __isScriptSetup: false', () => { const Comp = defineComponent({ setup() { diff --git a/packages/runtime-core/src/apiCreateApp.ts b/packages/runtime-core/src/apiCreateApp.ts index 3b6c047f3cb..ddf5888e845 100644 --- a/packages/runtime-core/src/apiCreateApp.ts +++ b/packages/runtime-core/src/apiCreateApp.ts @@ -124,6 +124,13 @@ export interface AppConfig { * Enable warnings for computed getters that recursively trigger itself. */ warnRecursiveComputed?: boolean + + /** + * Whether to throw unhandled errors in production. + * Default is `false` to avoid crashing on any error (and only logs it) + * But in some cases, e.g. SSR, throwing might be more desirable. + */ + throwUnhandledErrorInProduction?: boolean } export interface AppContext { diff --git a/packages/runtime-core/src/errorHandling.ts b/packages/runtime-core/src/errorHandling.ts index beddea5f644..d983999b213 100644 --- a/packages/runtime-core/src/errorHandling.ts +++ b/packages/runtime-core/src/errorHandling.ts @@ -2,7 +2,7 @@ import { pauseTracking, resetTracking } from '@vue/reactivity' import type { VNode } from './vnode' import type { ComponentInternalInstance } from './component' import { popWarningContext, pushWarningContext, warn } from './warning' -import { isArray, isFunction, isPromise } from '@vue/shared' +import { EMPTY_OBJ, isArray, isFunction, isPromise } from '@vue/shared' import { LifecycleHooks } from './enums' // contexts where user provided function may be executed, in addition to @@ -111,7 +111,9 @@ export function handleError( type: ErrorTypes, throwInDev = true, ) { - const contextVNode = instance ? instance.vnode : null + const contextVNode = instance && instance.vnode + const { errorHandler, throwUnhandledErrorInProduction } = + (instance && instance.appContext.config) || EMPTY_OBJ if (instance) { let cur = instance.parent // the exposed instance is the render proxy to keep it consistent with 2.x @@ -134,20 +136,18 @@ export function handleError( cur = cur.parent } // app-level handling - const appErrorHandler = instance.appContext.config.errorHandler - if (appErrorHandler) { + if (errorHandler) { pauseTracking() - callWithErrorHandling( - appErrorHandler, - null, - ErrorCodes.APP_ERROR_HANDLER, - [err, exposedInstance, errorInfo], - ) + callWithErrorHandling(errorHandler, null, ErrorCodes.APP_ERROR_HANDLER, [ + err, + exposedInstance, + errorInfo, + ]) resetTracking() return } } - logError(err, type, contextVNode, throwInDev) + logError(err, type, contextVNode, throwInDev, throwUnhandledErrorInProduction) } function logError( @@ -155,6 +155,7 @@ function logError( type: ErrorTypes, contextVNode: VNode | null, throwInDev = true, + throwInProd = false, ) { if (__DEV__) { const info = ErrorTypeStrings[type] @@ -171,6 +172,8 @@ function logError( } else if (!__TEST__) { console.error(err) } + } else if (throwInProd) { + throw err } else { // recover in prod to reduce the impact on end-user console.error(err)