fix(runtime-vapor): render slot fallback if slot content is not a valid block

close #13668
This commit is contained in:
daiwei 2025-07-21 08:29:31 +08:00
parent 56a7f9dd18
commit 8602be22fd
2 changed files with 74 additions and 2 deletions

View File

@ -502,5 +502,64 @@ describe('component: slots', () => {
await nextTick() await nextTick()
expect(host.innerHTML).toBe('<div><h1></h1><!--slot--></div>') expect(host.innerHTML).toBe('<div><h1></h1><!--slot--></div>')
}) })
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('<!--comment-->')()
},
})
},
}).render()
expect(html()).toBe('fallback<!--slot-->')
})
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<!--if--><!--slot-->')
toggle.value = true
await nextTick()
expect(html()).toBe('content<!--if--><!--slot-->')
toggle.value = false
await nextTick()
expect(html()).toBe('fallback<!--if--><!--slot-->')
})
}) })
}) })

View File

@ -1,5 +1,11 @@
import { EMPTY_OBJ, NO, hasOwn, isArray, isFunction } from '@vue/shared' 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 { rawPropsProxyHandlers } from './componentProps'
import { currentInstance, isRef } from '@vue/runtime-dom' import { currentInstance, isRef } from '@vue/runtime-dom'
import type { LooseRawProps, VaporComponentInstance } from './component' import type { LooseRawProps, VaporComponentInstance } from './component'
@ -126,6 +132,7 @@ export function createSlot(
const renderSlot = () => { const renderSlot = () => {
const slot = getSlot(rawSlots, isFunction(name) ? name() : name) const slot = getSlot(rawSlots, isFunction(name) ? name() : name)
if (slot) { if (slot) {
fragment.fallback = fallback
// create and cache bound version of the slot to make it stable // 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 // so that we avoid unnecessary updates if it resolves to the same slot
fragment.update( fragment.update(
@ -133,7 +140,13 @@ export function createSlot(
(slot._bound = () => { (slot._bound = () => {
const slotContent = slot(slotProps) const slotContent = slot(slotProps)
if (slotContent instanceof DynamicFragment) { 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 return slotContent
}), }),