From 32f753987ebbb9bf77b7d8abebed0270b386ec8c Mon Sep 17 00:00:00 2001 From: daiwei Date: Thu, 21 May 2026 17:57:48 +0800 Subject: [PATCH 1/2] perf(vapor): pack createIf metadata into flags --- .../__snapshots__/compile.spec.ts.snap | 4 +- .../TransformTransition.spec.ts.snap | 6 +-- .../__snapshots__/logicalIndex.spec.ts.snap | 26 +++++----- .../transformChildren.spec.ts.snap | 2 +- .../transformElement.spec.ts.snap | 2 +- .../__snapshots__/transformKey.spec.ts.snap | 4 +- .../transformTemplateRef.spec.ts.snap | 2 +- .../__snapshots__/vFor.spec.ts.snap | 2 +- .../transforms/__snapshots__/vIf.spec.ts.snap | 44 ++++++++--------- .../__snapshots__/vOnce.spec.ts.snap | 4 +- .../__snapshots__/vSlot.spec.ts.snap | 14 +++--- .../__tests__/transforms/vIf.spec.ts | 23 +++++++++ packages/compiler-vapor/src/generators/if.ts | 48 +++++++++++++++++-- .../__tests__/components/KeepAlive.spec.ts | 32 ++++--------- .../__tests__/helpers/useCssVars.spec.ts | 3 +- packages/runtime-vapor/__tests__/if.spec.ts | 15 ++---- packages/runtime-vapor/src/apiCreateIf.ts | 22 +++++---- packages/shared/src/vaporFlags.ts | 29 +++++++++++ 18 files changed, 176 insertions(+), 106 deletions(-) diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap index 377dd3df61a..2fb57531881 100644 --- a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap @@ -41,7 +41,7 @@ export function render(_ctx) { const n2 = _createComponentWithFallback(_component_Bar) _withVaporDirectives(n2, [[_directive_hello, void 0, void 0, { world: true }]]) return n3 - }, null, 1, true) + }, null, 17 /* BLOCK_SHAPE, ONCE */) return n0 }) }, true) @@ -277,7 +277,7 @@ export function render(_ctx) { const n1 = _createIf(() => (true), () => { const n3 = t0() return n3 - }, null, 1, true) + }, null, 17 /* BLOCK_SHAPE, ONCE */) _renderEffect(() => _setProp(n4, "disabled", _ctx.foo)) return n6 }" 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 82a1d0dcd20..a843242eca7 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap @@ -56,9 +56,9 @@ export function render(_ctx) { }, () => { const n13 = t1() return n13 - }, 5, null, 2) + }, 101 /* BLOCK_SHAPE, INDEX_SHIFT */) return n14 - }, 5, null, 1), 5, null, 0) + }, 69 /* BLOCK_SHAPE, INDEX_SHIFT */), 37 /* BLOCK_SHAPE, INDEX_SHIFT */) return [n0, n3, n7] } }, true) @@ -113,7 +113,7 @@ export function render(_ctx) { const n0 = _createIf(() => (_ctx.show), () => { const n2 = t0() return n2 - }, null, 1) + }) return n0 } }, true) diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/logicalIndex.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/logicalIndex.spec.ts.snap index 20666311567..d02452384c3 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/logicalIndex.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/logicalIndex.spec.ts.snap @@ -61,7 +61,7 @@ export function render(_ctx) { const n1 = _createIf(() => (true), () => { const n3 = t0() return n3 - }, null, 1, true) + }, null, 17 /* BLOCK_SHAPE, ONCE */) return n5 }" `; @@ -80,7 +80,7 @@ export function render(_ctx) { const n1 = _createIf(() => (true), () => { const n3 = t0() return n3 - }, null, 1, true) + }, null, 17 /* BLOCK_SHAPE, ONCE */) return n4 }" `; @@ -100,7 +100,7 @@ export function render(_ctx) { const n1 = _createIf(() => (true), () => { const n3 = t0() return n3 - }, null, 1, true) + }, null, 17 /* BLOCK_SHAPE, ONCE */) return n5 }" `; @@ -344,7 +344,7 @@ export function render(_ctx) { const n0 = _createIf(() => (_ctx.show), () => { const n2 = t0() return n2 - }, null, 1) + }) return n3 }" `; @@ -361,7 +361,7 @@ export function render(_ctx) { const n0 = _createIf(() => (_ctx.show), () => { const n2 = t0() return n2 - }, null, 1) + }) return n4 }" `; @@ -377,7 +377,7 @@ export function render(_ctx) { const n0 = _createIf(() => (_ctx.show), () => { const n2 = t0() return n2 - }, null, 1) + }) return n3 }" `; @@ -399,7 +399,7 @@ export function render(_ctx) { }, () => { const n5 = t1() return n5 - }, 5, null, 0) + }, 37 /* BLOCK_SHAPE, INDEX_SHIFT */) _setInsertionState(n7, null, 2) const n6 = _createAssetComponent("Comp2") return n7 @@ -423,7 +423,7 @@ export function render(_ctx) { }, () => { const n5 = t1() return n5 - }, 5, null, 0) + }, 37 /* BLOCK_SHAPE, INDEX_SHIFT */) return n6 }" `; @@ -444,7 +444,7 @@ export function render(_ctx) { }, () => { const n4 = t1() return n4 - }, 5, null, 0) + }, 37 /* BLOCK_SHAPE, INDEX_SHIFT */) return n6 }" `; @@ -469,7 +469,7 @@ export function render(_ctx) { }, () => { const n6 = t2() return n6 - }, 5, null, 1), 5, null, 0) + }, 69 /* BLOCK_SHAPE, INDEX_SHIFT */), 37 /* BLOCK_SHAPE, INDEX_SHIFT */) return n8 }" `; @@ -489,7 +489,7 @@ export function render(_ctx) { }, () => { const n4 = t1() return n4 - }, 5, null, 0) + }, 37 /* BLOCK_SHAPE, INDEX_SHIFT */) return n5 }" `; @@ -509,7 +509,7 @@ export function render(_ctx) { }, () => { const n4 = t1() return n4 - }, 5, null, 0) + }, 37 /* BLOCK_SHAPE, INDEX_SHIFT */) _setInsertionState(n6, null, 2) const n5 = _createAssetComponent("Comp") return n6 @@ -531,7 +531,7 @@ export function render(_ctx) { }, () => { const n4 = t1() return n4 - }, 5, null, 0) + }, 37 /* BLOCK_SHAPE, INDEX_SHIFT */) return n5 }" `; 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 a7b479e73e3..67780441347 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformChildren.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformChildren.spec.ts.snap @@ -12,7 +12,7 @@ export function render(_ctx) { const n0 = _createIf(() => (1), () => { const n2 = t0() return n2 - }, null, 1, true) + }, null, 17 /* BLOCK_SHAPE, ONCE */) return n4 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap index 54bcdb39497..5c2dade12cc 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap @@ -131,7 +131,7 @@ export function render(_ctx) { const n1 = _createIf(() => (_ctx.ok), () => { const n3 = _createComponentWithFallback(_component_Child) return n3 - }, null, 1) + }) return n1 }) }) diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformKey.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformKey.spec.ts.snap index 87321ca4df7..1f85fc87ef3 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformKey.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformKey.spec.ts.snap @@ -127,7 +127,7 @@ export function render(_ctx) { return n5 }) return n4 - }, null, 1), 5, null, 0) + }), 37 /* BLOCK_SHAPE, INDEX_SHIFT */) return n0 }" `; @@ -156,7 +156,7 @@ export function render(_ctx) { return n3 }) return n2 - }, null, 1) + }) return n0 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap index 916e5febda2..8693971aa28 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap @@ -175,7 +175,7 @@ export function render(_ctx) { const n2 = t0() _setTemplateRef(n2, "foo") return n2 - }, null, 1, true) + }, null, 17 /* BLOCK_SHAPE, ONCE */) return n0 }" `; 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 21277589f1b..145db0d53dc 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap @@ -355,7 +355,7 @@ export function render(_ctx) { }, () => { const n6 = _createComponentWithFallback(_component_Comp) return n6 - }, 10, null, 0) + }, 42 /* BLOCK_SHAPE, INDEX_SHIFT */) return n2 }, undefined, 16) return n0 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 d4602254138..46124b29966 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vIf.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vIf.spec.ts.snap @@ -10,7 +10,7 @@ export function render(_ctx) { const x2 = _txt(n2) _renderEffect(() => _setText(x2, _toDisplayString(_ctx.msg))) return n2 - }, null, 1) + }) return n0 }" `; @@ -36,7 +36,7 @@ export function render(_ctx) { const n10 = t3() const n11 = t4() return [n10, n11] - }, 10, null, 1), 5, null, 0) + }, 74 /* BLOCK_SHAPE, INDEX_SHIFT */), 37 /* BLOCK_SHAPE, INDEX_SHIFT */) const n13 = t5() const x13 = _txt(n13) _renderEffect(() => _setText(x13, _toDisplayString(_ctx.text))) @@ -52,7 +52,7 @@ export function render(_ctx) { const n0 = _createIf(() => (_ctx.ok), () => { const n2 = _createComponentWithFallback(_component_Component, null, null, true) return n2 - }, null, 1) + }) return n0 }" `; @@ -65,11 +65,11 @@ export function render(_ctx) { const n0 = _createIf(() => (_ctx.ok), () => { const n2 = t0() return n2 - }, null, 1) + }) const n3 = _createIf(() => (_ctx.ok), () => { const n5 = t0() return n5 - }, null, 1) + }) return [n0, n3] }" `; @@ -87,11 +87,11 @@ export function render(_ctx) { }, () => _createIf(() => (_ctx.bar), () => { const n4 = t1() return n4 - }, null, 1), 5, null, 0) + }), 37 /* BLOCK_SHAPE, INDEX_SHIFT */) const n6 = _createIf(() => (_ctx.baz), () => { const n8 = t2() return n8 - }, null, 1) + }) return [n0, n6] }" `; @@ -106,7 +106,7 @@ export function render(_ctx) { const n2 = t0() const n3 = t1() return [n2, n3] - }, null, 2) + }, null, 2 /* BLOCK_SHAPE */) return n0 }" `; @@ -119,7 +119,7 @@ export function render(_ctx) { const n0 = _createIf(() => (_ctx.foo), () => { const n2 = t0() return n2 - }, null, 1) + }) return n0 }" `; @@ -132,7 +132,7 @@ export function render(_ctx) { const n0 = _createIf(() => (_ctx.foo), () => { const n2 = t0() return n2 - }, null, 1) + }) return n0 }" `; @@ -148,7 +148,7 @@ export function render(_ctx) { return n4 }, undefined, 8) return n2 - }, null, 1) + }) return n0 }" `; @@ -166,7 +166,7 @@ export function render(_ctx) { return n4 }, (item, index) => (index), 8) return n2 - }, null, 1) + }) return n0 }" `; @@ -185,7 +185,7 @@ export function render(_ctx) { }, () => { const n5 = t2() return n5 - }, 6, null, 0) + }, 38 /* BLOCK_SHAPE, INDEX_SHIFT */) return n0 }" `; @@ -204,7 +204,7 @@ export function render(_ctx) { const x4 = _txt(n4) _renderEffect(() => _setText(x4, _toDisplayString(_ctx.msg))) return [n2, n3, n4] - }, null, 2) + }, null, 2 /* BLOCK_SHAPE */) return n0 }" `; @@ -221,7 +221,7 @@ export function render(_ctx) { }, () => { const n4 = t1() return n4 - }, 5, null, 0) + }, 37 /* BLOCK_SHAPE, INDEX_SHIFT */) return n0 }" `; @@ -245,7 +245,7 @@ export function render(_ctx) { }, () => { const n10 = t2() return n10 - }, 5, true, 2), 5, null, 1), 5, null, 0) + }, 21 /* BLOCK_SHAPE, ONCE */), 69 /* BLOCK_SHAPE, INDEX_SHIFT */), 37 /* BLOCK_SHAPE, INDEX_SHIFT */) return n0 }" `; @@ -262,7 +262,7 @@ export function render(_ctx) { }, () => _createIf(() => (_ctx.orNot), () => { const n4 = t1() return n4 - }, null, 1), 5, null, 0) + }), 37 /* BLOCK_SHAPE, INDEX_SHIFT */) return n0 }" `; @@ -280,7 +280,7 @@ export function render(_ctx) { const n0 = _createIf(() => (_ctx.foo), () => { const n2 = t0() return n2 - }, null, 1) + }) _setInsertionState(n8, null, 1) const n3 = _createIf(() => (_ctx.bar), () => { const n5 = t1() @@ -288,7 +288,7 @@ export function render(_ctx) { }, () => { const n7 = t2() return n7 - }, 5, null, 1) + }, 69 /* BLOCK_SHAPE, INDEX_SHIFT */) return n8 }" `; @@ -306,7 +306,7 @@ export function render(_ctx) { }, () => _createIf(() => (_ctx.bar), () => { const n4 = t1() return n4 - }, null, 1), 5, null, 0) + }), 37 /* BLOCK_SHAPE, INDEX_SHIFT */) const n6 = t2() return [n0, n6] }" @@ -322,7 +322,7 @@ export function render(_ctx) { const x2 = _txt(n2) _setText(x2, _toDisplayString(_ctx.msg)) return n2 - }, null, 1, true) + }, null, 17 /* BLOCK_SHAPE, ONCE */) return n0 }" `; @@ -336,7 +336,7 @@ export function render(_ctx) { const n2 = t0() _renderEffect(() => _setDynamicEvents(n2, { click: _ctx.clickEvent })) return n2 - }, null, 1, true) + }, null, 17 /* BLOCK_SHAPE, ONCE */) return n0 }" `; 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 57c8a129001..c74d2212f2e 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap @@ -102,7 +102,7 @@ export function render(_ctx) { const n0 = _createIf(() => (_ctx.expr), () => { const n2 = t0() return n2 - }, null, 1, true) + }, null, 17 /* BLOCK_SHAPE, ONCE */) return n0 }" `; @@ -119,7 +119,7 @@ export function render(_ctx) { }, () => { const n4 = t1() return n4 - }, 5, true, 0) + }, 21 /* BLOCK_SHAPE, ONCE */) return n0 }" `; 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 29f4e9326ad..cf767c04cd1 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap @@ -214,7 +214,7 @@ export function render(_ctx) { const n0 = _createIf(() => (_ctx.ok), () => { const n2 = _createSlot("default", null) return n2 - }, null, 1) + }) return n0 }) }, true) @@ -399,7 +399,7 @@ export function render(_ctx) { }, () => { const n5 = _createComponentWithFallback(_component_Bar) return n5 - }, 5, true, 0) + }, 21 /* BLOCK_SHAPE, ONCE */) return n6 }" `; @@ -647,7 +647,7 @@ export function render(_ctx) { _setInsertionState(n3, null, 0) const n2 = _createComponentWithFallback(_component_ChildComp) return n3 - }, null, 1) + }) return n0 }) }, true) @@ -682,7 +682,7 @@ export function render(_ctx) { _setInsertionState(n3, null, 0) const n2 = _createPlainElement("my-element") return n3 - }, null, 1) + }) return n0 }) }, true) @@ -721,9 +721,9 @@ export function render(_ctx) { _setInsertionState(n5, null, 0) const n4 = _createComponentWithFallback(_component_ChildComp) return n5 - }, null, 1) + }) return n6 - }, null, 1) + }) return n0 }) }, true) @@ -812,7 +812,7 @@ export function render(_ctx) { }, () => { const n4 = t1() return n4 - }, 5, null, 0) + }, 37 /* BLOCK_SHAPE, INDEX_SHIFT */) return n0 } }, true) diff --git a/packages/compiler-vapor/__tests__/transforms/vIf.spec.ts b/packages/compiler-vapor/__tests__/transforms/vIf.spec.ts index 55227203314..39f85f6f466 100644 --- a/packages/compiler-vapor/__tests__/transforms/vIf.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vIf.spec.ts @@ -66,6 +66,29 @@ describe('compiler: v-if', () => { expect(code).matchSnapshot() }) + test('omits default single-root flags', () => { + const { code } = compileWithVIf(`
`) + + expect(code).contains(`const n0 = _createIf(() => (_ctx.ok), () => {`) + expect(code).not.contains(`}, null, 1)`) + }) + + test('packs once flag', () => { + const { code } = compileWithVIf(`
`) + + expect(code).contains(`}, null, 17 /* BLOCK_SHAPE, ONCE */)`) + expect(code).not.contains(`}, null, 1, true)`) + }) + + test('packs branch index', () => { + const { code } = compileWithVIf( + `
foo
bar
`, + ) + + expect(code).contains(`}, 37 /* BLOCK_SHAPE, INDEX_SHIFT */)`) + expect(code).not.contains(`}, 5, null, 0)`) + }) + test('multiple v-if at root', () => { const { code, ir } = compileWithVIf( `
foo
bar
baz
`, diff --git a/packages/compiler-vapor/src/generators/if.ts b/packages/compiler-vapor/src/generators/if.ts index 74302e8132e..bfbe7afc1c3 100644 --- a/packages/compiler-vapor/src/generators/if.ts +++ b/packages/compiler-vapor/src/generators/if.ts @@ -1,5 +1,6 @@ import type { CodegenContext } from '../generate' import { IRNodeTypes, type IfIRNode } from '../ir' +import { VaporBlockShape, VaporIfFlags } from '@vue/shared' import { genBlock } from './block' import { genExpression } from './expression' import { type CodeFragment, NEWLINE, buildCodeFragment, genCall } from './utils' @@ -12,6 +13,7 @@ export function genIf( const { helper } = context const { condition, positive, negative, once, index, blockShape } = oper const [frag, push] = buildCodeFragment() + const flags = genIfFlags(blockShape, once, negative ? index : undefined) const conditionExpr: CodeFragment[] = [ '() => (', @@ -37,13 +39,49 @@ export function genIf( conditionExpr, positiveArg, negativeArg, - String(blockShape), - once && 'true', - // index is only used when the branch can change - // for transition keys and keep-alive caching - index !== undefined && negative && String(index), + flags, ), ) return frag } + +function genIfFlags( + blockShape: number, + once: boolean | undefined, + index: number | undefined, +): string | false { + let flags = blockShape + if (once) { + flags |= VaporIfFlags.ONCE + } else if (index !== undefined) { + // The encoded index is shifted by +1 so runtime can use 0 as the unkeyed + // sentinel while preserving source index 0. + flags |= (index + 1) << VaporIfFlags.INDEX_SHIFT + } + + // This is the only omitted-flags case: true branch is single-root, false + // branch is empty, and there is no once/index metadata. + if (flags === VaporBlockShape.SINGLE_ROOT) { + return false + } + + return __DEV__ + ? `${flags} /* ${genIfFlagNames(once, index)} */` + : String(flags) +} + +function genIfFlagNames( + once: boolean | undefined, + index: number | undefined, +): string { + const names = ['BLOCK_SHAPE'] + + if (once) { + names.push('ONCE') + } else if (index !== undefined) { + names.push('INDEX_SHIFT') + } + + return names.join(', ') +} diff --git a/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts b/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts index 7ad95da16cf..703c1d7dc48 100644 --- a/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts +++ b/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts @@ -249,9 +249,7 @@ describe('VaporKeepAlive', () => { setRefB(n1, instanceB) return n1 }, - undefined, - undefined, - 0, + 37, ), }) }, @@ -310,9 +308,7 @@ describe('VaporKeepAlive', () => { setRefB(n1, instanceB) return n1 }, - undefined, - undefined, - 0, + 37, ), }) }, @@ -444,9 +440,7 @@ describe('VaporKeepAlive', () => { () => toggle.value, () => renderItems(itemsA), () => renderItems(itemsB), - undefined, - undefined, - 0, + 37, ), }) }, @@ -2519,9 +2513,7 @@ describe('VaporKeepAlive', () => { () => toggle.value, () => createComponent(Comp, { id: () => 'a' }), () => createComponent(Comp, { id: () => 'b' }), - undefined, - undefined, - 0, + 37, ), }, ) @@ -2607,9 +2599,7 @@ describe('VaporKeepAlive', () => { () => toggle.value, () => createComponent(Comp, { id: () => 'a' }), () => createComponent(Comp, { id: () => 'b' }), - undefined, - undefined, - 0, + 37, ), }) return keepAlive @@ -2676,9 +2666,7 @@ describe('VaporKeepAlive', () => { () => toggle.value, () => createComponent(Comp, { id: () => 'a' }), () => createComponent(AsyncComp), - undefined, - undefined, - 0, + 37, ), }) return keepAlive @@ -2724,9 +2712,7 @@ describe('VaporKeepAlive', () => { () => toggle.value, () => createComponent(Comp, { id: () => 'a' }), () => createComponent(Comp, { id: () => 'b' }), - undefined, - undefined, - 0, + 37, ), }, ) @@ -2775,9 +2761,7 @@ describe('VaporKeepAlive', () => { () => toggle.value, () => createComponent(Comp, { id: () => 'a' }), () => createComponent(Comp, { id: () => 'b' }), - undefined, - undefined, - 0, + 37, ), }) }, diff --git a/packages/runtime-vapor/__tests__/helpers/useCssVars.spec.ts b/packages/runtime-vapor/__tests__/helpers/useCssVars.spec.ts index 7ac61d38587..dcc6305ab8e 100644 --- a/packages/runtime-vapor/__tests__/helpers/useCssVars.spec.ts +++ b/packages/runtime-vapor/__tests__/helpers/useCssVars.spec.ts @@ -547,8 +547,7 @@ describe('useVaporCssVars', () => { return n2 }, null as any, - undefined, - true, + 17, ) }, }).render({}, root) diff --git a/packages/runtime-vapor/__tests__/if.spec.ts b/packages/runtime-vapor/__tests__/if.spec.ts index fa5fdbcc589..07e877dc4f4 100644 --- a/packages/runtime-vapor/__tests__/if.spec.ts +++ b/packages/runtime-vapor/__tests__/if.spec.ts @@ -145,8 +145,7 @@ describe('createIf', () => { () => toggle.value, () => template('

foo

')(), () => template('

bar

')(), - undefined, - true, + 21, ) }, }).render() @@ -198,9 +197,7 @@ describe('createIf', () => { () => show.value, () => (branch = t0()), () => (branch = t1()), - undefined, - undefined, - 0, + 37, ), ).render() @@ -234,9 +231,7 @@ describe('createIf', () => { () => show.value, () => (branch = t0()), () => (branch = t1()), - undefined, - undefined, - 0, + 37, ), ]).render() @@ -270,9 +265,7 @@ describe('createIf', () => { () => show.value, () => (branch = t0()), () => (branch = t1()), - undefined, - undefined, - 0, + 37, ), }, true, diff --git a/packages/runtime-vapor/src/apiCreateIf.ts b/packages/runtime-vapor/src/apiCreateIf.ts index 54e4a0a85b1..f4a2038c55a 100644 --- a/packages/runtime-vapor/src/apiCreateIf.ts +++ b/packages/runtime-vapor/src/apiCreateIf.ts @@ -15,15 +15,15 @@ import { import { renderEffect } from './renderEffect' import { DynamicFragment } from './fragment' import { createComment, createTextNode } from './dom/node' -import { VaporBlockShape } from '@vue/shared' +import { VaporBlockShape, VaporIfFlags } from '@vue/shared' export function createIf( condition: () => any, b1: BlockFn, b2?: BlockFn, - blockShape?: number, - once?: boolean, - index?: number, + // Default flags encode true single-root + false empty, matching the compiler's + // only omitted-flags case. + flags: number = VaporBlockShape.SINGLE_ROOT, ): Block { const _insertionParent = insertionParent const _insertionAnchor = insertionAnchor @@ -32,10 +32,10 @@ export function createIf( let branchShape: VaporBlockShape | undefined let frag: Block - if (once) { + if (flags & VaporIfFlags.ONCE) { const ok = condition() if (isHydrating) { - branchShape = decodeIfShape(blockShape!, ok) + branchShape = decodeIfShape(flags, ok) hydrationCursor = enterHydrationCursor( branchShape === VaporBlockShape.MULTI_ROOT, ) @@ -47,7 +47,11 @@ export function createIf( : [__DEV__ ? createComment('if') : createTextNode()] } else { // DynamicFragment should be keyed for correct transition behavior - const keyed = index != null + // and KeepAlive cache identity. The encoded value is index + 1, so 0 is + // the unkeyed sentinel and source index 0 becomes encoded index 1. + const index = flags >> VaporIfFlags.INDEX_SHIFT + const keyed = index > 0 + const keyBase = keyed ? (index - 1) * 2 : 0 frag = isHydrating || __DEV__ ? new DynamicFragment('if', keyed, false) @@ -55,14 +59,14 @@ export function createIf( renderEffect(() => { const ok = condition() if (isHydrating) { - branchShape = decodeIfShape(blockShape!, ok) + branchShape = decodeIfShape(flags, ok) hydrationCursor = enterHydrationCursor( branchShape === VaporBlockShape.MULTI_ROOT, ) } ;(frag as DynamicFragment).update( ok ? b1 : b2, - keyed ? index * 2 + (ok ? 0 : 1) : undefined, + keyed ? keyBase + (ok ? 0 : 1) : undefined, ) }) } diff --git a/packages/shared/src/vaporFlags.ts b/packages/shared/src/vaporFlags.ts index 7939ec83ed1..052e1b095de 100644 --- a/packages/shared/src/vaporFlags.ts +++ b/packages/shared/src/vaporFlags.ts @@ -36,6 +36,35 @@ export enum VaporBlockShape { MULTI_ROOT = 2, } +/** + * Bit layout for vapor `createIf` flags. + * + * - bits 0-1: true branch VaporBlockShape + * - bits 2-3: false branch VaporBlockShape + * - bit 4: v-once + * - bits 5+: branch index + 1 for keyed dynamic fragments + * + * Examples: + * - v-once, true single-root, no false branch: 1 | ONCE = 17 + * - keyed index 0, true/false single-root: 1 | (1 << 2) | (1 << 5) = 37 + */ +export enum VaporIfFlags { + /** + * Mask for bits 0-3. Runtime can pass the full flags value to + * decodeIfShape() because that helper masks out these low bits. + */ + BLOCK_SHAPE = 0b1111, + /** + * Marks a branch that is created once and never updated. + */ + ONCE = 1 << 4, + /** + * Shift for keyed branch index. The encoded value is index + 1, so decoded + * zero means "not keyed" and source index 0 still round-trips. + */ + INDEX_SHIFT = 5, +} + /** * Flags used by vapor template factories, shared between the compiler and the * runtime. From 07b1495b8faf517588eabc8ce586774f954c6632 Mon Sep 17 00:00:00 2001 From: daiwei Date: Fri, 22 May 2026 08:42:09 +0800 Subject: [PATCH 2/2] chore: update --- packages/runtime-vapor/__tests__/_utils.ts | 14 ++++++ .../__tests__/components/KeepAlive.spec.ts | 21 +++++---- .../__tests__/helpers/useCssVars.spec.ts | 5 ++- .../runtime-vapor/__tests__/hydration.spec.ts | 43 +++++++++++++++++++ packages/runtime-vapor/__tests__/if.spec.ts | 13 +++--- packages/shared/src/vaporFlags.ts | 4 +- 6 files changed, 82 insertions(+), 18 deletions(-) diff --git a/packages/runtime-vapor/__tests__/_utils.ts b/packages/runtime-vapor/__tests__/_utils.ts index 2a2caaf4715..a1e9206dead 100644 --- a/packages/runtime-vapor/__tests__/_utils.ts +++ b/packages/runtime-vapor/__tests__/_utils.ts @@ -14,6 +14,7 @@ import { type CompilerOptions, compile as compileVapor, } from '@vue/compiler-vapor' +import { VaporIfFlags } from '@vue/shared' export interface RenderContext { component: VaporComponent @@ -149,6 +150,19 @@ export function makeInteropRender(): (comp: Component) => InteropRenderContext { } export { runtimeDom, runtimeVapor, VueServerRenderer } + +export function ifFlags( + blockShape: number, + once = false, + index?: number, +): number { + return ( + blockShape | + (once ? VaporIfFlags.ONCE : 0) | + (index === undefined ? 0 : (index + 1) << VaporIfFlags.INDEX_SHIFT) + ) +} + export function compile( sfc: string, data: runtimeDom.Ref, diff --git a/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts b/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts index 703c1d7dc48..5af0f2ede57 100644 --- a/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts +++ b/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts @@ -14,8 +14,9 @@ import { vModelText, withDirectives, } from 'vue' +import { VaporBlockShape } from '@vue/shared' import type { LooseRawProps, VaporComponent } from '../../src/component' -import { makeRender } from '../_utils' +import { ifFlags, makeRender } from '../_utils' import { VaporKeepAlive } from '../../src/components/KeepAlive' import { child, @@ -39,6 +40,8 @@ import { const define = makeRender() const timeout = (n: number = 0) => new Promise(r => setTimeout(r, n)) +const singleRootIfElse = + VaporBlockShape.SINGLE_ROOT | (VaporBlockShape.SINGLE_ROOT << 2) describe('VaporKeepAlive', () => { let one: VaporComponent @@ -249,7 +252,7 @@ describe('VaporKeepAlive', () => { setRefB(n1, instanceB) return n1 }, - 37, + ifFlags(singleRootIfElse, false, 0), ), }) }, @@ -308,7 +311,7 @@ describe('VaporKeepAlive', () => { setRefB(n1, instanceB) return n1 }, - 37, + ifFlags(singleRootIfElse, false, 0), ), }) }, @@ -440,7 +443,7 @@ describe('VaporKeepAlive', () => { () => toggle.value, () => renderItems(itemsA), () => renderItems(itemsB), - 37, + ifFlags(singleRootIfElse, false, 0), ), }) }, @@ -2513,7 +2516,7 @@ describe('VaporKeepAlive', () => { () => toggle.value, () => createComponent(Comp, { id: () => 'a' }), () => createComponent(Comp, { id: () => 'b' }), - 37, + ifFlags(singleRootIfElse, false, 0), ), }, ) @@ -2599,7 +2602,7 @@ describe('VaporKeepAlive', () => { () => toggle.value, () => createComponent(Comp, { id: () => 'a' }), () => createComponent(Comp, { id: () => 'b' }), - 37, + ifFlags(singleRootIfElse, false, 0), ), }) return keepAlive @@ -2666,7 +2669,7 @@ describe('VaporKeepAlive', () => { () => toggle.value, () => createComponent(Comp, { id: () => 'a' }), () => createComponent(AsyncComp), - 37, + ifFlags(singleRootIfElse, false, 0), ), }) return keepAlive @@ -2712,7 +2715,7 @@ describe('VaporKeepAlive', () => { () => toggle.value, () => createComponent(Comp, { id: () => 'a' }), () => createComponent(Comp, { id: () => 'b' }), - 37, + ifFlags(singleRootIfElse, false, 0), ), }, ) @@ -2761,7 +2764,7 @@ describe('VaporKeepAlive', () => { () => toggle.value, () => createComponent(Comp, { id: () => 'a' }), () => createComponent(Comp, { id: () => 'b' }), - 37, + ifFlags(singleRootIfElse, false, 0), ), }) }, diff --git a/packages/runtime-vapor/__tests__/helpers/useCssVars.spec.ts b/packages/runtime-vapor/__tests__/helpers/useCssVars.spec.ts index dcc6305ab8e..92b549b0450 100644 --- a/packages/runtime-vapor/__tests__/helpers/useCssVars.spec.ts +++ b/packages/runtime-vapor/__tests__/helpers/useCssVars.spec.ts @@ -13,7 +13,8 @@ import { withVaporCtx, } from '@vue/runtime-vapor' import { nextTick, onMounted, reactive, ref } from '@vue/runtime-core' -import { makeRender } from '../_utils' +import { VaporBlockShape } from '@vue/shared' +import { ifFlags, makeRender } from '../_utils' import type { VaporComponent } from '../../src/component' const define = makeRender() @@ -547,7 +548,7 @@ describe('useVaporCssVars', () => { return n2 }, null as any, - 17, + ifFlags(VaporBlockShape.SINGLE_ROOT, true), ) }, }).render({}, root) diff --git a/packages/runtime-vapor/__tests__/hydration.spec.ts b/packages/runtime-vapor/__tests__/hydration.spec.ts index 189e5cbed1f..43753b48970 100644 --- a/packages/runtime-vapor/__tests__/hydration.spec.ts +++ b/packages/runtime-vapor/__tests__/hydration.spec.ts @@ -1278,6 +1278,49 @@ describe('Vapor Mode hydration', () => { ) }) + test('v-if decodes packed branch shapes and keyed index during hydration', async () => { + const code = `` + + const truthy = ref({ ok: true }) + const { container: trueContainer } = await testHydration( + code, + undefined, + truthy, + ) + expect(formatHtml(trueContainer.innerHTML)).toMatchInlineSnapshot( + `" +foobar +"`, + ) + + truthy.value.ok = false + await nextTick() + expect(formatHtml(trueContainer.innerHTML)).toMatchInlineSnapshot( + `" +
baz
+"`, + ) + + const falsy = ref({ ok: false }) + const { container: falseContainer } = await testHydration( + code, + undefined, + falsy, + ) + expect(formatHtml(falseContainer.innerHTML)).toMatchInlineSnapshot( + `"
baz
"`, + ) + + falsy.value.ok = true + await nextTick() + expect(formatHtml(falseContainer.innerHTML)).toMatchInlineSnapshot( + `"foobar"`, + ) + }) + test('v-if on insertion parent', async () => { const data = ref(true) const { container } = await testHydration( diff --git a/packages/runtime-vapor/__tests__/if.spec.ts b/packages/runtime-vapor/__tests__/if.spec.ts index 07e877dc4f4..ff75098781c 100644 --- a/packages/runtime-vapor/__tests__/if.spec.ts +++ b/packages/runtime-vapor/__tests__/if.spec.ts @@ -10,13 +10,16 @@ import { withDirectives, } from '../src' import { nextTick, ref } from '@vue/runtime-dom' +import { VaporBlockShape } from '@vue/shared' import type { Mock } from 'vitest' -import { makeRender } from './_utils' +import { ifFlags, makeRender } from './_utils' import { unmountComponent } from '../src/component' import { setElementText } from '../src/dom/prop' import type { DynamicFragment } from '../src/fragment' const define = makeRender() +const singleRootIfElse = + VaporBlockShape.SINGLE_ROOT | (VaporBlockShape.SINGLE_ROOT << 2) describe('createIf', () => { test('basic', async () => { @@ -145,7 +148,7 @@ describe('createIf', () => { () => toggle.value, () => template('

foo

')(), () => template('

bar

')(), - 21, + ifFlags(singleRootIfElse, true), ) }, }).render() @@ -197,7 +200,7 @@ describe('createIf', () => { () => show.value, () => (branch = t0()), () => (branch = t1()), - 37, + ifFlags(singleRootIfElse, false, 0), ), ).render() @@ -231,7 +234,7 @@ describe('createIf', () => { () => show.value, () => (branch = t0()), () => (branch = t1()), - 37, + ifFlags(singleRootIfElse, false, 0), ), ]).render() @@ -265,7 +268,7 @@ describe('createIf', () => { () => show.value, () => (branch = t0()), () => (branch = t1()), - 37, + ifFlags(singleRootIfElse, false, 0), ), }, true, diff --git a/packages/shared/src/vaporFlags.ts b/packages/shared/src/vaporFlags.ts index 052e1b095de..c4ec7f3aab1 100644 --- a/packages/shared/src/vaporFlags.ts +++ b/packages/shared/src/vaporFlags.ts @@ -50,8 +50,8 @@ export enum VaporBlockShape { */ export enum VaporIfFlags { /** - * Mask for bits 0-3. Runtime can pass the full flags value to - * decodeIfShape() because that helper masks out these low bits. + * Documents the packed true/false branch shape bits. Runtime decode shifts + * to the selected branch first, then masks with 0b11 for one VaporBlockShape. */ BLOCK_SHAPE = 0b1111, /**