diff --git a/packages/runtime-vapor/__tests__/apiCreateDynamicComponent.spec.ts b/packages/runtime-vapor/__tests__/apiCreateDynamicComponent.spec.ts
index 89514e17701..1ee50e78591 100644
--- a/packages/runtime-vapor/__tests__/apiCreateDynamicComponent.spec.ts
+++ b/packages/runtime-vapor/__tests__/apiCreateDynamicComponent.spec.ts
@@ -148,4 +148,35 @@ describe('api: createDynamicComponent', () => {
await nextTick()
expect(html()).toBe('
')
})
+
+ test('fallback with dynamic slots', async () => {
+ const slotName = ref('default')
+ const { html } = define({
+ setup() {
+ return createDynamicComponent(() => 'div', null, {
+ $: [
+ () => ({
+ name: slotName.value,
+ fn: () => template('hi')(),
+ }),
+ ] as any,
+ })
+ },
+ }).render()
+
+ expect(html()).toBe(
+ 'hi
',
+ )
+
+ // update slot name
+ slotName.value = 'custom'
+ await nextTick()
+ expect(html()).toBe('')
+
+ slotName.value = 'default'
+ await nextTick()
+ expect(html()).toBe(
+ 'hi
',
+ )
+ })
})
diff --git a/packages/runtime-vapor/__tests__/hydration.spec.ts b/packages/runtime-vapor/__tests__/hydration.spec.ts
index 17174408eee..7094a0f60af 100644
--- a/packages/runtime-vapor/__tests__/hydration.spec.ts
+++ b/packages/runtime-vapor/__tests__/hydration.spec.ts
@@ -1065,6 +1065,34 @@ describe('Vapor Mode hydration', () => {
`,
)
})
+
+ test('dynamic component fallback with dynamic slots', async () => {
+ const data = ref({
+ name: 'default',
+ msg: 'foo',
+ })
+ const { container } = await testHydration(
+ `
+
+
+ {{ data.msg }}
+
+
+ `,
+ {},
+ data,
+ )
+
+ expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
+ `"foo
"`,
+ )
+
+ data.value.msg = 'bar'
+ await nextTick()
+ expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
+ `"bar
"`,
+ )
+ })
})
describe('if', () => {
diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts
index e850f08932d..d083d7404cb 100644
--- a/packages/runtime-vapor/src/component.ts
+++ b/packages/runtime-vapor/src/component.ts
@@ -665,9 +665,16 @@ export function createComponentWithFallback(
setCurrentHydrationNode(el.firstChild)
}
if (rawSlots.$) {
- // TODO dynamic slot fragment
+ // ssr output does not contain the slot anchor, use an empty string
+ // as the anchor label to avoid slot anchor search errors
+ const frag = new DynamicFragment(
+ isHydrating ? '' : __DEV__ ? 'slot' : undefined,
+ )
+ renderEffect(() => frag.update(getSlot(rawSlots as RawSlots, 'default')))
+ if (!isHydrating) insert(frag, el)
} else {
- insert(getSlot(rawSlots as RawSlots, 'default')!(), el)
+ const block = getSlot(rawSlots as RawSlots, 'default')!()
+ if (!isHydrating) insert(block, el)
}
if (isHydrating) {
setCurrentHydrationNode(nextNode)