feat(hydration): handle consecutive if node
ci / test (push) Has been cancelled Details
ci / continuous-release (push) Has been cancelled Details

This commit is contained in:
daiwei 2025-07-30 14:39:56 +08:00
parent 5ee1628687
commit 85693f0eb3
3 changed files with 38 additions and 8 deletions

View File

@ -1269,6 +1269,26 @@ describe('Vapor Mode hydration', () => {
expect(container.innerHTML).toBe(`<!--${anchorLabel}-->`)
})
test('consecutive if node', async () => {
const data = ref(true)
const { container } = await testHydration(
`<template>
<components.Child v-if="data"/>
</template>`,
{ Child: `<template><div v-if="data">foo</div></template>` },
data,
)
expect(container.innerHTML).toBe(`<div>foo</div><!--if--><!--if-->`)
data.value = false
await nextTick()
expect(container.innerHTML).toBe(`<!--if-->`)
data.value = true
await nextTick()
expect(container.innerHTML).toBe(`<div>foo</div><!--if--><!--if-->`)
})
test('v-if/else-if/else chain on component - switch branches', async () => {
const data = ref('a')
const { container } = await testHydration(

View File

@ -8,6 +8,7 @@ import {
import { createComment, createTextNode } from './dom/node'
import { EffectScope, setActiveSub } from '@vue/reactivity'
import {
advanceHydrationNode,
currentHydrationNode,
isComment,
isHydrating,
@ -41,12 +42,13 @@ export class DynamicFragment extends VaporFragment {
current?: BlockFn
fallback?: BlockFn
teardown?: () => void
anchorLabel?: string
constructor(anchorLabel?: string) {
super([])
if (isHydrating) {
this.anchorLabel = anchorLabel
locateHydrationNode()
this.hydrate(anchorLabel!)
} else {
this.anchor =
__DEV__ && anchorLabel ? createComment(anchorLabel) : createTextNode()
@ -55,12 +57,13 @@ export class DynamicFragment extends VaporFragment {
update(render?: BlockFn, key: any = render): void {
if (key === this.current) {
if (isHydrating) this.hydrate(this.anchorLabel!)
return
}
this.current = key
const prevSub = setActiveSub()
const parent = this.anchor.parentNode
const parent = isHydrating ? null : this.anchor.parentNode
// teardown previous branch
if (this.scope) {
@ -89,6 +92,8 @@ export class DynamicFragment extends VaporFragment {
}
setActiveSub(prevSub)
if (isHydrating) this.hydrate(this.anchorLabel!)
}
hydrate(label: string): void {
@ -105,6 +110,7 @@ export class DynamicFragment extends VaporFragment {
throw new Error(`${label} fragment anchor node was not found.`)
}
}
advanceHydrationNode(this.anchor)
}
}

View File

@ -19,6 +19,10 @@ export function setCurrentHydrationNode(node: Node | null): void {
currentHydrationNode = node
}
export function advanceHydrationNode(node: Node): void {
setCurrentHydrationNode(node.nextSibling || node.parentNode)
}
let isOptimized = false
function performHydration<T>(
@ -96,7 +100,7 @@ function adoptTemplateImpl(node: Node, template: string): Node | null {
}
}
currentHydrationNode = node.nextSibling
advanceHydrationNode(node)
return node
}
@ -169,12 +173,12 @@ export function isNonHydrationNode(node: Node): boolean {
export function locateVaporFragmentAnchor(
node: Node,
anchorLabel: string,
): Comment | undefined {
let n = node.nextSibling
while (n) {
if (isComment(n, anchorLabel)) return n
n = n.nextSibling
): Comment | null {
while (node) {
if (isComment(node, anchorLabel)) return node
node = node.nextSibling!
}
return null
}
export function isEmptyTextNode(node: Node): node is Text {