From 679a67dbba053cfffc6057e56079d8734f2137cc Mon Sep 17 00:00:00 2001 From: daiwei Date: Tue, 30 Sep 2025 09:50:11 +0800 Subject: [PATCH] refactor: update logical child handling and optimize hydration state management --- .../src/generators/operation.ts | 4 +- .../src/transforms/transformChildren.ts | 4 +- packages/runtime-vapor/src/apiCreateFor.ts | 9 +-- packages/runtime-vapor/src/block.ts | 4 +- packages/runtime-vapor/src/dom/hydration.ts | 76 ++++++------------- packages/runtime-vapor/src/dom/node.ts | 4 +- packages/runtime-vapor/src/dom/prop.ts | 3 +- packages/runtime-vapor/src/insertionState.ts | 35 ++++----- 8 files changed, 50 insertions(+), 89 deletions(-) diff --git a/packages/compiler-vapor/src/generators/operation.ts b/packages/compiler-vapor/src/generators/operation.ts index beefab6cf5..28cf7a0b9d 100644 --- a/packages/compiler-vapor/src/generators/operation.ts +++ b/packages/compiler-vapor/src/generators/operation.ts @@ -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}` diff --git a/packages/compiler-vapor/src/transforms/transformChildren.ts b/packages/compiler-vapor/src/transforms/transformChildren.ts index 69250ab5c3..973b332b5b 100644 --- a/packages/compiler-vapor/src/transforms/transformChildren.ts +++ b/packages/compiler-vapor/src/transforms/transformChildren.ts @@ -60,6 +60,7 @@ export const transformChildren: NodeTransform = (node, context) => { function processDynamicChildren(context: TransformContext) { 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) { } else { registerInsertion(prevDynamics, context, -1 /* prepend */) } + dynamicCount += prevDynamics.length prevDynamics = [] } staticCount++ @@ -84,7 +86,7 @@ function processDynamicChildren(context: TransformContext) { } if (prevDynamics.length) { - registerInsertion(prevDynamics, context, staticCount, true) + registerInsertion(prevDynamics, context, dynamicCount + staticCount, true) } } diff --git a/packages/runtime-vapor/src/apiCreateFor.ts b/packages/runtime-vapor/src/apiCreateFor.ts index a6db691f76..6f7bd2dca5 100644 --- a/packages/runtime-vapor/src/apiCreateFor.ts +++ b/packages/runtime-vapor/src/apiCreateFor.ts @@ -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 { diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts index 070b5f98b1..9dad78cb2f 100644 --- a/packages/runtime-vapor/src/block.ts +++ b/packages/runtime-vapor/src/block.ts @@ -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 diff --git a/packages/runtime-vapor/src/dom/hydration.ts b/packages/runtime-vapor/src/dom/hydration.ts index 0ac82ebaef..8e9dd0c98c 100644 --- a/packages/runtime-vapor/src/dom/hydration.ts +++ b/packages/runtime-vapor/src/dom/hydration.ts @@ -44,11 +44,11 @@ function performHydration( // 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)) { diff --git a/packages/runtime-vapor/src/dom/node.ts b/packages/runtime-vapor/src/dom/node.ts index c88747f5fe..4bea538fbb 100644 --- a/packages/runtime-vapor/src/dom/node.ts +++ b/packages/runtime-vapor/src/dom/node.ts @@ -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 = ( diff --git a/packages/runtime-vapor/src/dom/prop.ts b/packages/runtime-vapor/src/dom/prop.ts index b9a607b341..cfc5263f40 100644 --- a/packages/runtime-vapor/src/dom/prop.ts +++ b/packages/runtime-vapor/src/dom/prop.ts @@ -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 = diff --git a/packages/runtime-vapor/src/insertionState.ts b/packages/runtime-vapor/src/insertionState.ts index 10262bb20f..9b993ba49e 100644 --- a/packages/runtime-vapor/src/insertionState.ts +++ b/packages/runtime-vapor/src/insertionState.ts @@ -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 { -- 2.47.3