diff --git a/packages/runtime-vapor/src/dom/hydration.ts b/packages/runtime-vapor/src/dom/hydration.ts index d05090981..9a4d923f7 100644 --- a/packages/runtime-vapor/src/dom/hydration.ts +++ b/packages/runtime-vapor/src/dom/hydration.ts @@ -29,8 +29,9 @@ function performHydration( locateHydrationNode = locateHydrationNodeImpl // optimize anchor cache lookup ;(Comment.prototype as any).$fe = undefined + ;(Node.prototype as any).$pns = undefined ;(Node.prototype as any).$idx = undefined - ;(Node.prototype as any).$auc = undefined + ;(Node.prototype as any).$uc = undefined ;(Node.prototype as any).$children = undefined isOptimized = true } @@ -140,20 +141,28 @@ function locateHydrationNodeImpl(): void { const { prevDynamicCount, logicalChildren, appendAnchor } = hydrationState // prepend if (insertionAnchor === 0) { + // use prevDynamicCount as index to locate the hydration node node = logicalChildren[prevDynamicCount] } // insert else if (insertionAnchor instanceof Node) { - const usedCount = (insertionAnchor as ChildItem).$auc + // 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 = - logicalChildren[(insertionAnchor as ChildItem).$idx + usedCount + 1] - ;(insertionAnchor as ChildItem).$auc = usedCount + 1 + node = logicalChildren[$idx + usedCount + 1] + usedCount++ } else { node = insertionAnchor - hydrationState.insertionAnchorCount++ - ;(insertionAnchor as ChildItem).$auc = 0 + // first use of this anchor: it doesn't consume the next child + // so we track unique anchor appearances for later offset correction + hydrationState.uniqueAnchorCount++ + usedCount = 0 } + ;(insertionAnchor as ChildItem).$uc = usedCount } // append else { @@ -161,14 +170,18 @@ function locateHydrationNodeImpl(): void { node = logicalChildren[(appendAnchor as ChildItem).$idx + 1] } else { node = + // insertionAnchor is null, indicates no previous static nodes + // use the first child as hydration node insertionAnchor === null ? logicalChildren[0] : // insertionAnchor is a number > 0 // indicates how many static nodes precede the node to append + // use it as index to locate the hydration node logicalChildren[prevDynamicCount + insertionAnchor] } hydrationState.appendAnchor = node } + hydrationState.prevDynamicCount++ } else { node = currentHydrationNode diff --git a/packages/runtime-vapor/src/dom/node.ts b/packages/runtime-vapor/src/dom/node.ts index 21c94eeff..a48ecf5bb 100644 --- a/packages/runtime-vapor/src/dom/node.ts +++ b/packages/runtime-vapor/src/dom/node.ts @@ -72,7 +72,7 @@ export function _nthChild(node: InsertionParent, i: number): Node { export function __nthChild(node: Node, i: number): Node { const hydrationState = getHydrationState(node as ParentNode) if (hydrationState) { - const { prevDynamicCount, insertionAnchorCount, logicalChildren } = + const { prevDynamicCount, uniqueAnchorCount, logicalChildren } = hydrationState // prevDynamicCount tracks how many dynamic nodes have been processed // so far (prepend/insert/append). @@ -80,11 +80,11 @@ export function __nthChild(node: Node, i: number): Node { // anchor node itself and do NOT consume the next child in `logicalChildren`, // yet prevDynamicCount is still incremented. This overcounts the base // offset by 1 per unique anchor that has appeared. - // insertionAnchorCount equals the number of unique anchors seen, so we + // uniqueAnchorCount equals the number of unique anchors seen, so we // subtract it to neutralize those "first-use doesn't consume" cases: - // base = prevDynamicCount - insertionAnchorCount + // base = prevDynamicCount - uniqueAnchorCount // Then index from this base: logicalChildren[base + i]. - return logicalChildren[prevDynamicCount - insertionAnchorCount + i] + return logicalChildren[prevDynamicCount - uniqueAnchorCount + i] } return node.childNodes[i] } @@ -103,9 +103,8 @@ export function __next(node: Node): Node { const hydrationState = getHydrationState(node.parentNode!) if (hydrationState) { const { logicalChildren } = hydrationState - return logicalChildren[ - (node as ChildItem).$idx + ((node as ChildItem).$auc || 0) + 1 - ] + const { $idx, $uc: usedCount = 0 } = node as ChildItem + return logicalChildren[$idx + usedCount + 1] } return node.nextSibling! } diff --git a/packages/runtime-vapor/src/insertionState.ts b/packages/runtime-vapor/src/insertionState.ts index 6d53aeec5..0b29f59d2 100644 --- a/packages/runtime-vapor/src/insertionState.ts +++ b/packages/runtime-vapor/src/insertionState.ts @@ -1,17 +1,24 @@ import { isHydrating } from './dom/hydration' -export type ChildItem = ChildNode & { $idx: number; $auc?: number } +export type ChildItem = ChildNode & { + $idx: number + // used count as an anchor + $uc?: number +} export type InsertionParent = ParentNode & { $children?: ChildItem[] } - type HydrationState = { + // static nodes and the start anchors of fragments logicalChildren: ChildItem[] + // hydrated dynamic children count so far prevDynamicCount: number - insertionAnchorCount: number + // number of unique insertion anchors that have appeared + uniqueAnchorCount: number + // current append anchor appendAnchor: Node | null } -export let insertionParent: InsertionParent | undefined -export let insertionAnchor: Node | 0 | undefined | null const hydrationStateCache = new WeakMap() +export let insertionParent: InsertionParent | undefined +export let insertionAnchor: Node | 0 | undefined | null /** * This function is called before a block type that requires insertion @@ -77,7 +84,7 @@ function initializeHydrationState( hydrationStateCache.set(parent, { logicalChildren, prevDynamicCount: 0, - insertionAnchorCount: 0, + uniqueAnchorCount: 0, appendAnchor: null, }) }