]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core): bail manually rendered compiler slot fragments in all cases
authorEvan You <evan@vuejs.org>
Thu, 11 Jul 2024 16:34:05 +0000 (00:34 +0800)
committerEvan You <evan@vuejs.org>
Thu, 11 Jul 2024 16:34:23 +0000 (00:34 +0800)
Previously this bail was only applied on updates but not on initial mount,
and leads to different patch code paths between mount and update in edge
cases.

close #10870

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

index 09b37932147be3232e1ee242d5f44d007c1fe9f0..6042ccbd734f837b98e1e369f5c45178e091a4d7 100644 (file)
@@ -7,7 +7,7 @@ import {
   ref,
   render,
 } from '@vue/runtime-test'
-import { normalizeVNode } from '../src/vnode'
+import { createBlock, normalizeVNode } from '../src/vnode'
 import { createSlots } from '../src/helpers/createSlots'
 
 describe('component: slots', () => {
@@ -25,8 +25,21 @@ describe('component: slots', () => {
   }
 
   test('initSlots: instance.slots should be set correctly', () => {
+    let instance: any
+    const Comp = {
+      render() {
+        instance = getCurrentInstance()
+        return h('div')
+      },
+    }
+    const slots = { foo: () => {}, _: 1 }
+    render(createBlock(Comp, null, slots), nodeOps.createElement('div'))
+    expect(instance.slots).toMatchObject(slots)
+  })
+
+  test('initSlots: instance.slots should remove compiler marker if parent is using manual render function', () => {
     const { slots } = renderWithSlots({ _: 1 })
-    expect(slots).toMatchObject({ _: 1 })
+    expect(slots).toMatchObject({})
   })
 
   test('initSlots: should normalize object slots (when value is null, string, array)', () => {
index 556ab75209bab97be4f9223aae8267eeda96558a..4176f0fd411253d307dbbadc8087b24793c89958 100644 (file)
@@ -434,7 +434,7 @@ describe('renderer: optimized mode', () => {
     const App = {
       setup() {
         return () => {
-          return createVNode(Comp, null, {
+          return createBlock(Comp, null, {
             default: withCtx(() => [
               createVNode('p', null, foo.value, PatchFlags.TEXT),
             ]),
@@ -560,6 +560,7 @@ describe('renderer: optimized mode', () => {
     const state = ref(0)
 
     const CompA = {
+      name: 'A',
       setup(props: any, { slots }: SetupContext) {
         return () => {
           return (
@@ -571,6 +572,7 @@ describe('renderer: optimized mode', () => {
     }
 
     const Wrapper = {
+      name: 'Wrapper',
       setup(props: any, { slots }: SetupContext) {
         // use the manually written render function to rendering the optimized slots,
         // which should make subsequent updates exit the optimized mode correctly
@@ -581,6 +583,7 @@ describe('renderer: optimized mode', () => {
     }
 
     const app = createApp({
+      name: 'App',
       setup() {
         return () => {
           return (
index b5faa856ecafe8066b80d5165b2e4fb8aea38acf..df3a63769897221cf2b320754b098882c9fb3e81 100644 (file)
@@ -736,13 +736,14 @@ export let isInSSRComponentSetup = false
 export function setupComponent(
   instance: ComponentInternalInstance,
   isSSR = false,
+  optimized = false,
 ) {
   isSSR && setInSSRSetupState(isSSR)
 
   const { props, children } = instance.vnode
   const isStateful = isStatefulComponent(instance)
   initProps(instance, props, isStateful, isSSR)
-  initSlots(instance, children)
+  initSlots(instance, children, optimized)
 
   const setupResult = isStateful
     ? setupStatefulComponent(instance, isSSR)
index 2bc3466c459231b0098ae53f284ec1d86d2d138c..0145d557b067a94621f0f175ddd4bf4c6dcbc5cf 100644 (file)
@@ -164,6 +164,7 @@ const normalizeVNodeSlots = (
 export const initSlots = (
   instance: ComponentInternalInstance,
   children: VNodeNormalizedChildren,
+  optimized: boolean,
 ) => {
   const slots = (instance.slots = createInternalObject())
   if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
@@ -171,7 +172,15 @@ export const initSlots = (
     if (type) {
       extend(slots, children as InternalSlots)
       // make compiler marker non-enumerable
-      def(slots, '_', type, true)
+      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)
     }
@@ -205,13 +214,6 @@ export const updateSlots = (
         // compiled but dynamic (v-if/v-for on slots) - update slots, but skip
         // normalization.
         extend(slots, children as Slots)
-        // #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
-        if (!optimized && type === SlotFlags.STABLE) {
-          delete slots._
-        }
       }
     } else {
       needDeletionCheck = !(children as RawSlots).$stable
index d5c5b6d8dfb1dc45e5b367346e45f86e99f4d217..1f36502c713929c409eb6b0b6239a7bfbe6d45aa 100644 (file)
@@ -1229,7 +1229,7 @@ function baseCreateRenderer(
       if (__DEV__) {
         startMeasure(instance, `init`)
       }
-      setupComponent(instance)
+      setupComponent(instance, false, optimized)
       if (__DEV__) {
         endMeasure(instance, `init`)
       }