feat(hydration): handle consecutive prepend

This commit is contained in:
daiwei 2025-07-31 09:28:18 +08:00
parent 80e6ea8730
commit 6606cbdb4b
3 changed files with 59 additions and 1 deletions

View File

@ -2511,6 +2511,56 @@ describe('Vapor Mode hydration', () => {
)
})
test('consecutive slots prepend', async () => {
const data = reactive({
msg1: 'foo',
msg2: 'bar',
msg3: 'baz',
})
const { container } = await testHydration(
`<template>
<components.Child>
<template #foo>
<span>{{data.msg1}}</span>
</template>
<template #bar>
<span>{{data.msg2}}</span>
</template>
</components.Child>
</template>`,
{
Child: `<template>
<div>
<slot name="foo"/>
<slot name="bar"/>
<div>{{data.msg3}}</div>
</div>
</template>`,
},
data,
)
expect(container.innerHTML).toBe(
`<div>` +
`<!--[--><span>foo</span><!--]--><!--${slotAnchorLabel}-->` +
`<!--[--><span>bar</span><!--]--><!--${slotAnchorLabel}-->` +
`<div>baz</div>` +
`</div>`,
)
data.msg1 = 'hello'
data.msg2 = 'vapor'
await nextTick()
expect(container.innerHTML).toBe(
`<div>` +
`<!--[--><span>hello</span><!--]--><!--${slotAnchorLabel}-->` +
`<!--[--><span>vapor</span><!--]--><!--${slotAnchorLabel}-->` +
`<div>baz</div>` +
`</div>`,
)
})
test('slot fallback', async () => {
const data = reactive({
foo: 'foo',

View File

@ -7,6 +7,7 @@ import {
} from '../insertionState'
import {
__next,
__nthChild,
_nthChild,
disableHydrationNodeLookup,
enableHydrationNodeLookup,
@ -39,6 +40,7 @@ function performHydration<T>(
// optimize anchor cache lookup
;(Comment.prototype as any).$fe = undefined
;(Node.prototype as any).$dp = undefined
;(Node.prototype as any).$np = undefined
isOptimized = true
}
enableHydrationNodeLookup()
@ -122,7 +124,9 @@ function locateHydrationNodeImpl(isFragment?: boolean): void {
let node: Node | null
// prepend / firstChild
if (insertionAnchor === 0) {
node = insertionParent!.firstChild
const n = insertionParent!.$np || 0
node = __nthChild(insertionParent!, n)
insertionParent!.$np = n + 1
} else if (insertionAnchor) {
// `insertionAnchor` is a Node, it is the DOM node to hydrate
// Template: `...<span/><!----><span/>...`// `insertionAnchor` is the placeholder

View File

@ -12,6 +12,10 @@ export let insertionParent:
// const n4 = t0(2) // n4.$dp = 2
// The first 2 nodes are static, dynamic nodes start from index 2
$dp?: number
// number of prepends - hydration only
// consecutive prepends need to skip nodes that were prepended earlier
// each prepend increases the value of $prepend
$np?: number
})
| undefined
export let insertionAnchor: Node | 0 | undefined