]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(hydration): handle consecutive prepend
authordaiwei <daiwei521@126.com>
Thu, 31 Jul 2025 01:28:18 +0000 (09:28 +0800)
committerdaiwei <daiwei521@126.com>
Thu, 31 Jul 2025 01:28:18 +0000 (09:28 +0800)
packages/runtime-vapor/__tests__/hydration.spec.ts
packages/runtime-vapor/src/dom/hydration.ts
packages/runtime-vapor/src/insertionState.ts

index e854a193603b223f76cded796c7dd5d928f9bda4..8bc7689e325290ef0e772efd2c8c46ec232e4702 100644 (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',
index e551d93938b5a3059286389ee70dbbd6fde569a9..f90f153a2ad116f83da75e69f4c68e9c0a563add 100644 (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
index 5c8ba4262e76c4103bdc47aa4cfe76f829577694..f76ceb8b110ba1c8dffde4b4095803c1eec67317 100644 (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