From: daiwei Date: Thu, 16 Oct 2025 08:07:00 +0000 (+0800) Subject: perf: advance to parent nextSibling only when insertion is the last in parent X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=afae33b5ba5bc7107a485626773a6e9b8bc3648a;p=thirdparty%2Fvuejs%2Fcore.git perf: advance to parent nextSibling only when insertion is the last in parent --- diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap index d2cd54c9e1..0120f5f487 100644 --- a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap @@ -38,7 +38,7 @@ export function render(_ctx) { "default": () => { const n0 = _createIf(() => (true), () => { const n3 = t0() - _setInsertionState(n3, null) + _setInsertionState(n3, null, true) const n2 = _createComponentWithFallback(_component_Bar) _withVaporDirectives(n2, [[_directive_hello, void 0, void 0, { world: true }]]) return n3 @@ -158,7 +158,7 @@ export function render(_ctx, $props, $emit, $attrs, $slots) { const n0 = t0() const n3 = t1() const n2 = _child(n3, 1) - _setInsertionState(n3, 0) + _setInsertionState(n3, 0, true) const n1 = _createComponentWithFallback(_component_Comp) _renderEffect(() => { _setProp(n3, "id", _ctx.foo) @@ -226,7 +226,7 @@ export function render(_ctx) { const n4 = _child(p0, 0) _setInsertionState(n6, n5) const n0 = _createComponentWithFallback(_component_Comp) - _setInsertionState(n6, n7) + _setInsertionState(n6, n7, true) const n1 = _createIf(() => (true), () => { const n3 = t0() return n3 @@ -244,9 +244,9 @@ export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n3 = t0() const n1 = _child(n3, 0) - _setInsertionState(n1, null) + _setInsertionState(n1, null, true) const n0 = _createSlot("default", null) - _setInsertionState(n3, 1) + _setInsertionState(n3, 1, true) const n2 = _createComponentWithFallback(_component_Comp) return n3 }" diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap index 8a83143b9b..3e6edc8ae9 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap @@ -51,7 +51,7 @@ export function render(_ctx) { return n5 }, () => { const n14 = t2() - _setInsertionState(n14, 0) + _setInsertionState(n14, 0, true) const n9 = _createIf(() => (_ctx.c), () => { const n11 = t1() return n11 diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformChildren.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformChildren.spec.ts.snap index 99f64c8cf2..c75f67c36d 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformChildren.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformChildren.spec.ts.snap @@ -8,7 +8,7 @@ const t1 = _template("
", true) export function render(_ctx) { const n4 = t1() const n3 = _next(_child(n4), 1) - _setInsertionState(n4, n3) + _setInsertionState(n4, n3, true) const n0 = _createIf(() => (1), () => { const n2 = t0() return n2 diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap index cccc9200f8..6b75644630 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap @@ -87,7 +87,7 @@ const t1 = _template("
", true) export function render(_ctx) { const n0 = _createFor(() => (_ctx.list), (_for_item0) => { const n5 = t1() - _setInsertionState(n5, null) + _setInsertionState(n5, null, true) const n2 = _createFor(() => (_for_item0.value), (_for_item1) => { const n4 = t0() const x4 = _txt(n4) diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vIf.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vIf.spec.ts.snap index c60ae16be0..a8dc5aa459 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vIf.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vIf.spec.ts.snap @@ -149,7 +149,7 @@ export function render(_ctx) { const n2 = t0() return n2 }) - _setInsertionState(n8, null) + _setInsertionState(n8, null, true) const n3 = _createIf(() => (_ctx.bar), () => { const n5 = t1() return n5 diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap index 2fcd18da1d..ae5e9df743 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap @@ -42,7 +42,7 @@ const t0 = _template("
", true) export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n1 = t0() - _setInsertionState(n1, null) + _setInsertionState(n1, null, true) const n0 = _createComponentWithFallback(_component_Comp, { id: () => (_ctx.foo) }, null, null, true) return n1 }" diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap index 9b11f074c4..3b804cb6ec 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap @@ -354,7 +354,7 @@ export function render(_ctx) { const n6 = t0() _setInsertionState(n6, null) const n0 = _createSlot("foo", null) - _setInsertionState(n6, null) + _setInsertionState(n6, null, true) const n1 = _createIf(() => (true), () => { const n3 = _createComponentWithFallback(_component_Foo) return n3 diff --git a/packages/compiler-vapor/__tests__/transforms/transformChildren.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformChildren.spec.ts index d41ed2ec47..9d5226921a 100644 --- a/packages/compiler-vapor/__tests__/transforms/transformChildren.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/transformChildren.spec.ts @@ -70,7 +70,7 @@ describe('compiler: children transform', () => { ) // ensure the insertion anchor is generated before the insertion statement expect(code).toMatch(`const n3 = _next(_child(n4), 1)`) - expect(code).toMatch(`_setInsertionState(n4, n3)`) + expect(code).toMatch(`_setInsertionState(n4, n3, true)`) expect(code).toMatchSnapshot() }) }) diff --git a/packages/compiler-vapor/src/generators/operation.ts b/packages/compiler-vapor/src/generators/operation.ts index 28cf7a0b9d..8045051602 100644 --- a/packages/compiler-vapor/src/generators/operation.ts +++ b/packages/compiler-vapor/src/generators/operation.ts @@ -168,7 +168,7 @@ function genInsertionState( operation: InsertionStateTypes, context: CodegenContext, ): CodeFragment[] { - const { parent, anchor, append } = operation + const { parent, anchor, append, last } = operation return [ NEWLINE, ...genCall( @@ -185,6 +185,7 @@ function genInsertionState( ? 'null' : `${anchor}` : `n${anchor}`, + last && 'true', ), ] } diff --git a/packages/compiler-vapor/src/ir/index.ts b/packages/compiler-vapor/src/ir/index.ts index 5bea27fc04..492dae6e7b 100644 --- a/packages/compiler-vapor/src/ir/index.ts +++ b/packages/compiler-vapor/src/ir/index.ts @@ -81,6 +81,7 @@ export interface IfIRNode extends BaseIRNode { parent?: number anchor?: number append?: boolean + last?: boolean } export interface IRFor { @@ -101,6 +102,7 @@ export interface ForIRNode extends BaseIRNode, IRFor { parent?: number anchor?: number append?: boolean + last?: boolean } export interface SetPropIRNode extends BaseIRNode { @@ -203,6 +205,8 @@ export interface CreateComponentIRNode extends BaseIRNode { parent?: number anchor?: number append?: boolean + last?: boolean + scopeId?: string | null } @@ -221,6 +225,7 @@ export interface SlotOutletIRNode extends BaseIRNode { parent?: number anchor?: number append?: boolean + last?: boolean } export interface GetTextChildIRNode extends BaseIRNode { diff --git a/packages/compiler-vapor/src/transforms/transformChildren.ts b/packages/compiler-vapor/src/transforms/transformChildren.ts index 973b332b5b..cdff90edeb 100644 --- a/packages/compiler-vapor/src/transforms/transformChildren.ts +++ b/packages/compiler-vapor/src/transforms/transformChildren.ts @@ -8,6 +8,7 @@ import { DynamicFlag, type IRDynamicInfo, IRNodeTypes, + type InsertionStateTypes, isBlockOperation, } from '../ir' @@ -61,11 +62,12 @@ function processDynamicChildren(context: TransformContext) { let prevDynamics: IRDynamicInfo[] = [] let staticCount = 0 let dynamicCount = 0 + let lastInsertionChild: IRDynamicInfo | undefined const children = context.dynamic.children for (const [index, child] of children.entries()) { if (child.flags & DynamicFlag.INSERT) { - prevDynamics.push(child) + prevDynamics.push((lastInsertionChild = child)) } if (!(child.flags & DynamicFlag.NON_TEMPLATE)) { @@ -86,7 +88,17 @@ function processDynamicChildren(context: TransformContext) { } if (prevDynamics.length) { - registerInsertion(prevDynamics, context, dynamicCount + staticCount, true) + registerInsertion( + prevDynamics, + context, + // the logical index of append child + dynamicCount + staticCount, + true, + ) + } + + if (lastInsertionChild && lastInsertionChild.operation) { + ;(lastInsertionChild.operation! as InsertionStateTypes).last = true } } diff --git a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts index cc7bd6aaf2..6e051a4e25 100644 --- a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts +++ b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts @@ -7,6 +7,7 @@ import type { RawSlots } from './componentSlots' import { insertionAnchor, insertionParent, + isLastInsertion, resetInsertionState, } from './insertionState' import { advanceHydrationNode, isHydrating } from './dom/hydration' @@ -22,6 +23,7 @@ export function createDynamicComponent( ): VaporFragment { const _insertionParent = insertionParent const _insertionAnchor = insertionAnchor + const _isLastInsertion = isLastInsertion if (!isHydrating) resetInsertionState() const frag = @@ -51,7 +53,7 @@ export function createDynamicComponent( if (!isHydrating) { if (_insertionParent) insert(frag, _insertionParent, _insertionAnchor) } else { - if (_insertionAnchor !== undefined) { + if (_isLastInsertion) { advanceHydrationNode(_insertionParent!) } } diff --git a/packages/runtime-vapor/src/apiCreateFor.ts b/packages/runtime-vapor/src/apiCreateFor.ts index 38c60b9916..ae70d6c31e 100644 --- a/packages/runtime-vapor/src/apiCreateFor.ts +++ b/packages/runtime-vapor/src/apiCreateFor.ts @@ -41,6 +41,7 @@ import { ForFragment, VaporFragment, findBlockNode } from './fragment' import { insertionAnchor, insertionParent, + isLastInsertion, resetInsertionState, } from './insertionState' import { applyTransitionHooks } from './components/Transition' @@ -94,6 +95,7 @@ export const createFor = ( ): ForFragment => { const _insertionParent = insertionParent const _insertionAnchor = insertionAnchor + const _isLastInsertion = isLastInsertion if (isHydrating) { locateHydrationNode() } else { @@ -485,9 +487,7 @@ export const createFor = ( if (!isHydrating) { if (_insertionParent) insert(frag, _insertionParent, _insertionAnchor) } else { - advanceHydrationNode( - _insertionAnchor !== undefined ? _insertionParent! : parentAnchor!, - ) + advanceHydrationNode(_isLastInsertion ? _insertionParent! : parentAnchor!) } return frag diff --git a/packages/runtime-vapor/src/apiCreateIf.ts b/packages/runtime-vapor/src/apiCreateIf.ts index 2183cb9677..b9764f7d1a 100644 --- a/packages/runtime-vapor/src/apiCreateIf.ts +++ b/packages/runtime-vapor/src/apiCreateIf.ts @@ -3,6 +3,7 @@ import { advanceHydrationNode, isHydrating } from './dom/hydration' import { insertionAnchor, insertionParent, + isLastInsertion, resetInsertionState, } from './insertionState' import { renderEffect } from './renderEffect' @@ -16,6 +17,7 @@ export function createIf( ): Block { const _insertionParent = insertionParent const _insertionAnchor = insertionAnchor + const _isLastInsertion = isLastInsertion if (!isHydrating) resetInsertionState() let frag: Block @@ -30,13 +32,7 @@ export function createIf( if (!isHydrating) { if (_insertionParent) insert(frag, _insertionParent, _insertionAnchor) } else { - // After block node hydration is completed, advance currentHydrationNode to - // _insertionParent's next sibling if _insertionAnchor has a value - // _insertionAnchor values: - // 1. Node type: _insertionAnchor is a static node, no hydration needed - // 2. null: block node is appended, potentially without next sibling - // 3. 0: next sibling of current block node is static, no hydration needed - if (_insertionAnchor !== undefined) { + if (_isLastInsertion) { advanceHydrationNode(_insertionParent!) } } diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 0139d9cf1c..63d363bb77 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -81,6 +81,7 @@ import { type TeleportFragment, isVaporTeleport } from './components/Teleport' import { insertionAnchor, insertionParent, + isLastInsertion, resetInsertionState, } from './insertionState' import { DynamicFragment } from './fragment' @@ -167,6 +168,7 @@ export function createComponent( ): VaporComponentInstance { const _insertionParent = insertionParent const _insertionAnchor = insertionAnchor + const _isLastInsertion = isLastInsertion if (isHydrating) { locateHydrationNode() } else { @@ -204,7 +206,7 @@ export function createComponent( if (_insertionParent) insert(frag, _insertionParent, _insertionAnchor) } else { frag.hydrate() - if (_insertionAnchor !== undefined) { + if (_isLastInsertion) { advanceHydrationNode(_insertionParent!) } } @@ -219,7 +221,7 @@ export function createComponent( if (_insertionParent) insert(frag, _insertionParent, _insertionAnchor) } else { frag.hydrate() - if (_insertionAnchor !== undefined) { + if (_isLastInsertion) { advanceHydrationNode(_insertionParent!) } } @@ -610,6 +612,7 @@ export function createComponentWithFallback( const _insertionParent = insertionParent const _insertionAnchor = insertionAnchor + const _isLastInsertion = isLastInsertion if (isHydrating) { locateHydrationNode() } else { @@ -650,7 +653,7 @@ export function createComponentWithFallback( if (!isHydrating) { if (_insertionParent) insert(el, _insertionParent, _insertionAnchor) } else { - if (_insertionAnchor !== undefined) { + if (_isLastInsertion) { advanceHydrationNode(_insertionParent!) } } diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts index f01034426b..ecbd81a0f1 100644 --- a/packages/runtime-vapor/src/componentSlots.ts +++ b/packages/runtime-vapor/src/componentSlots.ts @@ -7,6 +7,7 @@ import { renderEffect } from './renderEffect' import { insertionAnchor, insertionParent, + isLastInsertion, resetInsertionState, } from './insertionState' import { @@ -115,6 +116,7 @@ export function createSlot( ): Block { const _insertionParent = insertionParent const _insertionAnchor = insertionAnchor + const _isLastInsertion = isLastInsertion if (!isHydrating) resetInsertionState() const instance = i || (currentInstance as VaporComponentInstance) @@ -174,7 +176,7 @@ export function createSlot( if (_insertionParent) { updateLastLogicalChild(_insertionParent!, fragment.anchor) } - if (_insertionAnchor !== undefined) { + if (_isLastInsertion) { advanceHydrationNode(_insertionParent!) } } diff --git a/packages/runtime-vapor/src/insertionState.ts b/packages/runtime-vapor/src/insertionState.ts index a4bd23ed4c..993dc65b0d 100644 --- a/packages/runtime-vapor/src/insertionState.ts +++ b/packages/runtime-vapor/src/insertionState.ts @@ -22,6 +22,11 @@ export type InsertionParent = ParentNode & { export let insertionParent: InsertionParent | undefined export let insertionAnchor: Node | 0 | undefined | null +// indicates whether the insertion is the last one in the parent. +// if true, means no more nodes need to be hydrated after this insertion, +// advancing current hydration node to parent nextSibling +export let isLastInsertion: boolean | undefined + /** * This function is called before a block type that requires insertion * (component, slot outlet, if, for) is created. The state is used for actual @@ -30,8 +35,10 @@ export let insertionAnchor: Node | 0 | undefined | null export function setInsertionState( parent: ParentNode & { $fc?: Node | null }, anchor?: Node | 0 | null | number, + last?: boolean, ): void { insertionParent = parent + isLastInsertion = last if (anchor !== undefined) { if (isHydrating) { @@ -51,5 +58,5 @@ export function setInsertionState( } export function resetInsertionState(): void { - insertionParent = insertionAnchor = undefined + insertionParent = insertionAnchor = isLastInsertion = undefined }