]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor: update logical child handling and optimize hydration state management
authordaiwei <daiwei521@126.com>
Tue, 30 Sep 2025 01:50:11 +0000 (09:50 +0800)
committerdaiwei <daiwei521@126.com>
Tue, 30 Sep 2025 02:02:47 +0000 (10:02 +0800)
packages/compiler-vapor/src/generators/operation.ts
packages/compiler-vapor/src/transforms/transformChildren.ts
packages/runtime-vapor/src/apiCreateFor.ts
packages/runtime-vapor/src/block.ts
packages/runtime-vapor/src/dom/hydration.ts
packages/runtime-vapor/src/dom/node.ts
packages/runtime-vapor/src/dom/prop.ts
packages/runtime-vapor/src/insertionState.ts

index beefab6cf5fc4653d50806e4bf61fe6b64d8bebf..28cf7a0b9d8b45df57ad569c13a53dea3abe3ce9 100644 (file)
@@ -179,8 +179,8 @@ function genInsertionState(
         : anchor === -1 // -1 indicates prepend
           ? `0` // runtime anchor value for prepend
           : append // -2 indicates append
-            ? // null or number > 0 for append
-              // number > 0 is used for locate the previous static node during hydration
+            ? // null or anchor > 0 for append
+              // anchor > 0 is the logical index of append node - used for locate node during hydration
               anchor === 0
               ? 'null'
               : `${anchor}`
index 69250ab5c3beb3f332e542c38b668a81016f8743..973b332b5b856b1e83ef7d8a2e459d8512bbc74a 100644 (file)
@@ -60,6 +60,7 @@ export const transformChildren: NodeTransform = (node, context) => {
 function processDynamicChildren(context: TransformContext<ElementNode>) {
   let prevDynamics: IRDynamicInfo[] = []
   let staticCount = 0
+  let dynamicCount = 0
   const children = context.dynamic.children
 
   for (const [index, child] of children.entries()) {
@@ -77,6 +78,7 @@ function processDynamicChildren(context: TransformContext<ElementNode>) {
         } else {
           registerInsertion(prevDynamics, context, -1 /* prepend */)
         }
+        dynamicCount += prevDynamics.length
         prevDynamics = []
       }
       staticCount++
@@ -84,7 +86,7 @@ function processDynamicChildren(context: TransformContext<ElementNode>) {
   }
 
   if (prevDynamics.length) {
-    registerInsertion(prevDynamics, context, staticCount, true)
+    registerInsertion(prevDynamics, context, dynamicCount + staticCount, true)
   }
 }
 
index a6db691f760a92508fcac11a36d7b94409c6b100..6f7bd2dca5d00d680448adb00d02b4d71c63eb8e 100644 (file)
@@ -151,14 +151,13 @@ export const createFor = (
         if (!parentAnchor || (parentAnchor && !isComment(parentAnchor, ']'))) {
           throw new Error(`v-for fragment anchor node was not found.`)
         }
-
-        // $lastLogicalChild is the fragment start anchor; replacing it with end anchor
+        // the lastLogicalChild is the fragment start anchor; replacing it with end anchor
         // can avoid the call to locateEndAnchor within locateChildByLogicalIndex
-        if (_insertionParent && _insertionParent!.$lastLogicalChild) {
+        if (_insertionParent && _insertionParent!.$llc) {
           ;(parentAnchor as any as ChildItem).$idx = (
-            _insertionParent!.$lastLogicalChild as ChildItem
+            _insertionParent!.$llc as ChildItem
           ).$idx
-          _insertionParent.$lastLogicalChild = parentAnchor
+          _insertionParent.$llc = parentAnchor
         }
       }
     } else {
index 070b5f98b13ede5e6ef72ee964e906979c1b3f16..9dad78cb2f68fa91a86fc39fd2df0f696fb0b5f9 100644 (file)
@@ -69,11 +69,11 @@ export function isValidBlock(block: Block): boolean {
 
 export function insert(
   block: Block,
-  parent: ParentNode & { $prependAnchor?: Node | null },
+  parent: ParentNode & { $fc?: Node | null },
   anchor: Node | null | 0 = null, // 0 means prepend
   parentSuspense?: any, // TODO Suspense
 ): void {
-  anchor = anchor === 0 ? parent.$prependAnchor || _child(parent) : anchor
+  anchor = anchor === 0 ? parent.$fc || _child(parent) : anchor
   if (block instanceof Node) {
     if (!isHydrating) {
       // only apply transition on Element nodes
index 0ac82ebaef9bd84f176d2aee7d569a84c2c688f8..8e9dd0c98ca57ca98d6375a9c4d4573fc44c8100 100644 (file)
@@ -44,11 +44,11 @@ function performHydration<T>(
     // optimize anchor cache lookup
     ;(Comment.prototype as any).$fe = undefined
     ;(Node.prototype as any).$pns = undefined
-    ;(Node.prototype as any).$uc = undefined
     ;(Node.prototype as any).$idx = undefined
-    ;(Node.prototype as any).$prevDynamicCount = undefined
-    ;(Node.prototype as any).$anchorCount = undefined
-    ;(Node.prototype as any).$appendIndex = undefined
+    ;(Node.prototype as any).$llc = undefined
+    ;(Node.prototype as any).$lpn = undefined
+    ;(Node.prototype as any).$lan = undefined
+    ;(Node.prototype as any).$lin = undefined
 
     isOptimized = true
   }
@@ -146,67 +146,37 @@ function adoptTemplateImpl(node: Node, template: string): Node | null {
   return node
 }
 
+function nextNode(node: Node): Node | null {
+  return isComment(node, '[')
+    ? locateEndAnchor(node as Anchor)!.nextSibling
+    : node.nextSibling
+}
+
 function locateHydrationNodeImpl(): void {
   let node: Node | null
   if (insertionAnchor !== undefined) {
-    const {
-      $prevDynamicCount: prevDynamicCount = 0,
-      $appendIndex: appendIndex,
-      $anchorCount: anchorCount = 0,
-    } = insertionParent!
+    const { $lpn: lastPrepend, $lan: lastAppend, firstChild } = insertionParent!
     // prepend
     if (insertionAnchor === 0) {
-      // use prevDynamicCount as logical index to locate the hydration node
-      node =
-        prevDynamicCount === 0 &&
-        currentHydrationNode!.parentNode === insertionParent
-          ? currentHydrationNode
-          : locateChildByLogicalIndex(insertionParent!, prevDynamicCount)!
+      node = insertionParent!.$lpn = lastPrepend
+        ? nextNode(lastPrepend)
+        : firstChild
     }
     // insert
     else if (insertionAnchor instanceof Node) {
-      // handling insertion anchors:
-      // 1. first encounter: use insertionAnchor itself as the hydration node
-      // 2. subsequent: use node following the insertionAnchor as the hydration node
-      // used count tracks how many times insertionAnchor has been used, ensuring
-      // consecutive insert operations locate the correct hydration node.
-      let { $idx, $uc: usedCount } = insertionAnchor as ChildItem
-      if (usedCount !== undefined) {
-        node = locateChildByLogicalIndex(
-          insertionParent!,
-          $idx + usedCount + 1,
-        )!
-        usedCount++
-      } else {
-        insertionParent!.$lastLogicalChild = node = insertionAnchor
-        // first use of this anchor: it doesn't consume the next child
-        // so we track unique anchor appearances for later offset correction
-        insertionParent!.$anchorCount = anchorCount + 1
-        usedCount = 0
-      }
-      ;(insertionAnchor as ChildItem).$uc = usedCount
+      const { $lin: lastInsertedNode } = insertionAnchor as ChildItem
+      node = (insertionAnchor as ChildItem).$lin = lastInsertedNode
+        ? nextNode(lastInsertedNode)
+        : insertionAnchor
     }
     // append
     else {
-      if (appendIndex !== null && appendIndex !== undefined) {
-        node = locateChildByLogicalIndex(insertionParent!, appendIndex + 1)!
-      } else {
-        if (insertionAnchor === null) {
-          node =
-            currentHydrationNode!.parentNode === insertionParent
-              ? currentHydrationNode
-              : locateChildByLogicalIndex(insertionParent!, 0)!
-        } else {
-          node = locateChildByLogicalIndex(
-            insertionParent!,
-            prevDynamicCount + insertionAnchor,
-          )!
-        }
-      }
-      insertionParent!.$appendIndex = (node as ChildItem).$idx
+      node = insertionParent!.$lan = lastAppend
+        ? nextNode(lastAppend)
+        : insertionAnchor === null
+          ? firstChild
+          : locateChildByLogicalIndex(insertionParent!, insertionAnchor)!
     }
-
-    insertionParent!.$prevDynamicCount = prevDynamicCount + 1
   } else {
     node = currentHydrationNode
     if (insertionParent && (!node || node.parentNode !== insertionParent)) {
index c88747f5fef52f9427890a62fe7a99d1c0a425db..4bea538fbba867bae47ee222e596f42ab874501a 100644 (file)
@@ -142,13 +142,13 @@ export function locateChildByLogicalIndex(
   parent: InsertionParent,
   logicalIndex: number,
 ): Node | null {
-  let child = (parent.$lastLogicalChild || parent.firstChild) as ChildItem
+  let child = (parent.$llc || parent.firstChild) as ChildItem
   let fromIndex = child.$idx || 0
 
   while (child) {
     if (fromIndex === logicalIndex) {
       child.$idx = logicalIndex
-      return (parent.$lastLogicalChild = child)
+      return (parent.$llc = child)
     }
 
     child = (
index b9a607b3419f45d426956a4f5297a0b23ba0093b..cfc5263f40de3c516cb98f04cc2ba238e438c8ba 100644 (file)
@@ -386,8 +386,7 @@ export function optimizePropertyLookup(): void {
   const proto = Element.prototype as any
   proto.$transition = undefined
   proto.$key = undefined
-  proto.$prependAnchor = proto.$evtclick = undefined
-  proto.$idx = undefined
+  proto.$fc = proto.$evtclick = undefined
   proto.$root = false
   proto.$html =
     proto.$txt =
index 10262bb20f381bb367ac13c7cf9beb79df22120b..9b993ba49edac04d49d47db5db4f638b13379b63 100644 (file)
@@ -1,24 +1,21 @@
 import { isHydrating } from './dom/hydration'
 export type ChildItem = ChildNode & {
+  // logical index, used during hydration to locate the node
   $idx: number
-  // used count as an anchor
-  $uc?: number
+  // last inserted node
+  $lin?: Node | null
 }
 
 export type InsertionParent = ParentNode & {
-  $prependAnchor?: Node | null
+  // cache the first child for potential consecutive prepends
+  $fc?: Node | null
 
-  /**
-   * hydration-specific properties
-   */
-  // hydrated dynamic children count so far
-  $prevDynamicCount?: number
-  // number of unique insertion anchors that have appeared
-  $anchorCount?: number
-  // last append index
-  $appendIndex?: number | null
   // last located logical child
-  $lastLogicalChild?: Node | null
+  $llc?: Node | null
+  // last prepend node
+  $lpn?: Node | null
+  // last append node
+  $lan?: Node | null
 }
 export let insertionParent: InsertionParent | undefined
 export let insertionAnchor: Node | 0 | undefined | null
@@ -29,7 +26,7 @@ export let insertionAnchor: Node | 0 | undefined | null
  * insertion on client-side render, and used for node adoption during hydration.
  */
 export function setInsertionState(
-  parent: ParentNode & { $prependAnchor?: Node | null },
+  parent: ParentNode & { $fc?: Node | null },
   anchor?: Node | 0 | null | number,
 ): void {
   insertionParent = parent
@@ -37,19 +34,13 @@ export function setInsertionState(
   if (anchor !== undefined) {
     if (isHydrating) {
       insertionAnchor = anchor as Node
-      // when the setInsertionState is called for the first time, reset $lastLogicalChild,
-      // in order to reuse it in locateChildByLogicalIndex
-      if (insertionParent.$prevDynamicCount === undefined) {
-        insertionParent!.$lastLogicalChild = null
-      }
     } else {
       // special handling append anchor value to null
       insertionAnchor =
         typeof anchor === 'number' && anchor > 0 ? null : (anchor as Node)
 
-      // track the first child for potential future use
-      if (anchor === 0 && !parent.$prependAnchor) {
-        parent.$prependAnchor = parent.firstChild
+      if (anchor === 0 && !parent.$fc) {
+        parent.$fc = parent.firstChild
       }
     }
   } else {