]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(hydration): handle consecutive if node edison/feat/vaporHydration 13226/head
authordaiwei <daiwei521@126.com>
Wed, 30 Jul 2025 06:39:56 +0000 (14:39 +0800)
committerdaiwei <daiwei521@126.com>
Wed, 30 Jul 2025 06:41:54 +0000 (14:41 +0800)
packages/runtime-vapor/__tests__/hydration.spec.ts
packages/runtime-vapor/src/block.ts
packages/runtime-vapor/src/dom/hydration.ts

index ffe2e8f098e9ff0fb6af9ca3353cbdbfc3279e31..5aba438ded9b10793048f782e62d22f1b2c9e2df 100644 (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(
index dc6ae4666551b8d88e8cf0970d53ea7bc7cfdb34..c1be8202ae0bf0e3a5ceaef0d1e68b2ba64eeddc 100644 (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)
   }
 }
 
index 6730f1e57ffc93cea0a16c41d5d7e6e06ce98656..1e12a071971c1415bcc353f739e626a385177385 100644 (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 {