Skip to content

Commit 80b8893

Browse files
committed
refactor: reuse hydration state
1 parent 0690dee commit 80b8893

File tree

7 files changed

+159
-97
lines changed

7 files changed

+159
-97
lines changed

packages/runtime-vapor/src/component.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -616,10 +616,8 @@ export function mountComponent(
616616
startMeasure(instance, `mount`)
617617
}
618618
if (instance.bm) invokeArrayFns(instance.bm)
619-
if (!isHydrating) {
620-
insert(instance.block, parent, anchor)
621-
setComponentScopeId(instance)
622-
}
619+
insert(instance.block, parent, anchor)
620+
if (!isHydrating) setComponentScopeId(instance)
623621
if (instance.m) queuePostFlushCb(() => invokeArrayFns(instance.m!))
624622
instance.isMounted = true
625623
if (__DEV__) {

packages/runtime-vapor/src/dom/hydration.ts

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { warn } from '@vue/runtime-dom'
22
import {
33
type ChildItem,
4-
getHydrationState,
4+
incrementIndexOffset,
55
insertionAnchor,
66
insertionParent,
77
resetInsertionState,
@@ -30,9 +30,15 @@ function performHydration<T>(
3030
// optimize anchor cache lookup
3131
;(Comment.prototype as any).$fe = undefined
3232
;(Node.prototype as any).$pns = undefined
33-
;(Node.prototype as any).$idx = undefined
3433
;(Node.prototype as any).$uc = undefined
34+
;(Node.prototype as any).$idx = undefined
3535
;(Node.prototype as any).$children = undefined
36+
;(Node.prototype as any).$idxMap = undefined
37+
;(Node.prototype as any).$prevDynamicCount = undefined
38+
;(Node.prototype as any).$anchorCount = undefined
39+
;(Node.prototype as any).$appendIndex = undefined
40+
;(Node.prototype as any).$indexOffset = undefined
41+
3642
isOptimized = true
3743
}
3844
enableHydrationNodeLookup()
@@ -108,6 +114,7 @@ function adoptTemplateImpl(node: Node, template: string): Node | null {
108114
isComment(node.previousSibling!, '[')
109115
) {
110116
node = node.parentNode!.insertBefore(createTextNode(' '), node)
117+
incrementIndexOffset(node.parentNode!)
111118
break
112119
}
113120
}
@@ -136,13 +143,19 @@ function adoptTemplateImpl(node: Node, template: string): Node | null {
136143

137144
function locateHydrationNodeImpl(): void {
138145
let node: Node | null
139-
if (insertionAnchor !== undefined) {
140-
const hydrationState = getHydrationState(insertionParent!)!
141-
const { prevDynamicCount, logicalChildren, appendAnchor } = hydrationState
146+
let idxMap: number[] | undefined
147+
if (insertionAnchor !== undefined && (idxMap = insertionParent!.$idxMap)) {
148+
const {
149+
$prevDynamicCount: prevDynamicCount = 0,
150+
$appendIndex: appendIndex,
151+
$indexOffset: indexOffset = 0,
152+
$anchorCount: anchorCount = 0,
153+
} = insertionParent!
142154
// prepend
143155
if (insertionAnchor === 0) {
144-
// use prevDynamicCount as index to locate the hydration node
145-
node = logicalChildren[prevDynamicCount]
156+
// use prevDynamicCount as logical index to locate the hydration node
157+
const realIndex = idxMap![prevDynamicCount] + indexOffset
158+
node = insertionParent!.childNodes[realIndex]
146159
}
147160
// insert
148161
else if (insertionAnchor instanceof Node) {
@@ -153,36 +166,42 @@ function locateHydrationNodeImpl(): void {
153166
// consecutive insert operations locate the correct hydration node.
154167
let { $idx, $uc: usedCount } = insertionAnchor as ChildItem
155168
if (usedCount !== undefined) {
156-
node = logicalChildren[$idx + usedCount + 1]
169+
const realIndex = idxMap![$idx + usedCount + 1] + indexOffset
170+
node = insertionParent!.childNodes[realIndex]
157171
usedCount++
158172
} else {
159173
node = insertionAnchor
160174
// first use of this anchor: it doesn't consume the next child
161175
// so we track unique anchor appearances for later offset correction
162-
hydrationState.uniqueAnchorCount++
176+
insertionParent!.$anchorCount = anchorCount + 1
163177
usedCount = 0
164178
}
165179
;(insertionAnchor as ChildItem).$uc = usedCount
166180
}
167181
// append
168182
else {
169-
if (appendAnchor) {
170-
node = logicalChildren[(appendAnchor as ChildItem).$idx + 1]
183+
let realIndex: number
184+
if (appendIndex !== null && appendIndex !== undefined) {
185+
realIndex = idxMap![appendIndex + 1] + indexOffset
186+
node = insertionParent!.childNodes[realIndex]
171187
} else {
172-
node =
188+
if (insertionAnchor === null) {
173189
// insertionAnchor is null, indicates no previous static nodes
174190
// use the first child as hydration node
175-
insertionAnchor === null
176-
? logicalChildren[0]
177-
: // insertionAnchor is a number > 0
178-
// indicates how many static nodes precede the node to append
179-
// use it as index to locate the hydration node
180-
logicalChildren[prevDynamicCount + insertionAnchor]
191+
realIndex = idxMap![0] + indexOffset
192+
node = insertionParent!.childNodes[realIndex]
193+
} else {
194+
// insertionAnchor is a number > 0
195+
// indicates how many static nodes precede the node to append
196+
// use it as index to locate the hydration node
197+
realIndex = idxMap![prevDynamicCount + insertionAnchor] + indexOffset
198+
node = insertionParent!.childNodes[realIndex]
199+
}
181200
}
182-
hydrationState.appendAnchor = node
201+
insertionParent!.$appendIndex = (node as ChildItem).$idx
183202
}
184203

185-
hydrationState.prevDynamicCount++
204+
insertionParent!.$prevDynamicCount = prevDynamicCount + 1
186205
} else {
187206
node = currentHydrationNode
188207
if (insertionParent && (!node || node.parentNode !== insertionParent)) {

packages/runtime-vapor/src/dom/node.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
/* @__NO_SIDE_EFFECTS__ */
22

3-
import {
4-
type ChildItem,
5-
type InsertionParent,
6-
getHydrationState,
7-
} from '../insertionState'
3+
import type { ChildItem, InsertionParent } from '../insertionState'
84

95
export function createElement(tagName: string): HTMLElement {
106
return document.createElement(tagName)
@@ -70,21 +66,27 @@ export function _nthChild(node: InsertionParent, i: number): Node {
7066
*/
7167
/* @__NO_SIDE_EFFECTS__ */
7268
export function __nthChild(node: Node, i: number): Node {
73-
const hydrationState = getHydrationState(node as ParentNode)
74-
if (hydrationState) {
75-
const { prevDynamicCount, uniqueAnchorCount, logicalChildren } =
76-
hydrationState
69+
const parent = node as InsertionParent
70+
if (parent.$idxMap) {
71+
const {
72+
$prevDynamicCount: prevDynamicCount = 0,
73+
$anchorCount: anchorCount = 0,
74+
$idxMap: idxMap,
75+
$indexOffset: indexOffset = 0,
76+
} = parent
7777
// prevDynamicCount tracks how many dynamic nodes have been processed
7878
// so far (prepend/insert/append).
7979
// For anchor-based insert, the first time an anchor is used we adopt the
80-
// anchor node itself and do NOT consume the next child in `logicalChildren`,
80+
// anchor node itself and do NOT consume the next child in `idxMap`,
8181
// yet prevDynamicCount is still incremented. This overcounts the base
8282
// offset by 1 per unique anchor that has appeared.
83-
// uniqueAnchorCount equals the number of unique anchors seen, so we
83+
// anchorCount equals the number of unique anchors seen, so we
8484
// subtract it to neutralize those "first-use doesn't consume" cases:
85-
// base = prevDynamicCount - uniqueAnchorCount
86-
// Then index from this base: logicalChildren[base + i].
87-
return logicalChildren[prevDynamicCount - uniqueAnchorCount + i]
85+
// base = prevDynamicCount - anchorCount
86+
// Then index from this base: idxMap[base + i] + indexOffset.
87+
const logicalIndex = prevDynamicCount - anchorCount + i
88+
const realIndex = idxMap[logicalIndex] + indexOffset
89+
return node.childNodes[realIndex]
8890
}
8991
return node.childNodes[i]
9092
}
@@ -100,11 +102,13 @@ export function _next(node: Node): Node {
100102
*/
101103
/* @__NO_SIDE_EFFECTS__ */
102104
export function __next(node: Node): Node {
103-
const hydrationState = getHydrationState(node.parentNode!)
104-
if (hydrationState) {
105-
const { logicalChildren } = hydrationState
105+
const parent = node.parentNode! as InsertionParent
106+
if (parent.$idxMap) {
107+
const { $idxMap: idxMap, $indexOffset: indexOffset = 0 } = parent
106108
const { $idx, $uc: usedCount = 0 } = node as ChildItem
107-
return logicalChildren[$idx + usedCount + 1]
109+
const logicalIndex = $idx + usedCount + 1
110+
const realIndex = idxMap[logicalIndex] + indexOffset
111+
return node.parentNode!.childNodes[realIndex]
108112
}
109113
return node.nextSibling!
110114
}

packages/runtime-vapor/src/dom/prop.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,8 @@ export function optimizePropertyLookup(): void {
275275
proto.$transition = undefined
276276
proto.$key = undefined
277277
proto.$evtclick = undefined
278+
proto.$children = undefined
279+
proto.$idx = undefined
278280
proto.$root = false
279281
proto.$html =
280282
proto.$txt =

packages/runtime-vapor/src/dom/template.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,22 @@ import { child, createElement, createTextNode } from './node'
33

44
let t: HTMLTemplateElement
55

6+
export let currentTemplateFn: (Function & { $idxMap?: number[] }) | undefined =
7+
undefined
8+
9+
export function resetTemplateFn(): void {
10+
currentTemplateFn = undefined
11+
}
12+
613
/*! #__NO_SIDE_EFFECTS__ */
7-
export function template(html: string, root?: boolean) {
14+
export function template(
15+
html: string,
16+
root?: boolean,
17+
): () => Node & { $root?: true } {
818
let node: Node
9-
return (): Node & { $root?: true } => {
19+
const fn = () => {
1020
if (isHydrating) {
21+
currentTemplateFn = fn
1122
if (__DEV__ && !currentHydrationNode) {
1223
// TODO this should not happen
1324
throw new Error('No current hydration node')
@@ -32,4 +43,5 @@ export function template(html: string, root?: boolean) {
3243
if (root) (ret as any).$root = true
3344
return ret
3445
}
46+
return fn
3547
}

packages/runtime-vapor/src/fragment.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
} from './components/Transition'
2525
import { type VaporComponentInstance, isVaporComponent } from './component'
2626
import { isArray } from '@vue/shared'
27+
import { incrementIndexOffset } from './insertionState'
2728

2829
export class VaporFragment<T extends Block = Block>
2930
implements TransitionOptions
@@ -185,6 +186,8 @@ export class DynamicFragment extends VaporFragment {
185186
(this.anchor = createComment(this.anchorLabel!)),
186187
nextSibling,
187188
)
189+
// increment index offset since we dynamically inserted a comment node
190+
incrementIndexOffset(parentNode!)
188191
advanceHydrationNode(this.anchor)
189192
}
190193
}

0 commit comments

Comments
 (0)