Просмотр исходного кода

fix(compiler-vapor): bind dynamic slot sources to owner

daiwei 1 месяц назад
Родитель
Сommit
6b1d3175ed

+ 20 - 0
packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap

@@ -624,6 +624,26 @@ export function render(_ctx) {
 }"
 }"
 `;
 `;
 
 
+exports[`compiler: transform slot > withVaporCtx optimization > dynamic slot source with slot outlet should have withVaporCtx 1`] = `
+"import { resolveComponent as _resolveComponent, createSlot as _createSlot, withVaporCtx as _withVaporCtx, createForSlots as _createForSlots, createComponentWithFallback as _createComponentWithFallback } from 'vue';
+
+export function render(_ctx) {
+  const _component_Comp = _resolveComponent("Comp")
+  const n2 = _createComponentWithFallback(_component_Comp, null, {
+    $: [
+      _withVaporCtx(() => (_createForSlots(_ctx.slots, (_, name) => ({
+        name: name,
+        fn: _withVaporCtx(() => {
+          const n0 = _createSlot(() => (name), null)
+          return n0
+        })
+      }))))
+    ]
+  }, true)
+  return n2
+}"
+`;
+
 exports[`compiler: transform slot > withVaporCtx optimization > slot with component inside v-for should have withVaporCtx 1`] = `
 exports[`compiler: transform slot > withVaporCtx optimization > slot with component inside v-for should have withVaporCtx 1`] = `
 "import { resolveComponent as _resolveComponent, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, createFor as _createFor, withVaporCtx as _withVaporCtx, template as _template } from 'vue';
 "import { resolveComponent as _resolveComponent, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, createFor as _createFor, withVaporCtx as _withVaporCtx, template as _template } from 'vue';
 const t0 = _template("<div>")
 const t0 = _template("<div>")

+ 14 - 0
packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts

@@ -818,6 +818,20 @@ describe('compiler: transform slot', () => {
       expect(code).toMatchSnapshot()
       expect(code).toMatchSnapshot()
     })
     })
 
 
+    test('dynamic slot source with slot outlet should have withVaporCtx', () => {
+      const { code } = compileWithSlots(`
+        <Comp>
+          <template v-for="(_, name) in slots" #[name]>
+            <slot :name="name" />
+          </template>
+        </Comp>
+      `)
+      expect(code).toContain(`$: [
+      _withVaporCtx(() => (_createForSlots`)
+      expect(code).toContain(`fn: _withVaporCtx(() =>`)
+      expect(code).toMatchSnapshot()
+    })
+
     test('slot with component inside v-if should have withVaporCtx', () => {
     test('slot with component inside v-if should have withVaporCtx', () => {
       const { code } = compileWithSlots(`
       const { code } = compileWithSlots(`
         <Comp>
         <Comp>

+ 19 - 1
packages/compiler-vapor/src/generators/component.ts

@@ -488,7 +488,25 @@ function genDynamicSlot(
       frag = genConditionalSlot(slot, context)
       frag = genConditionalSlot(slot, context)
       break
       break
   }
   }
-  return withFunction ? ['() => (', ...frag, ')'] : frag
+  if (!withFunction) return frag
+
+  return needsDynamicSlotSourceCtx(slot)
+    ? [`${context.helper('withVaporCtx')}(() => (`, ...frag, '))']
+    : ['() => (', ...frag, ')']
+}
+
+function needsDynamicSlotSourceCtx(slot: IRSlotDynamic): boolean {
+  switch (slot.slotType) {
+    case IRSlotType.DYNAMIC:
+      return needsVaporCtx(slot.fn)
+    case IRSlotType.LOOP:
+      return needsVaporCtx(slot.fn)
+    case IRSlotType.CONDITIONAL:
+      return (
+        needsDynamicSlotSourceCtx(slot.positive) ||
+        (slot.negative ? needsDynamicSlotSourceCtx(slot.negative) : false)
+      )
+  }
 }
 }
 
 
 function genBasicDynamicSlot(
 function genBasicDynamicSlot(

+ 45 - 0
packages/runtime-vapor/__tests__/componentSlots.spec.ts

@@ -32,6 +32,7 @@ import {
   renderSlot,
   renderSlot,
   shallowRef,
   shallowRef,
   toDisplayString,
   toDisplayString,
+  useSlots,
 } from '@vue/runtime-dom'
 } from '@vue/runtime-dom'
 import { makeRender } from './_utils'
 import { makeRender } from './_utils'
 import type { DynamicSlot } from '../src/componentSlots'
 import type { DynamicSlot } from '../src/componentSlots'
@@ -907,6 +908,50 @@ describe('component: slots', () => {
       expect(host.innerHTML).toBe('<div>footer<!--slot--></div>')
       expect(host.innerHTML).toBe('<div>footer<!--slot--></div>')
     })
     })
 
 
+    test('dynamic slot source inside forwarded slot should preserve owner', async () => {
+      const msg = ref('late')
+      const Leaf = defineVaporComponent(() => createSlot('late', null))
+      const Carrier = defineVaporComponent(() => createSlot('default', null))
+      const Root = defineVaporComponent(() => {
+        const slots = useSlots()
+        return createComponent(Carrier, null, {
+          default: withVaporCtx(() =>
+            createComponent(Leaf, null, {
+              $: [
+                // required wrapped in withVaporCtx to preserve owner when create dynamic slot source
+                withVaporCtx(() =>
+                  createForSlots(slots, (_slot, name) => ({
+                    name,
+                    fn: withVaporCtx(() => createSlot(name)),
+                  })),
+                ) as any,
+              ],
+            }),
+          ),
+        })
+      })
+
+      const { host } = define(() =>
+        createComponent(Root, null, {
+          late: withVaporCtx(() => {
+            const n0 = template('<span></span>')()
+            renderEffect(() => setElementText(n0, msg.value))
+            return n0
+          }),
+        }),
+      ).render()
+
+      expect(host.innerHTML).toBe(
+        '<span>late</span><!--slot--><!--slot--><!--slot-->',
+      )
+
+      msg.value = 'updated'
+      await nextTick()
+      expect(host.innerHTML).toBe(
+        '<span>updated</span><!--slot--><!--slot--><!--slot-->',
+      )
+    })
+
     test('fallback should be render correctly', () => {
     test('fallback should be render correctly', () => {
       const Comp = defineVaporComponent(() => {
       const Comp = defineVaporComponent(() => {
         const n0 = template('<div></div>')()
         const n0 = template('<div></div>')()