feat(runtime-core): add app.config.throwUnhandledErrorInProduction

close #7876
This commit is contained in:
Evan You 2024-07-17 10:05:09 +08:00
parent 912494318f
commit f476b7f030
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
3 changed files with 38 additions and 11 deletions

View File

@ -538,6 +538,23 @@ describe('api: createApp', () => {
expect(serializeInner(root)).toBe('hello') 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', () => { test('return property "_" should not overwrite "ctx._", __isScriptSetup: false', () => {
const Comp = defineComponent({ const Comp = defineComponent({
setup() { setup() {

View File

@ -124,6 +124,13 @@ export interface AppConfig {
* Enable warnings for computed getters that recursively trigger itself. * Enable warnings for computed getters that recursively trigger itself.
*/ */
warnRecursiveComputed?: boolean 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 { export interface AppContext {

View File

@ -2,7 +2,7 @@ import { pauseTracking, resetTracking } from '@vue/reactivity'
import type { VNode } from './vnode' import type { VNode } from './vnode'
import type { ComponentInternalInstance } from './component' import type { ComponentInternalInstance } from './component'
import { popWarningContext, pushWarningContext, warn } from './warning' 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' import { LifecycleHooks } from './enums'
// contexts where user provided function may be executed, in addition to // contexts where user provided function may be executed, in addition to
@ -111,7 +111,9 @@ export function handleError(
type: ErrorTypes, type: ErrorTypes,
throwInDev = true, throwInDev = true,
) { ) {
const contextVNode = instance ? instance.vnode : null const contextVNode = instance && instance.vnode
const { errorHandler, throwUnhandledErrorInProduction } =
(instance && instance.appContext.config) || EMPTY_OBJ
if (instance) { if (instance) {
let cur = instance.parent let cur = instance.parent
// the exposed instance is the render proxy to keep it consistent with 2.x // the exposed instance is the render proxy to keep it consistent with 2.x
@ -134,20 +136,18 @@ export function handleError(
cur = cur.parent cur = cur.parent
} }
// app-level handling // app-level handling
const appErrorHandler = instance.appContext.config.errorHandler if (errorHandler) {
if (appErrorHandler) {
pauseTracking() pauseTracking()
callWithErrorHandling( callWithErrorHandling(errorHandler, null, ErrorCodes.APP_ERROR_HANDLER, [
appErrorHandler, err,
null, exposedInstance,
ErrorCodes.APP_ERROR_HANDLER, errorInfo,
[err, exposedInstance, errorInfo], ])
)
resetTracking() resetTracking()
return return
} }
} }
logError(err, type, contextVNode, throwInDev) logError(err, type, contextVNode, throwInDev, throwUnhandledErrorInProduction)
} }
function logError( function logError(
@ -155,6 +155,7 @@ function logError(
type: ErrorTypes, type: ErrorTypes,
contextVNode: VNode | null, contextVNode: VNode | null,
throwInDev = true, throwInDev = true,
throwInProd = false,
) { ) {
if (__DEV__) { if (__DEV__) {
const info = ErrorTypeStrings[type] const info = ErrorTypeStrings[type]
@ -171,6 +172,8 @@ function logError(
} else if (!__TEST__) { } else if (!__TEST__) {
console.error(err) console.error(err)
} }
} else if (throwInProd) {
throw err
} else { } else {
// recover in prod to reduce the impact on end-user // recover in prod to reduce the impact on end-user
console.error(err) console.error(err)