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
}
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 {
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
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).
// 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]
}
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!
}
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<ParentNode, HydrationState>()
+export let insertionParent: InsertionParent | undefined
+export let insertionAnchor: Node | 0 | undefined | null
/**
* This function is called before a block type that requires insertion
hydrationStateCache.set(parent, {
logicalChildren,
prevDynamicCount: 0,
- insertionAnchorCount: 0,
+ uniqueAnchorCount: 0,
appendAnchor: null,
})
}