Browse Source

fix(runtime-core): bail manually rendered compiler slot fragments in all cases

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
Evan You 1 year ago
parent
commit
3d34f406ac

+ 15 - 2
packages/runtime-core/__tests__/componentSlots.spec.ts

@@ -7,7 +7,7 @@ import {
   ref,
   ref,
   render,
   render,
 } from '@vue/runtime-test'
 } from '@vue/runtime-test'
-import { normalizeVNode } from '../src/vnode'
+import { createBlock, normalizeVNode } from '../src/vnode'
 import { createSlots } from '../src/helpers/createSlots'
 import { createSlots } from '../src/helpers/createSlots'
 
 
 describe('component: slots', () => {
 describe('component: slots', () => {
@@ -25,8 +25,21 @@ describe('component: slots', () => {
   }
   }
 
 
   test('initSlots: instance.slots should be set correctly', () => {
   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 })
     const { slots } = renderWithSlots({ _: 1 })
-    expect(slots).toMatchObject({ _: 1 })
+    expect(slots).toMatchObject({})
   })
   })
 
 
   test('initSlots: should normalize object slots (when value is null, string, array)', () => {
   test('initSlots: should normalize object slots (when value is null, string, array)', () => {

+ 4 - 1
packages/runtime-core/__tests__/rendererOptimizedMode.spec.ts

@@ -434,7 +434,7 @@ describe('renderer: optimized mode', () => {
     const App = {
     const App = {
       setup() {
       setup() {
         return () => {
         return () => {
-          return createVNode(Comp, null, {
+          return createBlock(Comp, null, {
             default: withCtx(() => [
             default: withCtx(() => [
               createVNode('p', null, foo.value, PatchFlags.TEXT),
               createVNode('p', null, foo.value, PatchFlags.TEXT),
             ]),
             ]),
@@ -560,6 +560,7 @@ describe('renderer: optimized mode', () => {
     const state = ref(0)
     const state = ref(0)
 
 
     const CompA = {
     const CompA = {
+      name: 'A',
       setup(props: any, { slots }: SetupContext) {
       setup(props: any, { slots }: SetupContext) {
         return () => {
         return () => {
           return (
           return (
@@ -571,6 +572,7 @@ describe('renderer: optimized mode', () => {
     }
     }
 
 
     const Wrapper = {
     const Wrapper = {
+      name: 'Wrapper',
       setup(props: any, { slots }: SetupContext) {
       setup(props: any, { slots }: SetupContext) {
         // use the manually written render function to rendering the optimized slots,
         // use the manually written render function to rendering the optimized slots,
         // which should make subsequent updates exit the optimized mode correctly
         // which should make subsequent updates exit the optimized mode correctly
@@ -581,6 +583,7 @@ describe('renderer: optimized mode', () => {
     }
     }
 
 
     const app = createApp({
     const app = createApp({
+      name: 'App',
       setup() {
       setup() {
         return () => {
         return () => {
           return (
           return (

+ 2 - 1
packages/runtime-core/src/component.ts

@@ -736,13 +736,14 @@ export let isInSSRComponentSetup = false
 export function setupComponent(
 export function setupComponent(
   instance: ComponentInternalInstance,
   instance: ComponentInternalInstance,
   isSSR = false,
   isSSR = false,
+  optimized = false,
 ) {
 ) {
   isSSR && setInSSRSetupState(isSSR)
   isSSR && setInSSRSetupState(isSSR)
 
 
   const { props, children } = instance.vnode
   const { props, children } = instance.vnode
   const isStateful = isStatefulComponent(instance)
   const isStateful = isStatefulComponent(instance)
   initProps(instance, props, isStateful, isSSR)
   initProps(instance, props, isStateful, isSSR)
-  initSlots(instance, children)
+  initSlots(instance, children, optimized)
 
 
   const setupResult = isStateful
   const setupResult = isStateful
     ? setupStatefulComponent(instance, isSSR)
     ? setupStatefulComponent(instance, isSSR)

+ 10 - 8
packages/runtime-core/src/componentSlots.ts

@@ -164,6 +164,7 @@ const normalizeVNodeSlots = (
 export const initSlots = (
 export const initSlots = (
   instance: ComponentInternalInstance,
   instance: ComponentInternalInstance,
   children: VNodeNormalizedChildren,
   children: VNodeNormalizedChildren,
+  optimized: boolean,
 ) => {
 ) => {
   const slots = (instance.slots = createInternalObject())
   const slots = (instance.slots = createInternalObject())
   if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
   if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
@@ -171,7 +172,15 @@ export const initSlots = (
     if (type) {
     if (type) {
       extend(slots, children as InternalSlots)
       extend(slots, children as InternalSlots)
       // make compiler marker non-enumerable
       // 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 {
     } else {
       normalizeObjectSlots(children as RawSlots, slots, instance)
       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
         // compiled but dynamic (v-if/v-for on slots) - update slots, but skip
         // normalization.
         // normalization.
         extend(slots, children as Slots)
         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 {
     } else {
       needDeletionCheck = !(children as RawSlots).$stable
       needDeletionCheck = !(children as RawSlots).$stable

+ 1 - 1
packages/runtime-core/src/renderer.ts

@@ -1229,7 +1229,7 @@ function baseCreateRenderer(
       if (__DEV__) {
       if (__DEV__) {
         startMeasure(instance, `init`)
         startMeasure(instance, `init`)
       }
       }
-      setupComponent(instance)
+      setupComponent(instance, false, optimized)
       if (__DEV__) {
       if (__DEV__) {
         endMeasure(instance, `init`)
         endMeasure(instance, `init`)
       }
       }