perf: cache static children on parent

This commit is contained in:
daiwei 2025-09-08 17:33:01 +08:00
parent 41509dcc61
commit 479ea96e59
4 changed files with 22 additions and 31 deletions

View File

@ -20,8 +20,8 @@ describe('api: template', () => {
test('nthChild', () => { test('nthChild', () => {
const t = template('<div><span><b>nested</b></span><p></p></div>') const t = template('<div><span><b>nested</b></span><p></p></div>')
const root = t() const root = t() as ParentNode
const span = nthChild(root, 0) const span = nthChild(root, 0) as ParentNode
const b = nthChild(span, 0) const b = nthChild(span, 0)
const p = nthChild(root, 1) const p = nthChild(root, 1)
expect(span).toBe(root.firstChild) expect(span).toBe(root.firstChild)
@ -31,7 +31,7 @@ describe('api: template', () => {
test('next', () => { test('next', () => {
const t = template('<div><span></span><b></b><p></p></div>') const t = template('<div><span></span><b></b><p></p></div>')
const root = t() const root = t() as ParentNode
const span = child(root as ParentNode) const span = child(root as ParentNode)
const b = next(span) const b = next(span)

View File

@ -30,6 +30,7 @@ function performHydration<T>(
// optimize anchor cache lookup // optimize anchor cache lookup
;(Comment.prototype as any).$fe = undefined ;(Comment.prototype as any).$fe = undefined
;(Node.prototype as any).$idx = undefined ;(Node.prototype as any).$idx = undefined
;(Node.prototype as any).$children = undefined
isOptimized = true isOptimized = true
} }
enableHydrationNodeLookup() enableHydrationNodeLookup()

View File

@ -2,8 +2,8 @@
import { import {
type ChildItem, type ChildItem,
type InsertionParent,
getHydrationState, getHydrationState,
getTemplateChildren,
} from '../insertionState' } from '../insertionState'
export function createElement(tagName: string): HTMLElement { export function createElement(tagName: string): HTMLElement {
@ -46,23 +46,23 @@ const __txt: typeof __child = (node: ParentNode): Node => {
} }
/* @__NO_SIDE_EFFECTS__ */ /* @__NO_SIDE_EFFECTS__ */
export function _child(node: ParentNode): Node { export function _child(node: InsertionParent): Node {
const templateChildren = getTemplateChildren(node) const children = node.$children
return templateChildren ? templateChildren[0] : node.firstChild! return children ? children[0] : node.firstChild!
} }
/** /**
* Hydration-specific version of `child`. * Hydration-specific version of `child`.
*/ */
/* @__NO_SIDE_EFFECTS__ */ /* @__NO_SIDE_EFFECTS__ */
export function __child(node: ParentNode & { $lpn?: Node }): Node { export function __child(node: ParentNode): Node {
return __nthChild(node, 0)! return __nthChild(node, 0)!
} }
/* @__NO_SIDE_EFFECTS__ */ /* @__NO_SIDE_EFFECTS__ */
export function _nthChild(node: Node, i: number): Node { export function _nthChild(node: InsertionParent, i: number): Node {
const templateChildren = getTemplateChildren(node as ParentNode) const children = node.$children
return templateChildren ? templateChildren[i] : node.childNodes[i] return children ? children[i] : node.childNodes[i]
} }
/** /**
@ -92,10 +92,8 @@ export function __nthChild(node: Node, i: number): Node {
/* @__NO_SIDE_EFFECTS__ */ /* @__NO_SIDE_EFFECTS__ */
export function _next(node: Node): Node { export function _next(node: Node): Node {
const templateChildren = getTemplateChildren(node.parentNode!) const children = (node.parentNode! as InsertionParent).$children
return templateChildren return children ? children[(node as ChildItem).$idx + 1] : node.nextSibling!
? templateChildren[(node as ChildItem).$idx + 1]
: node.nextSibling!
} }
/** /**

View File

@ -1,18 +1,16 @@
import { isHydrating } from './dom/hydration' import { isHydrating } from './dom/hydration'
export interface ChildItem extends ChildNode { export type ChildItem = ChildNode & { $idx: number }
$idx: number export type InsertionParent = ParentNode & { $children?: ChildItem[] }
}
type HydrationState = { type HydrationState = {
logicalChildren: ChildItem[] logicalChildren: ChildItem[]
prevDynamicCount: number prevDynamicCount: number
insertionAnchors: Map<Node, number> | null insertionAnchors: Map<Node, number> | null
appendAnchor: Node | null appendAnchor: Node | null
} }
export let insertionParent: ParentNode | undefined export let insertionParent: InsertionParent | undefined
export let insertionAnchor: Node | 0 | undefined | null export let insertionAnchor: Node | 0 | undefined | null
const templateChildrenCache = new WeakMap<ParentNode, ChildItem[]>()
const hydrationStateCache = new WeakMap<ParentNode, HydrationState>() const hydrationStateCache = new WeakMap<ParentNode, HydrationState>()
/** /**
@ -87,22 +85,22 @@ function initializeHydrationState(
function cacheTemplateChildren( function cacheTemplateChildren(
anchor: number | Node | null | undefined, anchor: number | Node | null | undefined,
parent: ParentNode, parent: InsertionParent,
) { ) {
// special handling append anchor value to null // special handling append anchor value to null
insertionAnchor = insertionAnchor =
typeof anchor === 'number' && anchor > 0 ? null : (anchor as Node) typeof anchor === 'number' && anchor > 0 ? null : (anchor as Node)
if (!templateChildrenCache.has(parent)) { if (!parent.$children) {
const nodes = parent.childNodes const nodes = parent.childNodes
const len = nodes.length const len = nodes.length
const children = new Array(len) const children = new Array(len) as ChildItem[]
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
const node = nodes[i] as ChildItem const node = nodes[i] as ChildItem
node.$idx = i node.$idx = i
children[i] = node children[i] = node
} }
templateChildrenCache.set(parent, children) parent.$children = children
} }
} }
@ -110,12 +108,6 @@ export function resetInsertionState(): void {
insertionParent = insertionAnchor = undefined insertionParent = insertionAnchor = undefined
} }
export function getTemplateChildren(
parent: ParentNode,
): ChildItem[] | undefined {
return templateChildrenCache.get(parent)
}
export function getHydrationState( export function getHydrationState(
parent: ParentNode, parent: ParentNode,
): HydrationState | undefined { ): HydrationState | undefined {