diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
index 58076fff9..f54137724 100644
--- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts
+++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
@@ -502,5 +502,64 @@ describe('component: slots', () => {
await nextTick()
expect(host.innerHTML).toBe('
')
})
+
+ test('render fallback when slot content is not valid', async () => {
+ const Child = {
+ setup() {
+ return createSlot('default', null, () =>
+ document.createTextNode('fallback'),
+ )
+ },
+ }
+
+ const { html } = define({
+ setup() {
+ return createComponent(Child, null, {
+ default: () => {
+ return template('')()
+ },
+ })
+ },
+ }).render()
+
+ expect(html()).toBe('fallback')
+ })
+
+ test('render fallback when v-if condition is false', async () => {
+ const Child = {
+ setup() {
+ return createSlot('default', null, () =>
+ document.createTextNode('fallback'),
+ )
+ },
+ }
+
+ const toggle = ref(false)
+
+ const { html } = define({
+ setup() {
+ return createComponent(Child, null, {
+ default: () => {
+ return createIf(
+ () => toggle.value,
+ () => {
+ return document.createTextNode('content')
+ },
+ )
+ },
+ })
+ },
+ }).render()
+
+ expect(html()).toBe('fallback')
+
+ toggle.value = true
+ await nextTick()
+ expect(html()).toBe('content')
+
+ toggle.value = false
+ await nextTick()
+ expect(html()).toBe('fallback')
+ })
})
})
diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts
index 100c99cdb..907f7a3b7 100644
--- a/packages/runtime-vapor/src/componentSlots.ts
+++ b/packages/runtime-vapor/src/componentSlots.ts
@@ -1,5 +1,11 @@
import { EMPTY_OBJ, NO, hasOwn, isArray, isFunction } from '@vue/shared'
-import { type Block, type BlockFn, DynamicFragment, insert } from './block'
+import {
+ type Block,
+ type BlockFn,
+ DynamicFragment,
+ insert,
+ isValidBlock,
+} from './block'
import { rawPropsProxyHandlers } from './componentProps'
import { currentInstance, isRef } from '@vue/runtime-dom'
import type { LooseRawProps, VaporComponentInstance } from './component'
@@ -126,6 +132,7 @@ export function createSlot(
const renderSlot = () => {
const slot = getSlot(rawSlots, isFunction(name) ? name() : name)
if (slot) {
+ fragment.fallback = fallback
// create and cache bound version of the slot to make it stable
// so that we avoid unnecessary updates if it resolves to the same slot
fragment.update(
@@ -133,7 +140,13 @@ export function createSlot(
(slot._bound = () => {
const slotContent = slot(slotProps)
if (slotContent instanceof DynamicFragment) {
- slotContent.fallback = fallback
+ // render fallback if slot content is not a valid block
+ if (
+ (slotContent.fallback = fallback) &&
+ !isValidBlock(slotContent.nodes)
+ ) {
+ slotContent.update(fallback)
+ }
}
return slotContent
}),