: 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}`
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()) {
} else {
registerInsertion(prevDynamics, context, -1 /* prepend */)
}
+ dynamicCount += prevDynamics.length
prevDynamics = []
}
staticCount++
}
if (prevDynamics.length) {
- registerInsertion(prevDynamics, context, staticCount, true)
+ registerInsertion(prevDynamics, context, dynamicCount + staticCount, true)
}
}
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 {
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
// 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
}
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)) {
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 = (
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 =
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
* 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
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 {