]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core): more edge case fix for manually rendered compiled slot
authorEvan You <evan@vuejs.org>
Fri, 12 Jul 2024 09:29:42 +0000 (17:29 +0800)
committerEvan You <evan@vuejs.org>
Fri, 12 Jul 2024 09:29:42 +0000 (17:29 +0800)
close #11336

packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts
packages/runtime-core/src/componentSlots.ts

index 16ea42ed34243696d05475fc653f2a0584fa0120..dceda28fcae868ed3f0e429a49053b6d61903c54 100644 (file)
@@ -1040,4 +1040,69 @@ describe('renderer: optimized mode', () => {
       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()
+    }
+  })
 })
index 0145d557b067a94621f0f175ddd4bf4c6dcbc5cf..438c56efb4702281244f67102ab315d3764ab854 100644 (file)
@@ -12,7 +12,6 @@ import {
   ShapeFlags,
   SlotFlags,
   def,
-  extend,
   isArray,
   isFunction,
 } from '@vue/shared'
@@ -161,6 +160,22 @@ const normalizeVNodeSlots = (
   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,
@@ -170,16 +185,10 @@ export const initSlots = (
   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)
@@ -204,7 +213,7 @@ export const updateSlots = (
       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.
@@ -213,7 +222,7 @@ export const updateSlots = (
       } 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