expect(app.config.errorHandler).not.toHaveBeenCalled()
}
})
+
+ // #11336
+ test('should bail manually rendered compiler slots for both mount and update (2)', async () => {
+ // only reproducible in prod
+ __DEV__ = false
+ const n = ref(0)
+ function Outer(_: any, { slots }: any) {
+ n.value // track
+ return slots.default()
+ }
+ const Mid = {
+ render(ctx: any) {
+ return (
+ openBlock(),
+ createElementBlock('div', null, [renderSlot(ctx.$slots, 'default')])
+ )
+ },
+ }
+ const show = ref(false)
+ const App = {
+ render() {
+ return (
+ openBlock(),
+ createBlock(Outer, null, {
+ default: withCtx(() => [
+ createVNode(Mid, null, {
+ default: withCtx(() => [
+ createElementVNode('div', null, [
+ show.value
+ ? (openBlock(),
+ createElementBlock('div', { key: 0 }, '1'))
+ : createCommentVNode('v-if', true),
+ createElementVNode('div', null, '2'),
+ createElementVNode('div', null, '3'),
+ ]),
+ createElementVNode('div', null, '4'),
+ ]),
+ _: 1 /* STABLE */,
+ }),
+ ]),
+ _: 1 /* STABLE */,
+ })
+ )
+ },
+ }
+
+ const app = createApp(App)
+ app.config.errorHandler = vi.fn()
+
+ try {
+ app.mount(root)
+
+ // force Outer update, which will assign new slots to Mid
+ // we want to make sure the compiled slot flag doesn't accidentally
+ // get assigned again
+ n.value++
+ await nextTick()
+
+ show.value = true
+ await nextTick()
+ } finally {
+ __DEV__ = true
+ expect(app.config.errorHandler).not.toHaveBeenCalled()
+ }
+ })
})
ShapeFlags,
SlotFlags,
def,
- extend,
isArray,
isFunction,
} from '@vue/shared'
instance.slots.default = () => normalized
}
+const assignSlots = (
+ slots: InternalSlots,
+ children: Slots,
+ optimized: boolean,
+) => {
+ for (const key in children) {
+ // #2893
+ // when rendering the optimized slots by manually written render function,
+ // do not copy the `slots._` compiler flag so that `renderSlot` creates
+ // slot Fragment with BAIL patchFlag to force full updates
+ if (optimized || key !== '_') {
+ slots[key] = children[key]
+ }
+ }
+}
+
export const initSlots = (
instance: ComponentInternalInstance,
children: VNodeNormalizedChildren,
if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
const type = (children as RawSlots)._
if (type) {
- extend(slots, children as InternalSlots)
+ assignSlots(slots, children as Slots, optimized)
// make compiler marker non-enumerable
if (optimized) {
def(slots, '_', type, true)
- } else {
- // #2893
- // when rendering the optimized slots by manually written render function,
- // we need to delete the `slots._` flag if necessary to make subsequent
- // updates reliable, i.e. let the `renderSlot` create the bailed Fragment
- delete slots._
}
} else {
normalizeObjectSlots(children as RawSlots, slots, instance)
if (__DEV__ && isHmrUpdating) {
// Parent was HMR updated so slot content may have changed.
// force update slots and mark instance for hmr as well
- extend(slots, children as Slots)
+ assignSlots(slots, children as Slots, optimized)
trigger(instance, TriggerOpTypes.SET, '$slots')
} else if (optimized && type === SlotFlags.STABLE) {
// compiled AND stable.
} else {
// compiled but dynamic (v-if/v-for on slots) - update slots, but skip
// normalization.
- extend(slots, children as Slots)
+ assignSlots(slots, children as Slots, optimized)
}
} else {
needDeletionCheck = !(children as RawSlots).$stable