diff --git a/packages/runtime-core/__tests__/vnode.spec.ts b/packages/runtime-core/__tests__/vnode.spec.ts index fdd23797b10..8c31f9b316f 100644 --- a/packages/runtime-core/__tests__/vnode.spec.ts +++ b/packages/runtime-core/__tests__/vnode.spec.ts @@ -12,7 +12,7 @@ import { } from '../src/vnode' import { Data } from '../src/component' import { ShapeFlags, PatchFlags } from '@vue/shared' -import { h, reactive, isReactive, setBlockTracking, ref } from '../src' +import { h, reactive, isReactive, setBlockTracking, ref, withCtx } from '../src' import { createApp, nodeOps, serializeInner } from '@vue/runtime-test' import { setCurrentRenderingInstance } from '../src/componentRenderContext' @@ -614,6 +614,29 @@ describe('vnode', () => { ])) expect(vnode.dynamicChildren).toStrictEqual([]) }) + // #5657 + test('error of slot function execution should not affect block tracking', () => { + const error = new Error('slot execution error') + let caughtError + const slot = withCtx( + () => { + throw error + }, + { type: {}, appContext: {} } as any + ) + try { + slot() + } catch (e) { + caughtError = e + } + expect(caughtError).toBe(error) + expect( + `[Vue warn]: Unhandled error during execution of slot function` + ).toHaveBeenWarned() + + const vnode = (openBlock(), createBlock('div')) + expect(vnode.dynamicChildren).toStrictEqual([]) + }) }) describe('transformVNodeArgs', () => { diff --git a/packages/runtime-core/src/componentRenderContext.ts b/packages/runtime-core/src/componentRenderContext.ts index 8f49ec251d5..3965218b205 100644 --- a/packages/runtime-core/src/componentRenderContext.ts +++ b/packages/runtime-core/src/componentRenderContext.ts @@ -1,6 +1,7 @@ import { ComponentInternalInstance } from './component' import { devtoolsComponentUpdated } from './devtools' import { setBlockTracking } from './vnode' +import { handleError, ErrorCodes } from './errorHandling' /** * mark the current rendering instance for asset resolution (e.g. @@ -89,10 +90,16 @@ export function withCtx( setBlockTracking(-1) } const prevInstance = setCurrentRenderingInstance(ctx) - const res = fn(...args) - setCurrentRenderingInstance(prevInstance) - if (renderFnWithContext._d) { - setBlockTracking(1) + let res + try { + res = fn(...args) + } catch (e) { + handleError(e, prevInstance, ErrorCodes.SLOT_FUNCTION) + } finally { + setCurrentRenderingInstance(prevInstance) + if (renderFnWithContext._d) { + setBlockTracking(1) + } } if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { diff --git a/packages/runtime-core/src/errorHandling.ts b/packages/runtime-core/src/errorHandling.ts index d190b993d43..8a3f96ecf57 100644 --- a/packages/runtime-core/src/errorHandling.ts +++ b/packages/runtime-core/src/errorHandling.ts @@ -20,7 +20,8 @@ export const enum ErrorCodes { APP_WARN_HANDLER, FUNCTION_REF, ASYNC_COMPONENT_LOADER, - SCHEDULER + SCHEDULER, + SLOT_FUNCTION } export const ErrorTypeStrings: Record = { @@ -54,7 +55,8 @@ export const ErrorTypeStrings: Record = { [ErrorCodes.ASYNC_COMPONENT_LOADER]: 'async component loader', [ErrorCodes.SCHEDULER]: 'scheduler flush. This is likely a Vue internals bug. ' + - 'Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/core' + 'Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/core', + [ErrorCodes.SLOT_FUNCTION]: 'slot function' } export type ErrorTypes = LifecycleHooks | ErrorCodes