Skip to content

Commit f476b7f

Browse files
committedJul 17, 2024··
feat(runtime-core): add app.config.throwUnhandledErrorInProduction
close #7876
1 parent 9124943 commit f476b7f

File tree

3 files changed

+38
-11
lines changed

3 files changed

+38
-11
lines changed
 

‎packages/runtime-core/__tests__/apiCreateApp.spec.ts

+17
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,23 @@ describe('api: createApp', () => {
538538
expect(serializeInner(root)).toBe('hello')
539539
})
540540

541+
test('config.throwUnhandledErrorInProduction', () => {
542+
__DEV__ = false
543+
try {
544+
const err = new Error()
545+
const app = createApp({
546+
setup() {
547+
throw err
548+
},
549+
})
550+
app.config.throwUnhandledErrorInProduction = true
551+
const root = nodeOps.createElement('div')
552+
expect(() => app.mount(root)).toThrow(err)
553+
} finally {
554+
__DEV__ = true
555+
}
556+
})
557+
541558
test('return property "_" should not overwrite "ctx._", __isScriptSetup: false', () => {
542559
const Comp = defineComponent({
543560
setup() {

‎packages/runtime-core/src/apiCreateApp.ts

+7
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,13 @@ export interface AppConfig {
124124
* Enable warnings for computed getters that recursively trigger itself.
125125
*/
126126
warnRecursiveComputed?: boolean
127+
128+
/**
129+
* Whether to throw unhandled errors in production.
130+
* Default is `false` to avoid crashing on any error (and only logs it)
131+
* But in some cases, e.g. SSR, throwing might be more desirable.
132+
*/
133+
throwUnhandledErrorInProduction?: boolean
127134
}
128135

129136
export interface AppContext {

‎packages/runtime-core/src/errorHandling.ts

+14-11
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { pauseTracking, resetTracking } from '@vue/reactivity'
22
import type { VNode } from './vnode'
33
import type { ComponentInternalInstance } from './component'
44
import { popWarningContext, pushWarningContext, warn } from './warning'
5-
import { isArray, isFunction, isPromise } from '@vue/shared'
5+
import { EMPTY_OBJ, isArray, isFunction, isPromise } from '@vue/shared'
66
import { LifecycleHooks } from './enums'
77

88
// contexts where user provided function may be executed, in addition to
@@ -111,7 +111,9 @@ export function handleError(
111111
type: ErrorTypes,
112112
throwInDev = true,
113113
) {
114-
const contextVNode = instance ? instance.vnode : null
114+
const contextVNode = instance && instance.vnode
115+
const { errorHandler, throwUnhandledErrorInProduction } =
116+
(instance && instance.appContext.config) || EMPTY_OBJ
115117
if (instance) {
116118
let cur = instance.parent
117119
// the exposed instance is the render proxy to keep it consistent with 2.x
@@ -134,27 +136,26 @@ export function handleError(
134136
cur = cur.parent
135137
}
136138
// app-level handling
137-
const appErrorHandler = instance.appContext.config.errorHandler
138-
if (appErrorHandler) {
139+
if (errorHandler) {
139140
pauseTracking()
140-
callWithErrorHandling(
141-
appErrorHandler,
142-
null,
143-
ErrorCodes.APP_ERROR_HANDLER,
144-
[err, exposedInstance, errorInfo],
145-
)
141+
callWithErrorHandling(errorHandler, null, ErrorCodes.APP_ERROR_HANDLER, [
142+
err,
143+
exposedInstance,
144+
errorInfo,
145+
])
146146
resetTracking()
147147
return
148148
}
149149
}
150-
logError(err, type, contextVNode, throwInDev)
150+
logError(err, type, contextVNode, throwInDev, throwUnhandledErrorInProduction)
151151
}
152152

153153
function logError(
154154
err: unknown,
155155
type: ErrorTypes,
156156
contextVNode: VNode | null,
157157
throwInDev = true,
158+
throwInProd = false,
158159
) {
159160
if (__DEV__) {
160161
const info = ErrorTypeStrings[type]
@@ -171,6 +172,8 @@ function logError(
171172
} else if (!__TEST__) {
172173
console.error(err)
173174
}
175+
} else if (throwInProd) {
176+
throw err
174177
} else {
175178
// recover in prod to reduce the impact on end-user
176179
console.error(err)

0 commit comments

Comments
 (0)
Please sign in to comment.