diff --git a/packages/runtime-core/__tests__/errorHandling.spec.ts b/packages/runtime-core/__tests__/errorHandling.spec.ts index 085127677ba..cf148bd2fe6 100644 --- a/packages/runtime-core/__tests__/errorHandling.spec.ts +++ b/packages/runtime-core/__tests__/errorHandling.spec.ts @@ -1,4 +1,5 @@ import { + type VNode, createApp, defineComponent, h, @@ -11,6 +12,7 @@ import { watch, watchEffect, } from '@vue/runtime-test' +import { ErrorCodes, ErrorTypeStrings } from '../src/errorHandling' describe('error handling', () => { test('propagation', () => { @@ -609,5 +611,33 @@ describe('error handling', () => { expect(handler).toHaveBeenCalledTimes(1) }) + test('errors in scheduler job with owner instance should be caught', async () => { + let vnode: VNode + const x = ref(0) + const app = createApp({ + render() { + return (vnode = vnode || h('div', x.value)) + }, + }) + + app.config.errorHandler = vi.fn() + app.mount(nodeOps.createElement('div')) + + const error = new Error('error') + Object.defineProperty(vnode!, 'el', { + get() { + throw error + }, + }) + + x.value++ + await nextTick() + expect(app.config.errorHandler).toHaveBeenCalledWith( + error, + {}, + ErrorTypeStrings[ErrorCodes.COMPONENT_UPDATE], + ) + }) + // native event handler handling should be tested in respective renderers }) diff --git a/packages/runtime-core/src/errorHandling.ts b/packages/runtime-core/src/errorHandling.ts index 41c92cbd34a..d243db5bffd 100644 --- a/packages/runtime-core/src/errorHandling.ts +++ b/packages/runtime-core/src/errorHandling.ts @@ -23,6 +23,7 @@ export enum ErrorCodes { FUNCTION_REF, ASYNC_COMPONENT_LOADER, SCHEDULER, + COMPONENT_UPDATE, } export const ErrorTypeStrings: Record = { @@ -54,16 +55,15 @@ export const ErrorTypeStrings: Record = { [ErrorCodes.APP_WARN_HANDLER]: 'app warnHandler', [ErrorCodes.FUNCTION_REF]: 'ref function', [ErrorCodes.ASYNC_COMPONENT_LOADER]: 'async component loader', - [ErrorCodes.SCHEDULER]: - 'scheduler flush. This is likely a Vue internals bug. ' + - 'Please open an issue at https://github.com/vuejs/core .', + [ErrorCodes.SCHEDULER]: 'scheduler flush', + [ErrorCodes.COMPONENT_UPDATE]: 'component update', } export type ErrorTypes = LifecycleHooks | ErrorCodes export function callWithErrorHandling( fn: Function, - instance: ComponentInternalInstance | null, + instance: ComponentInternalInstance | null | undefined, type: ErrorTypes, args?: unknown[], ) { @@ -105,7 +105,7 @@ export function callWithAsyncErrorHandling( export function handleError( err: unknown, - instance: ComponentInternalInstance | null, + instance: ComponentInternalInstance | null | undefined, type: ErrorTypes, throwInDev = true, ) { diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 1f36502c713..3f4a6f4a265 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1587,6 +1587,7 @@ function baseCreateRenderer( effect.run() } }) + update.i = instance update.id = instance.uid // allowRecurse // #1801, #2043 component render effects should allow recursive updates @@ -1599,7 +1600,6 @@ function baseCreateRenderer( effect.onTrigger = instance.rtg ? e => invokeArrayFns(instance.rtg!, e) : void 0 - update.ownerInstance = instance } update() diff --git a/packages/runtime-core/src/scheduler.ts b/packages/runtime-core/src/scheduler.ts index 4ae1c6d46e7..c1f9d4c9141 100644 --- a/packages/runtime-core/src/scheduler.ts +++ b/packages/runtime-core/src/scheduler.ts @@ -26,9 +26,8 @@ export interface SchedulerJob extends Function { /** * Attached by renderer.ts when setting up a component's render effect * Used to obtain component information when reporting max recursive updates. - * dev only. */ - ownerInstance?: ComponentInternalInstance + i?: ComponentInternalInstance } export type SchedulerJobs = SchedulerJob | SchedulerJob[] @@ -240,7 +239,11 @@ function flushJobs(seen?: CountMap) { if (__DEV__ && check(job)) { continue } - callWithErrorHandling(job, null, ErrorCodes.SCHEDULER) + callWithErrorHandling( + job, + job.i, + job.i ? ErrorCodes.COMPONENT_UPDATE : ErrorCodes.SCHEDULER, + ) } } } finally { @@ -265,7 +268,7 @@ function checkRecursiveUpdates(seen: CountMap, fn: SchedulerJob) { } else { const count = seen.get(fn)! if (count > RECURSION_LIMIT) { - const instance = fn.ownerInstance + const instance = fn.i const componentName = instance && getComponentName(instance.type) handleError( `Maximum recursive updates exceeded${