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}-->`) 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 () => { test('v-if/else-if/else chain on component - switch branches', async () => {
const data = ref('a') const data = ref('a')
const { container } = await testHydration( const { container } = await testHydration(

View File

@ -8,6 +8,7 @@ import {
import { createComment, createTextNode } from './dom/node' import { createComment, createTextNode } from './dom/node'
import { EffectScope, setActiveSub } from '@vue/reactivity' import { EffectScope, setActiveSub } from '@vue/reactivity'
import { import {
advanceHydrationNode,
currentHydrationNode, currentHydrationNode,
isComment, isComment,
isHydrating, isHydrating,
@ -41,12 +42,13 @@ export class DynamicFragment extends VaporFragment {
current?: BlockFn current?: BlockFn
fallback?: BlockFn fallback?: BlockFn
teardown?: () => void teardown?: () => void
anchorLabel?: string
constructor(anchorLabel?: string) { constructor(anchorLabel?: string) {
super([]) super([])
if (isHydrating) { if (isHydrating) {
this.anchorLabel = anchorLabel
locateHydrationNode() locateHydrationNode()
this.hydrate(anchorLabel!)
} else { } else {
this.anchor = this.anchor =
__DEV__ && anchorLabel ? createComment(anchorLabel) : createTextNode() __DEV__ && anchorLabel ? createComment(anchorLabel) : createTextNode()
@ -55,12 +57,13 @@ export class DynamicFragment extends VaporFragment {
update(render?: BlockFn, key: any = render): void { update(render?: BlockFn, key: any = render): void {
if (key === this.current) { if (key === this.current) {
if (isHydrating) this.hydrate(this.anchorLabel!)
return return
} }
this.current = key this.current = key
const prevSub = setActiveSub() const prevSub = setActiveSub()
const parent = this.anchor.parentNode const parent = isHydrating ? null : this.anchor.parentNode
// teardown previous branch // teardown previous branch
if (this.scope) { if (this.scope) {
@ -89,6 +92,8 @@ export class DynamicFragment extends VaporFragment {
} }
setActiveSub(prevSub) setActiveSub(prevSub)
if (isHydrating) this.hydrate(this.anchorLabel!)
} }
hydrate(label: string): void { hydrate(label: string): void {
@ -105,6 +110,7 @@ export class DynamicFragment extends VaporFragment {
throw new Error(`${label} fragment anchor node was not found.`) 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 currentHydrationNode = node
} }
export function advanceHydrationNode(node: Node): void {
setCurrentHydrationNode(node.nextSibling || node.parentNode)
}
let isOptimized = false let isOptimized = false
function performHydration<T>( function performHydration<T>(
@ -96,7 +100,7 @@ function adoptTemplateImpl(node: Node, template: string): Node | null {
} }
} }
currentHydrationNode = node.nextSibling advanceHydrationNode(node)
return node return node
} }
@ -169,12 +173,12 @@ export function isNonHydrationNode(node: Node): boolean {
export function locateVaporFragmentAnchor( export function locateVaporFragmentAnchor(
node: Node, node: Node,
anchorLabel: string, anchorLabel: string,
): Comment | undefined { ): Comment | null {
let n = node.nextSibling while (node) {
while (n) { if (isComment(node, anchorLabel)) return node
if (isComment(n, anchorLabel)) return n node = node.nextSibling!
n = n.nextSibling
} }
return null
} }
export function isEmptyTextNode(node: Node): node is Text { export function isEmptyTextNode(node: Node): node is Text {