]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(runtime-core): add app.config.throwUnhandledErrorInProduction
authorEvan You <evan@vuejs.org>
Wed, 17 Jul 2024 02:05:09 +0000 (10:05 +0800)
committerEvan You <evan@vuejs.org>
Wed, 17 Jul 2024 02:05:09 +0000 (10:05 +0800)
close #7876

packages/runtime-core/__tests__/apiCreateApp.spec.ts
packages/runtime-core/src/apiCreateApp.ts
packages/runtime-core/src/errorHandling.ts

index 0d7c338031143b3f062042fef50af2ee5d860a71..d8576faa8982a40ac86bcfe3e00bcd489406c49b 100644 (file)
@@ -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() {
index 3b6c047f3cbf93f50fe8828f44b0e134b0417a8c..ddf5888e845d7253ef07284334af4eb04e69bd68 100644 (file)
@@ -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 {
index beddea5f6442c8170d4ea0e25a2c7a192c9c069b..d983999b213e070ff731f9fe7b35b30aced1c90c 100644 (file)
@@ -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)