Skip to content

Commit 0359298

Browse files
committed
refactor: reuse hydration state
1 parent 2ef3caa commit 0359298

File tree

6 files changed

+180
-101
lines changed

6 files changed

+180
-101
lines changed

packages/runtime-vapor/src/block.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
locateFragmentEndAnchor,
1616
locateHydrationNode,
1717
} from './dom/hydration'
18+
import { incrementIndexOffset } from './insertionState'
1819

1920
export type Block =
2021
| Node
@@ -136,6 +137,8 @@ export class DynamicFragment extends VaporFragment {
136137
: createTextNode()),
137138
nextSibling,
138139
)
140+
// increment index offset since we dynamically inserted a comment node
141+
incrementIndexOffset(parentNode!)
139142
advanceHydrationNode(this.anchor)
140143
}
141144
}

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

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { MismatchTypes, isMismatchAllowed, warn } from '@vue/runtime-dom'
22
import {
33
type ChildItem,
4-
getHydrationState,
4+
incrementIndexOffset,
55
insertionAnchor,
66
insertionParent,
77
resetInsertionState,
@@ -36,9 +36,14 @@ function performHydration<T>(
3636
// optimize anchor cache lookup
3737
;(Comment.prototype as any).$fe = undefined
3838
;(Node.prototype as any).$pns = undefined
39-
;(Node.prototype as any).$idx = undefined
4039
;(Node.prototype as any).$uc = undefined
40+
;(Node.prototype as any).$idx = undefined
4141
;(Node.prototype as any).$children = undefined
42+
;(Node.prototype as any).$idxMap = undefined
43+
;(Node.prototype as any).$prevDynamicCount = undefined
44+
;(Node.prototype as any).$anchorCount = undefined
45+
;(Node.prototype as any).$appendIndex = undefined
46+
;(Node.prototype as any).$indexOffset = undefined
4247
isOptimized = true
4348
}
4449
enableHydrationNodeHelper()
@@ -113,7 +118,9 @@ function adoptTemplateImpl(node: Node, template: string): Node | null {
113118
isComment(node, ']') &&
114119
isComment(node.previousSibling!, '[')
115120
) {
116-
node = parentNode(node)!.insertBefore(createTextNode(), node)
121+
const parent = parentNode(node)!
122+
node = parent.insertBefore(createTextNode(), node)
123+
incrementIndexOffset(parent)
117124
break
118125
}
119126
}
@@ -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,48 +166,52 @@ 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
188-
if (insertionParent && (!node || parentNode(node) !== insertionParent)) {
189-
node = _child(insertionParent)
207+
if (insertionParent && (!node || node.parentNode !== insertionParent)) {
208+
node = insertionParent.firstChild
190209
}
191210
}
192211

193212
if (__DEV__ && !node) {
194-
throw new Error(
195-
`No current hydration node was found.\n` +
196-
`this is likely a Vue internal bug.`,
197-
)
213+
// TODO more info
214+
warn('Hydration mismatch in ', insertionParent)
198215
}
199216

200217
resetInsertionState()

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)
@@ -74,21 +70,27 @@ export function _nthChild(node: InsertionParent, i: number): Node {
7470
*/
7571
/* @__NO_SIDE_EFFECTS__ */
7672
export function __nthChild(node: Node, i: number): Node {
77-
const hydrationState = getHydrationState(node as ParentNode)
78-
if (hydrationState) {
79-
const { prevDynamicCount, uniqueAnchorCount, logicalChildren } =
80-
hydrationState
73+
const parent = node as InsertionParent
74+
if (parent.$idxMap) {
75+
const {
76+
$prevDynamicCount: prevDynamicCount = 0,
77+
$anchorCount: anchorCount = 0,
78+
$idxMap: idxMap,
79+
$indexOffset: indexOffset = 0,
80+
} = parent
8181
// prevDynamicCount tracks how many dynamic nodes have been processed
8282
// so far (prepend/insert/append).
8383
// For anchor-based insert, the first time an anchor is used we adopt the
84-
// anchor node itself and do NOT consume the next child in `logicalChildren`,
84+
// anchor node itself and do NOT consume the next child in `idxMap`,
8585
// yet prevDynamicCount is still incremented. This overcounts the base
8686
// offset by 1 per unique anchor that has appeared.
87-
// uniqueAnchorCount equals the number of unique anchors seen, so we
87+
// anchorCount equals the number of unique anchors seen, so we
8888
// subtract it to neutralize those "first-use doesn't consume" cases:
89-
// base = prevDynamicCount - uniqueAnchorCount
90-
// Then index from this base: logicalChildren[base + i].
91-
return logicalChildren[prevDynamicCount - uniqueAnchorCount + i]
89+
// base = prevDynamicCount - anchorCount
90+
// Then index from this base: idxMap[base + i] + indexOffset.
91+
const logicalIndex = prevDynamicCount - anchorCount + i
92+
const realIndex = idxMap[logicalIndex] + indexOffset
93+
return node.childNodes[realIndex]
9294
}
9395
return node.childNodes[i]
9496
}
@@ -104,11 +106,13 @@ export function _next(node: Node): Node {
104106
*/
105107
/* @__NO_SIDE_EFFECTS__ */
106108
export function __next(node: Node): Node {
107-
const hydrationState = getHydrationState(node.parentNode!)
108-
if (hydrationState) {
109-
const { logicalChildren } = hydrationState
109+
const parent = node.parentNode! as InsertionParent
110+
if (parent.$idxMap) {
111+
const { $idxMap: idxMap, $indexOffset: indexOffset = 0 } = parent
110112
const { $idx, $uc: usedCount = 0 } = node as ChildItem
111-
return logicalChildren[$idx + usedCount + 1]
113+
const logicalIndex = $idx + usedCount + 1
114+
const realIndex = idxMap[logicalIndex] + indexOffset
115+
return node.parentNode!.childNodes[realIndex]
112116
}
113117
return node.nextSibling!
114118
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,8 @@ export function optimizePropertyLookup(): void {
403403
isOptimized = true
404404
const proto = Element.prototype as any
405405
proto.$evtclick = undefined
406+
proto.$children = undefined
407+
proto.$idx = undefined
406408
proto.$root = false
407409
proto.$html =
408410
proto.$txt =

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,26 @@ 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
22+
if (__DEV__ && !currentHydrationNode) {
23+
// TODO this should not happen
24+
throw new Error('No current hydration node')
25+
}
1126
// do not cache the adopted node in node because it contains child nodes
1227
// this avoids duplicate rendering of children
1328
const adopted = adoptTemplate(currentHydrationNode!, html)!
@@ -27,4 +42,5 @@ export function template(html: string, root?: boolean) {
2742
if (root) (ret as any).$root = true
2843
return ret
2944
}
45+
return fn
3046
}

0 commit comments

Comments
 (0)