From: edison Date: Mon, 8 Sep 2025 06:57:35 +0000 (+0800) Subject: refactor: vapor hydration (#13850) X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=7c45a309507f8f2379d7e1794cc8e5113dfb2f7e;p=thirdparty%2Fvuejs%2Fcore.git refactor: vapor hydration (#13850) --- diff --git a/packages/compiler-core/src/ast.ts b/packages/compiler-core/src/ast.ts index 4b3d16579c..bae13372a9 100644 --- a/packages/compiler-core/src/ast.ts +++ b/packages/compiler-core/src/ast.ts @@ -163,7 +163,6 @@ export interface ComponentNode extends BaseElementNode { | MemoExpression // when cached by v-memo | undefined ssrCodegenNode?: CallExpression - needAnchor?: boolean } export interface SlotOutletNode extends BaseElementNode { @@ -173,14 +172,12 @@ export interface SlotOutletNode extends BaseElementNode { | CacheExpression // when cached by v-once | undefined ssrCodegenNode?: CallExpression - needAnchor?: boolean } export interface TemplateNode extends BaseElementNode { tagType: ElementTypes.TEMPLATE // TemplateNode is a container type that always gets compiled away codegenNode: undefined - needAnchor?: boolean } export interface TextNode extends Node { @@ -290,7 +287,6 @@ export interface IfNode extends Node { type: NodeTypes.IF branches: IfBranchNode[] codegenNode?: IfConditionalExpression | CacheExpression //
- needAnchor?: boolean } export interface IfBranchNode extends Node { @@ -310,7 +306,6 @@ export interface ForNode extends Node { parseResult: ForParseResult children: TemplateChildNode[] codegenNode?: ForCodegenNode - needAnchor?: boolean } export interface ForParseResult { diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 0b5dcca8f7..99020bcf1a 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -167,7 +167,6 @@ function createCodegenContext( ssr = false, isTS = false, inSSR = false, - vapor = false, }: CodegenOptions, ): CodegenContext { const context: CodegenContext = { @@ -183,7 +182,6 @@ function createCodegenContext( ssr, isTS, inSSR, - vapor, source: ast.source, code: ``, column: 1, diff --git a/packages/compiler-core/src/options.ts b/packages/compiler-core/src/options.ts index 03a32e0113..9983071609 100644 --- a/packages/compiler-core/src/options.ts +++ b/packages/compiler-core/src/options.ts @@ -220,11 +220,6 @@ interface SharedTransformCodegenOptions { * @default 'template.vue.html' */ filename?: string - - /** - * Indicates vapor component - */ - vapor?: boolean } export interface TransformOptions diff --git a/packages/compiler-core/src/transform.ts b/packages/compiler-core/src/transform.ts index 11077df625..10121fb5d5 100644 --- a/packages/compiler-core/src/transform.ts +++ b/packages/compiler-core/src/transform.ts @@ -146,7 +146,6 @@ export function createTransformContext( slotted = true, ssr = false, inSSR = false, - vapor = false, ssrCssVars = ``, bindingMetadata = EMPTY_OBJ, inline = false, @@ -174,7 +173,6 @@ export function createTransformContext( slotted, ssr, inSSR, - vapor, ssrCssVars, bindingMetadata, inline, diff --git a/packages/compiler-core/src/transforms/vSlot.ts b/packages/compiler-core/src/transforms/vSlot.ts index da29d0de89..43296dcc9b 100644 --- a/packages/compiler-core/src/transforms/vSlot.ts +++ b/packages/compiler-core/src/transforms/vSlot.ts @@ -100,7 +100,6 @@ export type SlotFnBuilder = ( vFor: DirectiveNode | undefined, slotChildren: TemplateChildNode[], loc: SourceLocation, - parent: ElementNode, ) => FunctionExpression const buildClientSlotFn: SlotFnBuilder = (props, _vForExp, children, loc) => @@ -148,7 +147,7 @@ export function buildSlots( slotsProperties.push( createObjectProperty( arg || createSimpleExpression('default', true), - buildSlotFn(exp, undefined, children, loc, node), + buildSlotFn(exp, undefined, children, loc), ), ) } @@ -201,13 +200,7 @@ export function buildSlots( } const vFor = findDir(slotElement, 'for') - const slotFunction = buildSlotFn( - slotProps, - vFor, - slotChildren, - slotLoc, - slotElement, - ) + const slotFunction = buildSlotFn(slotProps, vFor, slotChildren, slotLoc) // check if this slot is conditional (v-if/v-for) let vIf: DirectiveNode | undefined @@ -311,7 +304,7 @@ export function buildSlots( props: ExpressionNode | undefined, children: TemplateChildNode[], ) => { - const fn = buildSlotFn(props, undefined, children, loc, node) + const fn = buildSlotFn(props, undefined, children, loc) if (__COMPAT__ && context.compatConfig) { fn.isNonScopedSlot = true } diff --git a/packages/compiler-sfc/src/compileTemplate.ts b/packages/compiler-sfc/src/compileTemplate.ts index 1d832388fe..29d1853d2d 100644 --- a/packages/compiler-sfc/src/compileTemplate.ts +++ b/packages/compiler-sfc/src/compileTemplate.ts @@ -253,7 +253,6 @@ function doCompileTemplate({ slotted, sourceMap: true, ...compilerOptions, - vapor, hmr: !isProd, nodeTransforms: nodeTransforms.concat( compilerOptions.nodeTransforms || [], diff --git a/packages/compiler-ssr/__tests__/ssrVaporAnchors.spec.ts b/packages/compiler-ssr/__tests__/ssrVaporAnchors.spec.ts deleted file mode 100644 index 321dce552e..0000000000 --- a/packages/compiler-ssr/__tests__/ssrVaporAnchors.spec.ts +++ /dev/null @@ -1,828 +0,0 @@ -import { getCompiledString } from './utils' - -describe('block anchors', () => { - describe('prepend', () => { - test('prepend anchor with component', () => { - expect( - getCompiledString('
', { vapor: true }), - ).toMatchInlineSnapshot(` - "\`
\`) - _push(_ssrRenderComponent(_component_Comp, null, null, _parent)) - _push(\`\`) - _push(_ssrRenderComponent(_component_Comp, null, null, _parent)) - _push(\`
\`" - `) - }) - - test('prepend anchor with component in ssr slot vnode fallback', () => { - expect( - getCompiledString( - ` -
- -
-
`, - { vapor: true }, - ), - ).toMatchInlineSnapshot(` - "\`\`) - _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent('div'), null, { - default: _withCtx((_, _push, _parent, _scopeId) => { - if (_push) { - _push(\`\`) - _push(_ssrRenderComponent(_component_Comp, null, null, _parent, _scopeId)) - _push(\`\`) - _push(_ssrRenderComponent(_component_Comp, null, null, _parent, _scopeId)) - _push(\`
\`) - } else { - return [ - _createVNode("div", null, [ - _createCommentVNode("[["), - _createVNode(_component_Comp), - _createCommentVNode("]]"), - _createCommentVNode("[["), - _createVNode(_component_Comp), - _createCommentVNode("]]"), - _createVNode("span") - ]) - ] - } - }), - _: 1 /* STABLE */ - }), _parent) - _push(\`\`" - `) - }) - - test('prepend anchor with slot', () => { - expect( - getCompiledString('
', { - vapor: true, - }), - ).toMatchInlineSnapshot(` - "\`
\`) - _ssrRenderSlot(_ctx.$slots, "foo", {}, null, _push, _parent) - _push(\`\`) - _ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent) - _push(\`
\`" - `) - }) - - test('prepend anchor with slot in ssr slot vnode fallback', () => { - expect( - getCompiledString( - ` -
- - - -
-
`, - { vapor: true }, - ), - ).toMatchInlineSnapshot(` - "\`\`) - _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent('div'), null, { - default: _withCtx((_, _push, _parent, _scopeId) => { - if (_push) { - _push(\`\`) - _ssrRenderSlot(_ctx.$slots, "foo", {}, null, _push, _parent, _scopeId) - _push(\`\`) - _ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent, _scopeId) - _push(\`\`) - } else { - return [ - _createVNode("div", null, [ - _createCommentVNode("[["), - _renderSlot(_ctx.$slots, "foo"), - _createCommentVNode("slot"), - _createCommentVNode("]]"), - _createCommentVNode("[["), - _renderSlot(_ctx.$slots, "default"), - _createCommentVNode("slot"), - _createCommentVNode("]]"), - _createVNode("span") - ]) - ] - } - }), - _: 3 /* FORWARDED */ - }), _parent) - _push(\`\`" - `) - }) - - test('prepend anchor with v-if/else-if/else', () => { - expect( - getCompiledString( - `
- - - - -
`, - { - vapor: true, - }, - ), - ).toMatchInlineSnapshot(` - "\`
\`) - if (_ctx.foo) { - _push(\`\`) - _push(\`\`) - } else if (_ctx.bar) { - _push(\`\`) - _push(\`\`) - } else { - _push(\`\`) - _push(\`\`) - } - _push(\`
\`" - `) - }) - - test('prepend anchor with v-if/else-if/else in ssr slot vnode fallback', () => { - expect( - getCompiledString( - ` -
- - - - -
-
`, - { vapor: true }, - ), - ).toMatchInlineSnapshot(` - "\`\`) - _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent('div'), null, { - default: _withCtx((_, _push, _parent, _scopeId) => { - if (_push) { - _push(\`\`) - if (_ctx.foo) { - _push(\`\`) - _push(\`\`) - } else if (_ctx.bar) { - _push(\`\`) - _push(\`\`) - } else { - _push(\`\`) - _push(\`\`) - } - _push(\`\`) - } else { - return [ - _createVNode("div", null, [ - _createCommentVNode("[["), - (_ctx.foo) - ? (_openBlock(), _createBlock(_Fragment, { key: 0 }, [ - _createVNode("span"), - _createCommentVNode("") - ], 64 /* STABLE_FRAGMENT */)) - : (_ctx.bar) - ? (_openBlock(), _createBlock(_Fragment, { key: 1 }, [ - _createVNode("span"), - _createCommentVNode("") - ], 64 /* STABLE_FRAGMENT */)) - : (_openBlock(), _createBlock(_Fragment, { key: 2 }, [ - _createVNode("span"), - _createCommentVNode("") - ], 64 /* STABLE_FRAGMENT */)), - _createCommentVNode("]]"), - _createVNode("span") - ]) - ] - } - }), - _: 1 /* STABLE */ - }), _parent) - _push(\`\`" - `) - }) - - test('prepend anchor with nested v-if', () => { - expect( - getCompiledString( - `
- - - - - - - - - - - - - -
`, - { - vapor: true, - }, - ), - ).toMatchInlineSnapshot(` - "\`
\`) - if (_ctx.foo) { - _push(\`\`) - if (_ctx.foo1) { - _push(\`\`) - _push(\`\`) - } else { - _push(\`\`) - } - _push(\`\`) - _push(\`\`) - } else if (_ctx.bar) { - _push(\`\`) - if (_ctx.bar1) { - _push(\`\`) - _push(\`\`) - } else { - _push(\`\`) - } - _push(\`\`) - _push(\`\`) - } else { - _push(\`\`) - if (_ctx.bar2) { - _push(\`\`) - _push(\`\`) - } else { - _push(\`\`) - } - _push(\`\`) - _push(\`\`) - } - _push(\`
\`" - `) - }) - - test('prepend anchor with nested v-if in ssr slot vnode fallback', () => { - expect( - getCompiledString( - ` -
- - - - - - - - - - - - - -
-
`, - { vapor: true }, - ), - ).toMatchInlineSnapshot(` - "\`\`) - _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent('div'), null, { - default: _withCtx((_, _push, _parent, _scopeId) => { - if (_push) { - _push(\`\`) - if (_ctx.foo) { - _push(\`\`) - if (_ctx.foo1) { - _push(\`\`) - _push(\`\`) - } else { - _push(\`\`) - } - _push(\`\`) - _push(\`\`) - } else if (_ctx.bar) { - _push(\`\`) - if (_ctx.bar1) { - _push(\`\`) - _push(\`\`) - } else { - _push(\`\`) - } - _push(\`\`) - _push(\`\`) - } else { - _push(\`\`) - if (_ctx.bar2) { - _push(\`\`) - _push(\`\`) - } else { - _push(\`\`) - } - _push(\`\`) - _push(\`\`) - } - _push(\`\`) - } else { - return [ - _createVNode("div", null, [ - _createCommentVNode("[["), - (_ctx.foo) - ? (_openBlock(), _createBlock(_Fragment, { key: 0 }, [ - _createVNode("span", null, [ - _createCommentVNode("[["), - (_ctx.foo1) - ? (_openBlock(), _createBlock("span", { key: 0 })) - : _createCommentVNode("v-if", true), - _createCommentVNode("if"), - _createCommentVNode("]]"), - _createVNode("span") - ]), - _createCommentVNode("") - ], 64 /* STABLE_FRAGMENT */)) - : (_ctx.bar) - ? (_openBlock(), _createBlock(_Fragment, { key: 1 }, [ - _createVNode("span", null, [ - _createCommentVNode("[["), - (_ctx.bar1) - ? (_openBlock(), _createBlock("span", { key: 0 })) - : _createCommentVNode("v-if", true), - _createCommentVNode("if"), - _createCommentVNode("]]"), - _createVNode("span") - ]), - _createCommentVNode("") - ], 64 /* STABLE_FRAGMENT */)) - : (_openBlock(), _createBlock(_Fragment, { key: 2 }, [ - _createVNode("span", null, [ - _createCommentVNode("[["), - (_ctx.bar2) - ? (_openBlock(), _createBlock("span", { key: 0 })) - : _createCommentVNode("v-if", true), - _createCommentVNode("if"), - _createCommentVNode("]]"), - _createVNode("span") - ]), - _createCommentVNode("") - ], 64 /* STABLE_FRAGMENT */)), - _createCommentVNode("]]"), - _createVNode("span") - ]) - ] - } - }), - _: 1 /* STABLE */ - }), _parent) - _push(\`\`" - `) - }) - - test('prepend anchor with template v-if', () => { - expect( - getCompiledString( - ` -
- -
-
-
`, - { vapor: true }, - ), - ).toMatchInlineSnapshot(` - "\`\`) - _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.tag), null, { - default: _withCtx((_, _push, _parent, _scopeId) => { - if (_push) { - if (_ctx.foo) { - _push(\`\`) - if (_ctx.depth < 5) { - _push(\` foo \`) - _push(\`\`) - } else { - _push(\`\`) - } - _push(\`\`) - _push(\`\`) - } else { - _push(\`\`) - } - } else { - return [ - (_ctx.foo) - ? (_openBlock(), _createBlock("div", { key: 0 }, [ - _createCommentVNode("[["), - (_ctx.depth < 5) - ? (_openBlock(), _createBlock(_Fragment, { key: 0 }, [ - _createTextVNode(" foo ") - ], 64 /* STABLE_FRAGMENT */)) - : _createCommentVNode("v-if", true), - _createCommentVNode("if"), - _createCommentVNode("]]"), - _createVNode("div") - ])) - : _createCommentVNode("v-if", true), - _createCommentVNode("if") - ] - } - }), - _: 1 /* STABLE */ - }), _parent) - _push(\`\`" - `) - }) - - test('prepend anchor with v-for', () => { - expect( - getCompiledString('
', { - vapor: true, - }), - ).toMatchInlineSnapshot(` - "\`
\`) - _ssrRenderList(_ctx.items, (item) => { - _push(\`\`) - }) - _push(\`
\`" - `) - }) - - test('prepend anchor with v-for in ssr slot vnode fallback', () => { - expect( - getCompiledString( - ` -
- -
-
`, - { vapor: true }, - ), - ).toMatchInlineSnapshot(` - "\`\`) - _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent('div'), null, { - default: _withCtx((_, _push, _parent, _scopeId) => { - if (_push) { - _push(\`\`) - _ssrRenderList(_ctx.items, (item) => { - _push(\`\`) - }) - _push(\`\`) - } else { - return [ - _createVNode("div", null, [ - _createCommentVNode("[["), - (_openBlock(true), _createBlock(_Fragment, null, _renderList(_ctx.items, (item) => { - return (_openBlock(), _createBlock("span")) - }), 256 /* UNKEYED_FRAGMENT */)), - _createCommentVNode("for"), - _createCommentVNode("]]"), - _createVNode("span") - ]) - ] - } - }), - _: 1 /* STABLE */ - }), _parent) - _push(\`\`" - `) - }) - }) - - // TODO add more tests - describe('insert', () => { - test('insertion anchor with component', () => { - expect( - getCompiledString('
', { vapor: true }), - ).toMatchInlineSnapshot(` - "\`
\`) - _push(_ssrRenderComponent(_component_Comp, null, null, _parent)) - _push(\`
\`" - `) - }) - }) - - // TODO add more tests - describe('append', () => { - test('append anchor', () => { - expect( - getCompiledString('
', { vapor: true }), - ).toMatchInlineSnapshot(` - "\`
\`) - _push(_ssrRenderComponent(_component_Comp, null, null, _parent)) - _push(\`\`) - _push(_ssrRenderComponent(_component_Comp, null, null, _parent)) - _push(\`
\`" - `) - }) - }) - - test('mixed anchors', () => { - expect( - getCompiledString('
', { - vapor: true, - }), - ).toMatchInlineSnapshot(` - "\`
\`) - _push(_ssrRenderComponent(_component_Comp, null, null, _parent)) - _push(\`\`) - _push(_ssrRenderComponent(_component_Comp, null, null, _parent)) - _push(\`\`) - _push(_ssrRenderComponent(_component_Comp, null, null, _parent)) - _push(\`
\`" - `) - }) - - test('mixed anchors in ssr slot vnode fallback', () => { - expect( - getCompiledString( - ` -
- - - -
-
`, - { - vapor: true, - }, - ), - ).toMatchInlineSnapshot(` - "\`\`) - _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent('div'), null, { - default: _withCtx((_, _push, _parent, _scopeId) => { - if (_push) { - _push(\`\`) - _push(_ssrRenderComponent(_component_Comp, null, null, _parent, _scopeId)) - _push(\`\`) - _push(_ssrRenderComponent(_component_Comp, null, null, _parent, _scopeId)) - _push(\`\`) - _push(_ssrRenderComponent(_component_Comp, null, null, _parent, _scopeId)) - _push(\`\`) - } else { - return [ - _createVNode("div", null, [ - _createCommentVNode("[["), - _createVNode(_component_Comp), - _createCommentVNode("]]"), - _createVNode("span"), - _createCommentVNode("[["), - _createVNode(_component_Comp), - _createCommentVNode("]]"), - _createVNode("span"), - _createCommentVNode("[["), - _createVNode(_component_Comp), - _createCommentVNode("]]") - ]) - ] - } - }), - _: 1 /* STABLE */ - }), _parent) - _push(\`\`" - `) - }) -}) - -describe('fragment anchors', () => { - test('if', () => { - expect( - getCompiledString( - ` - 1 - 2 - 3 - 4 - `, - { - vapor: true, - }, - ), - ).toMatchInlineSnapshot(` - "\`\`) - _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.tag), null, { - default: _withCtx((_, _push, _parent, _scopeId) => { - if (_push) { - if (_ctx.count === 1) { - _push(\`1\`) - _push(\`\`) - } else if (_ctx.count === 2) { - _push(\`2\`) - _push(\`\`) - } else if (_ctx.count === 3) { - _push(\`3\`) - _push(\`\`) - } else { - _push(\`4\`) - _push(\`\`) - } - } else { - return [ - (_ctx.count === 1) - ? (_openBlock(), _createBlock(_Fragment, { key: 0 }, [ - _createVNode("span", null, "1"), - _createCommentVNode("") - ], 64 /* STABLE_FRAGMENT */)) - : (_ctx.count === 2) - ? (_openBlock(), _createBlock(_Fragment, { key: 1 }, [ - _createVNode("span", null, "2"), - _createCommentVNode("") - ], 64 /* STABLE_FRAGMENT */)) - : (_ctx.count === 3) - ? (_openBlock(), _createBlock(_Fragment, { key: 2 }, [ - _createVNode("span", null, "3"), - _createCommentVNode("") - ], 64 /* STABLE_FRAGMENT */)) - : (_openBlock(), _createBlock(_Fragment, { key: 3 }, [ - _createVNode("span", null, "4"), - _createCommentVNode("") - ], 64 /* STABLE_FRAGMENT */)) - ] - } - }), - _: 1 /* STABLE */ - }), _parent) - _push(\`\`" - `) - }) - - test('if + v-html/v-text', () => { - expect( - getCompiledString( - ` - - - 4 - `, - { - vapor: true, - }, - ), - ).toMatchInlineSnapshot(` - "\`\`) - _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.tag), null, { - default: _withCtx((_, _push, _parent, _scopeId) => { - if (_push) { - if (_ctx.count === 1) { - _push(\`\${ - (_ctx.html) ?? '' - }\`) - _push(\`\`) - } else if (_ctx.count === 2) { - _push(\`\${ - _ssrInterpolate(_ctx.txt) - }\`) - _push(\`\`) - } else { - _push(\`4\`) - _push(\`\`) - } - } else { - return [ - (_ctx.count === 1) - ? (_openBlock(), _createBlock(_Fragment, { key: 0 }, [ - _createVNode("span", { innerHTML: _ctx.html }, null, 8 /* PROPS */, ["innerHTML"]), - _createCommentVNode("") - ], 64 /* STABLE_FRAGMENT */)) - : (_ctx.count === 2) - ? (_openBlock(), _createBlock(_Fragment, { key: 1 }, [ - _createVNode("span", { - textContent: _toDisplayString(_ctx.txt) - }, null, 8 /* PROPS */, ["textContent"]), - _createCommentVNode("") - ], 64 /* STABLE_FRAGMENT */)) - : (_openBlock(), _createBlock(_Fragment, { key: 2 }, [ - _createVNode("span", null, "4"), - _createCommentVNode("") - ], 64 /* STABLE_FRAGMENT */)) - ] - } - }), - _: 1 /* STABLE */ - }), _parent) - _push(\`\`" - `) - }) - - test('for', () => { - expect( - getCompiledString( - ` - {{item}} - `, - { - vapor: true, - }, - ), - ).toMatchInlineSnapshot(` - "\`\`) - _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.tag), null, { - default: _withCtx((_, _push, _parent, _scopeId) => { - if (_push) { - _ssrRenderList(_ctx.items, (item) => { - _push(\`\${ - _ssrInterpolate(item) - }\`) - }) - _push(\`\`) - } else { - return [ - (_openBlock(true), _createBlock(_Fragment, null, _renderList(_ctx.items, (item) => { - return (_openBlock(), _createBlock("span", null, _toDisplayString(item), 1 /* TEXT */)) - }), 256 /* UNKEYED_FRAGMENT */)), - _createCommentVNode("for") - ] - } - }), - _: 1 /* STABLE */ - }), _parent) - _push(\`\`" - `) - }) - - test('slot', () => { - expect( - getCompiledString( - `
- - -
`, - { vapor: true }, - ), - ).toMatchInlineSnapshot(` - "\`
\`) - _ssrRenderSlot(_ctx.$slots, "foo", {}, null, _push, _parent) - _push(\`\`) - _ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent) - _push(\`
\`" - `) - }) - - test('forwarded slot', () => { - expect( - getCompiledString( - ` - - - `, - { vapor: true }, - ), - ).toMatchInlineSnapshot(` - "\`\`) - _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.tag), null, { - default: _withCtx((_, _push, _parent, _scopeId) => { - if (_push) { - _ssrRenderSlot(_ctx.$slots, "foo", {}, null, _push, _parent, _scopeId) - _push(\`\`) - _ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent, _scopeId) - _push(\`\`) - } else { - return [ - _renderSlot(_ctx.$slots, "foo"), - _createCommentVNode("slot"), - _renderSlot(_ctx.$slots, "default"), - _createCommentVNode("slot") - ] - } - }), - _: 3 /* FORWARDED */ - }), _parent) - _push(\`\`" - `) - }) - - test('dynamic component', () => { - expect( - getCompiledString( - ` -
- -
-
`, - { vapor: true }, - ), - ).toMatchInlineSnapshot(` - "\`\`) - _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent("tag"), null, { - default: _withCtx((_, _push, _parent, _scopeId) => { - if (_push) { - _push(\`\`) - _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent("foo"), null, null), _parent, _scopeId) - _push(\`\`) - } else { - return [ - _createVNode("div", null, [ - (_openBlock(), _createBlock(_resolveDynamicComponent("foo"))), - _createCommentVNode("dynamic-component") - ]) - ] - } - }), - _: 1 /* STABLE */ - }), _parent) - _push(\`\`" - `) - }) -}) diff --git a/packages/compiler-ssr/src/ssrCodegenTransform.ts b/packages/compiler-ssr/src/ssrCodegenTransform.ts index d9e48c47d0..fe5b1ee267 100644 --- a/packages/compiler-ssr/src/ssrCodegenTransform.ts +++ b/packages/compiler-ssr/src/ssrCodegenTransform.ts @@ -1,16 +1,13 @@ import { - type AttributeNode, type BlockStatement, type CallExpression, type CompilerError, type CompilerOptions, - type DirectiveNode, type ElementNode, ElementTypes, type IfStatement, type JSChildNode, NodeTypes, - type PlainElementNode, type RootNode, type TemplateChildNode, type TemplateLiteral, @@ -24,12 +21,7 @@ import { isText, processExpression, } from '@vue/compiler-dom' -import { - BLOCK_ANCHOR_END_LABEL, - BLOCK_ANCHOR_START_LABEL, - escapeHtml, - isString, -} from '@vue/shared' +import { escapeHtml, isString } from '@vue/shared' import { SSR_INTERPOLATE, ssrHelpers } from './runtimeHelpers' import { ssrProcessIf } from './transforms/ssrVIf' import { ssrProcessFor } from './transforms/ssrVFor' @@ -167,17 +159,12 @@ export function processChildren( disableNestedFragments = false, disableComment = false, ): void { - const vapor = context.options.vapor - if (asFragment && !vapor) { + if (asFragment) { context.pushStringPart(``) } const { children } = parent - if (vapor && isElementWithChildren(parent as PlainElementNode)) { - processBlockNodeAnchor(children) - } - for (let i = 0; i < children.length; i++) { const child = children[i] switch (child.type) { @@ -187,19 +174,11 @@ export function processChildren( ssrProcessElement(child, context) break case ElementTypes.COMPONENT: - if (child.needAnchor) - context.pushStringPart(``) ssrProcessComponent(child, context, parent) - if (child.needAnchor) - context.pushStringPart(``) break case ElementTypes.SLOT: - if (child.needAnchor) - context.pushStringPart(``) ssrProcessSlotOutlet(child, context) - if (child.needAnchor) - context.pushStringPart(``) break case ElementTypes.TEMPLATE: // TODO @@ -234,18 +213,10 @@ export function processChildren( ) break case NodeTypes.IF: - if (child.needAnchor) - context.pushStringPart(``) ssrProcessIf(child, context, disableNestedFragments, disableComment) - if (child.needAnchor) - context.pushStringPart(``) break case NodeTypes.FOR: - if (child.needAnchor) - context.pushStringPart(``) ssrProcessFor(child, context, disableNestedFragments) - if (child.needAnchor) - context.pushStringPart(``) break case NodeTypes.IF_BRANCH: // no-op - handled by ssrProcessIf @@ -267,7 +238,7 @@ export function processChildren( return exhaustiveCheck } } - if (asFragment && !vapor) { + if (asFragment) { context.pushStringPart(``) } } @@ -283,67 +254,6 @@ export function processChildrenAsStatement( return createBlockStatement(childContext.body) } -export function processBlockNodeAnchor(children: TemplateChildNode[]): void { - let prevBlocks: (TemplateChildNode & { needAnchor?: boolean })[] = [] - let hasStaticNode = false - let blockCount = 0 - for (const child of children) { - if (isBlockNode(child)) { - prevBlocks.push(child) - blockCount++ - } - - if (isStaticNode(child)) { - if (prevBlocks.length) { - if (hasStaticNode) { - // insert - prevBlocks.forEach(child => (child.needAnchor = true)) - } else { - // prepend - prevBlocks.forEach(child => (child.needAnchor = true)) - } - prevBlocks = [] - } - hasStaticNode = true - } - } - - // When there is only one block node, no anchor is needed, - // firstChild is used as the hydration node - if (prevBlocks.length && !(blockCount === 1 && !hasStaticNode)) { - // append - prevBlocks.forEach(child => (child.needAnchor = true)) - } -} - -export function hasBlockDir( - props: Array, -): boolean { - return props.some(p => p.name === 'if' || p.name === 'for') -} - -function isBlockNode(child: TemplateChildNode): boolean { - return ( - child.type === NodeTypes.IF || - child.type === NodeTypes.FOR || - (child.type === NodeTypes.ELEMENT && - (child.tagType === ElementTypes.COMPONENT || - child.tagType === ElementTypes.SLOT || - hasBlockDir(child.props))) - ) -} - -function isStaticNode(child: TemplateChildNode): boolean { - return ( - child.type === NodeTypes.TEXT || - child.type === NodeTypes.INTERPOLATION || - child.type === NodeTypes.COMMENT || - (child.type === NodeTypes.ELEMENT && - child.tagType === ElementTypes.ELEMENT && - !hasBlockDir(child.props)) - ) -} - export function isElementWithChildren( node: TemplateChildNode, ): node is ElementNode { diff --git a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts index 47c906a07c..08f78aa561 100644 --- a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts +++ b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts @@ -1,7 +1,6 @@ import { CREATE_VNODE, type CallExpression, - type CommentNode, type CompilerOptions, type ComponentNode, DOMDirectiveTransforms, @@ -9,14 +8,11 @@ import { type DirectiveNode, ElementTypes, type ExpressionNode, - type ForNode, type FunctionExpression, - type IfNode, type JSChildNode, Namespaces, type NodeTransform, NodeTypes, - type PlainElementNode, RESOLVE_DYNAMIC_COMPONENT, type ReturnStatement, type RootNode, @@ -47,8 +43,6 @@ import { import { SSR_RENDER_COMPONENT, SSR_RENDER_VNODE } from '../runtimeHelpers' import { type SSRTransformContext, - isElementWithChildren, - processBlockNodeAnchor, processChildren, processChildrenAsStatement, } from '../ssrCodegenTransform' @@ -61,19 +55,7 @@ import { ssrProcessTransitionGroup, ssrTransformTransitionGroup, } from './ssrTransformTransitionGroup' -import { - BLOCK_ANCHOR_END_LABEL, - BLOCK_ANCHOR_START_LABEL, - DYNAMIC_COMPONENT_ANCHOR_LABEL, - FOR_ANCHOR_LABEL, - IF_ANCHOR_LABEL, - SLOT_ANCHOR_LABEL, - extend, - isArray, - isObject, - isPlainObject, - isSymbol, -} from '@vue/shared' +import { extend, isArray, isObject, isPlainObject, isSymbol } from '@vue/shared' import { buildSSRProps } from './ssrTransformElement' import { ssrProcessTransition, @@ -144,9 +126,9 @@ export const ssrTransformComponent: NodeTransform = (node, context) => { // fallback in case the child is render-fn based). Store them in an array // for later use. if (clonedNode.children.length) { - buildSlots(clonedNode, context, (props, vFor, children, _loc, parent) => { + buildSlots(clonedNode, context, (props, vFor, children) => { vnodeBranches.push( - createVNodeSlotBranch(props, vFor, children, context, parent), + createVNodeSlotBranch(props, vFor, children, context), ) return createFunctionExpression(undefined) }) @@ -282,11 +264,6 @@ export function ssrProcessComponent( // dynamic component (`resolveDynamicComponent` call) // the codegen node is a `renderVNode` call context.pushStatement(node.ssrCodegenNode) - - // anchor for vapor dynamic component - if (context.options.vapor) { - context.pushStringPart(``) - } } } } @@ -309,7 +286,6 @@ function createVNodeSlotBranch( vFor: DirectiveNode | undefined, children: TemplateChildNode[], parentContext: TransformContext, - parent: TemplateChildNode, ): ReturnStatement { // apply a sub-transform using vnode-based transforms. const rawOptions = rawOptionsMap.get(parentContext.root)! @@ -344,9 +320,6 @@ function createVNodeSlotBranch( if (vFor) { wrapperProps.push(extend({}, vFor)) } - if (parentContext.vapor) { - children = injectVaporAnchors(children, parent) - } const wrapperNode: TemplateNode = { type: NodeTypes.ELEMENT, @@ -400,209 +373,6 @@ function subTransform( // - hoists are not enabled for the client branch here } -function injectVaporAnchors( - children: TemplateChildNode[], - parent: TemplateChildNode, -): TemplateChildNode[] { - if (isElementWithChildren(parent)) { - processBlockNodeAnchor(children) - } - - const newChildren: TemplateChildNode[] = [] - for (let i = 0; i < children.length; i++) { - const child = children[i] - - if (child.type !== NodeTypes.ELEMENT) { - newChildren.push(child) - continue - } - - const { tagType, props } = child - let needBlockAnchor: boolean | undefined - - if ( - tagType === ElementTypes.COMPONENT || - tagType === ElementTypes.SLOT || - tagType === ElementTypes.TEMPLATE - ) { - needBlockAnchor = child.needAnchor - } else if (tagType === ElementTypes.ELEMENT) { - let hasIf = false - let hasFor = false - - for (const prop of props) { - if (prop.name === 'if') { - hasIf = true - break - } else if (prop.name === 'for') { - hasFor = true - } - } - - if (hasIf) { - needBlockAnchor = (child as any as IfNode).needAnchor - const lastBranchIndex = findLastIfBranchIndex(children, i) - if (lastBranchIndex > i) { - injectIfAnchors( - needBlockAnchor, - newChildren, - i, - lastBranchIndex, - children, - ) - i = lastBranchIndex - continue - } - } else if (hasFor) { - needBlockAnchor = (child as any as ForNode).needAnchor - } - } - - if (needBlockAnchor) { - newChildren.push(createAnchor(BLOCK_ANCHOR_START_LABEL)) - } - - newChildren.push(child) - - // inject fragment anchor - const fragmentAnchorLabel = getFragmentAnchorLabel(child) - if (fragmentAnchorLabel) newChildren.push(createAnchor(fragmentAnchorLabel)) - - if (needBlockAnchor) { - newChildren.push(createAnchor(BLOCK_ANCHOR_END_LABEL)) - } - - child.children = injectVaporAnchors(child.children, child) - } - - return newChildren -} - -function injectIfAnchors( - needBlockAnchor: boolean | undefined, - newChildren: TemplateChildNode[], - i: number, - lastBranchIndex: number, - children: TemplateChildNode[], -) { - if (needBlockAnchor) { - newChildren.push(createAnchor(BLOCK_ANCHOR_START_LABEL)) - } - - for (let j = i; j <= lastBranchIndex; j++) { - const node = children[j] as PlainElementNode - const fragmentAnchorLabel = getFragmentAnchorLabel(node) - let isElse = false - - const conditionalProps: typeof node.props = [] - const restProps: typeof node.props = [] - - for (const prop of node.props) { - if ( - prop.name === 'if' || - prop.name === 'else-if' || - prop.name === 'else' - ) { - conditionalProps.push(prop) - if (prop.name === 'else') isElse = true - } else { - restProps.push(prop) - } - } - node.props = restProps - - // wrap the node with a template node - const wrapperNode: TemplateNode = { - type: NodeTypes.ELEMENT, - ns: Namespaces.HTML, - tag: 'template', - tagType: ElementTypes.TEMPLATE, - props: conditionalProps, - children: [node], - loc: node.loc, - codegenNode: undefined, - } - newChildren.push(wrapperNode) - - if (fragmentAnchorLabel) { - const repeatCount = j - i - (isElse ? 1 : 0) + 1 - wrapperNode.children.push( - createAnchor(``.repeat(repeatCount)), - ) - } - node.children = injectVaporAnchors(node.children, node) - } - - if (needBlockAnchor) { - newChildren.push(createAnchor(BLOCK_ANCHOR_END_LABEL)) - } -} - -function createAnchor(content: string): CommentNode { - return { - type: NodeTypes.COMMENT, - content, - loc: locStub, - } -} - -function findLastIfBranchIndex( - children: TemplateChildNode[], - ifIndex: number, -): number { - let lastIndex = ifIndex - - for (let i = ifIndex + 1; i < children.length; i++) { - const sibling = children[i] - - if (sibling.type !== NodeTypes.ELEMENT) { - continue - } - - let hasElseIf = false - let hasElse = false - - for (const prop of sibling.props) { - if (prop.name === 'else-if') { - hasElseIf = true - break - } else if (prop.name === 'else') { - hasElse = true - break - } - } - - if (hasElseIf || hasElse) { - lastIndex = i - if (hasElse) { - break - } - } else { - break - } - } - - return lastIndex -} - -function getFragmentAnchorLabel(child: TemplateChildNode): string | undefined { - if (child.type !== NodeTypes.ELEMENT) return - - if (child.tagType === ElementTypes.COMPONENT && child.tag === 'component') { - return DYNAMIC_COMPONENT_ANCHOR_LABEL - } else if (child.tagType === ElementTypes.SLOT) { - return SLOT_ANCHOR_LABEL - } else if ( - child.props.some( - p => p.name === 'if' || p.name === 'else-if' || p.name === 'else', - ) - ) { - return IF_ANCHOR_LABEL - } else if (child.props.some(p => p.name === 'for')) { - return FOR_ANCHOR_LABEL - } -} - function clone(v: any): any { if (isArray(v)) { return v.map(clone) diff --git a/packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts b/packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts index 5c72c8a2a6..aae7a31e8b 100644 --- a/packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts +++ b/packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts @@ -16,7 +16,6 @@ import { type SSRTransformContext, processChildrenAsStatement, } from '../ssrCodegenTransform' -import { SLOT_ANCHOR_LABEL } from '@vue/shared' export const ssrTransformSlotOutlet: NodeTransform = (node, context) => { if (isSlotOutlet(node)) { @@ -94,9 +93,4 @@ export function ssrProcessSlotOutlet( } context.pushStatement(node.ssrCodegenNode!) - - // anchor for vapor slot - if (context.options.vapor) { - context.pushStringPart(``) - } } diff --git a/packages/compiler-ssr/src/transforms/ssrVFor.ts b/packages/compiler-ssr/src/transforms/ssrVFor.ts index f786751f8b..f1541ce4ab 100644 --- a/packages/compiler-ssr/src/transforms/ssrVFor.ts +++ b/packages/compiler-ssr/src/transforms/ssrVFor.ts @@ -13,7 +13,6 @@ import { processChildrenAsStatement, } from '../ssrCodegenTransform' import { SSR_RENDER_LIST } from '../runtimeHelpers' -import { FOR_ANCHOR_LABEL } from '@vue/shared' // Plugin for the first transform pass, which simply constructs the AST node export const ssrTransformFor: NodeTransform = @@ -38,9 +37,8 @@ export function ssrProcessFor( needFragmentWrapper, ) - const vapor = context.options.vapor // v-for always renders a fragment unless explicitly disabled - if (!disableNestedFragments && !vapor) { + if (!disableNestedFragments) { context.pushStringPart(``) } context.pushStatement( @@ -49,12 +47,7 @@ export function ssrProcessFor( renderLoop, ]), ) - if (!disableNestedFragments && !vapor) { + if (!disableNestedFragments) { context.pushStringPart(``) } - - // anchor for vapor v-for fragment - if (vapor) { - context.pushStringPart(``) - } } diff --git a/packages/compiler-ssr/src/transforms/ssrVIf.ts b/packages/compiler-ssr/src/transforms/ssrVIf.ts index f565c1be56..775ad835f2 100644 --- a/packages/compiler-ssr/src/transforms/ssrVIf.ts +++ b/packages/compiler-ssr/src/transforms/ssrVIf.ts @@ -14,7 +14,6 @@ import { type SSRTransformContext, processChildrenAsStatement, } from '../ssrCodegenTransform' -import { IF_ANCHOR_LABEL } from '@vue/shared' // Plugin for the first transform pass, which simply constructs the AST node export const ssrTransformIf: NodeTransform = createStructuralDirectiveTransform( @@ -37,16 +36,6 @@ export function ssrProcessIf( ) context.pushStatement(ifStatement) - // anchor addition rules (matching runtime-vapor behavior): - // - v-else-if: the N-th branch → add N anchors - // - v-else: if there are M preceding branches → add M anchors - const isVapor = context.options.vapor - if (isVapor) { - ifStatement.consequent.body.push( - createCallExpression(`_push`, createIfAnchors(1)), - ) - } - let currentIf = ifStatement for (let i = 1; i < node.branches.length; i++) { const branch = node.branches[i] @@ -61,29 +50,15 @@ export function ssrProcessIf( branch.condition, branchBlockStatement, ) - - if (isVapor) { - branchBlockStatement.body.push( - createCallExpression(`_push`, createIfAnchors(i + 1)), - ) - } } else { // else currentIf.alternate = branchBlockStatement - - if (isVapor) { - branchBlockStatement.body.push( - createCallExpression(`_push`, createIfAnchors(i)), - ) - } } } if (!currentIf.alternate && !disableComment) { currentIf.alternate = createBlockStatement([ - createCallExpression(`_push`, [ - isVapor ? `\`\`` : '``', - ]), + createCallExpression(`_push`, ['``']), ]) } } @@ -102,11 +77,3 @@ function processIfBranch( return processChildrenAsStatement(branch, context, needFragmentWrapper) } - -function createIfAnchors(count: number): string[] { - const anchors: string[] = [] - for (let i = 0; i < count; i++) { - anchors.push(``) - } - return [`\`${anchors.join('')}\``] -} diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap index ea0e2bbf6b..41e2e776bd 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) + _setInsertionState(n3, null) const n2 = _createComponentWithFallback(_component_Bar) _withVaporDirectives(n2, [[_directive_hello, void 0, void 0, { world: true }]]) return n3 @@ -157,9 +157,9 @@ export function render(_ctx, $props, $emit, $attrs, $slots) { const _component_Comp = _resolveComponent("Comp") const n0 = t0() const n3 = t1() - const n2 = _child(n3) _setInsertionState(n3, 0) const n1 = _createComponentWithFallback(_component_Comp) + const n2 = _child(n3) _renderEffect(() => { _setProp(n3, "id", _ctx.foo) _setText(n2, _toDisplayString(_ctx.bar)) @@ -220,9 +220,9 @@ export function render(_ctx) { const _component_Comp = _resolveComponent("Comp") const n3 = t0() const n1 = _child(n3) - _setInsertionState(n1) + _setInsertionState(n1, null) const n0 = _createSlot("default", null) - _setInsertionState(n3, null) + _setInsertionState(n3, 1) const n2 = _createComponentWithFallback(_component_Comp) return n3 }" @@ -280,30 +280,6 @@ export function render(_ctx) { }" `; -exports[`compile > setInsertionState > next, child and nthChild should be above the setInsertionState 1`] = ` -"import { resolveComponent as _resolveComponent, child as _child, next as _next, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, nthChild as _nthChild, createIf as _createIf, setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue'; -const t0 = _template("
") -const t1 = _template("
", true) - -export function render(_ctx) { - const _component_Comp = _resolveComponent("Comp") - const n6 = t1() - const n5 = _next(_child(n6)) - const n7 = _nthChild(n6, 3) - const p0 = _next(n7) - const n4 = _child(p0) - _setInsertionState(n6, n5) - const n0 = _createComponentWithFallback(_component_Comp) - _setInsertionState(n6, n7) - const n1 = _createIf(() => (true), () => { - const n3 = t0() - return n3 - }) - _renderEffect(() => _setProp(n4, "disabled", _ctx.foo)) - return n6 -}" -`; - exports[`compile > static + dynamic root 1`] = ` "import { toDisplayString as _toDisplayString, setText as _setText, template as _template } from 'vue'; const t0 = _template(" ") diff --git a/packages/compiler-vapor/__tests__/compile.spec.ts b/packages/compiler-vapor/__tests__/compile.spec.ts index 7963a9e98c..ae59cc78e0 100644 --- a/packages/compiler-vapor/__tests__/compile.spec.ts +++ b/packages/compiler-vapor/__tests__/compile.spec.ts @@ -221,23 +221,6 @@ describe('compile', () => { }) }) - describe('setInsertionState', () => { - test('next, child and nthChild should be above the setInsertionState', () => { - const code = compile(` -
-
- -
-
-
-
-
- `) - expect(code).toMatchSnapshot() - }) - }) - describe('execution order', () => { test('basic', () => { const code = compile(`
{{ bar }}
`) 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 f73bb18b9c..2d6def4e12 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) + _setInsertionState(n5, null) const n2 = _createFor(() => (_for_item0.value), (_for_item1) => { const n4 = t0() const x4 = _txt(n4) 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 b6107d5a1a..4ca745ef0f 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) + _setInsertionState(n1, null) const n0 = _createComponentWithFallback(_component_Comp, { id: () => (_ctx.foo) }, null, null, true) return n1 }" diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts index 8250966617..ff3806611a 100644 --- a/packages/compiler-vapor/src/generate.ts +++ b/packages/compiler-vapor/src/generate.ts @@ -86,7 +86,6 @@ export class CodegenContext { isTS: false, inSSR: false, inline: false, - vapor: false, bindingMetadata: {}, expressionPlugins: [], } diff --git a/packages/compiler-vapor/src/generators/block.ts b/packages/compiler-vapor/src/generators/block.ts index 40fa8da632..9ad5da1213 100644 --- a/packages/compiler-vapor/src/generators/block.ts +++ b/packages/compiler-vapor/src/generators/block.ts @@ -71,7 +71,7 @@ export function genBlockContent( } for (const child of dynamic.children) { if (!child.hasDynamicChild) { - push(...genChildren(child, context, push, `n${child.id!}`)) + push(...genChildren(child, context, `n${child.id!}`)) } } diff --git a/packages/compiler-vapor/src/generators/operation.ts b/packages/compiler-vapor/src/generators/operation.ts index bc78032380..beefab6cf5 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 } = operation + const { parent, anchor, append } = operation return [ NEWLINE, ...genCall( @@ -178,8 +178,12 @@ function genInsertionState( ? undefined : anchor === -1 // -1 indicates prepend ? `0` // runtime anchor value for prepend - : anchor === -2 // -2 indicates append - ? `null` // runtime anchor value for append + : append // -2 indicates append + ? // null or number > 0 for append + // number > 0 is used for locate the previous static node during hydration + anchor === 0 + ? 'null' + : `${anchor}` : `n${anchor}`, ), ] diff --git a/packages/compiler-vapor/src/generators/template.ts b/packages/compiler-vapor/src/generators/template.ts index f710c8869f..c22d0bf987 100644 --- a/packages/compiler-vapor/src/generators/template.ts +++ b/packages/compiler-vapor/src/generators/template.ts @@ -36,7 +36,7 @@ export function genSelf( } if (hasDynamicChild) { - push(...genChildren(dynamic, context, push, `n${id}`)) + push(...genChildren(dynamic, context, `n${id}`)) } return frag @@ -45,7 +45,6 @@ export function genSelf( export function genChildren( dynamic: IRDynamicInfo, context: CodegenContext, - pushBlock: (...items: CodeFragment[]) => number, from: string = `n${dynamic.id}`, ): CodeFragment[] { const { helper } = context @@ -76,17 +75,17 @@ export function genChildren( // p for "placeholder" variables that are meant for possible reuse by // other access paths const variable = id === undefined ? `p${context.block.tempId++}` : `n${id}` - pushBlock(NEWLINE, `const ${variable} = `) + push(NEWLINE, `const ${variable} = `) if (prev) { if (elementIndex - prev[1] === 1) { - pushBlock(...genCall(helper('next'), prev[0])) + push(...genCall(helper('next'), prev[0])) } else { - pushBlock(...genCall(helper('nthChild'), from, String(elementIndex))) + push(...genCall(helper('nthChild'), from, String(elementIndex))) } } else { if (elementIndex === 0) { - pushBlock(...genCall(helper('child'), from)) + push(...genCall(helper('child'), from)) } else { // check if there's a node that we can reuse from let init = genCall(helper('child'), from) @@ -95,7 +94,7 @@ export function genChildren( } else if (elementIndex > 1) { init = genCall(helper('nthChild'), from, String(elementIndex)) } - pushBlock(...init) + push(...init) } } @@ -108,7 +107,7 @@ export function genChildren( } prev = [variable, elementIndex] - push(...genChildren(child, context, pushBlock, variable)) + push(...genChildren(child, context, variable)) } return frag diff --git a/packages/compiler-vapor/src/ir/index.ts b/packages/compiler-vapor/src/ir/index.ts index 96110207d5..e5f4a1126e 100644 --- a/packages/compiler-vapor/src/ir/index.ts +++ b/packages/compiler-vapor/src/ir/index.ts @@ -80,6 +80,7 @@ export interface IfIRNode extends BaseIRNode { once?: boolean parent?: number anchor?: number + append?: boolean childIndex?: number } @@ -100,6 +101,7 @@ export interface ForIRNode extends BaseIRNode, IRFor { onlyChild: boolean parent?: number anchor?: number + append?: boolean childIndex?: number } @@ -202,6 +204,7 @@ export interface CreateComponentIRNode extends BaseIRNode { dynamic?: SimpleExpressionNode parent?: number anchor?: number + append?: boolean childIndex?: number scopeId?: string | null } @@ -220,6 +223,7 @@ export interface SlotOutletIRNode extends BaseIRNode { forwarded?: boolean parent?: number anchor?: number + append?: boolean childIndex?: number } diff --git a/packages/compiler-vapor/src/transforms/transformChildren.ts b/packages/compiler-vapor/src/transforms/transformChildren.ts index 1ad8dae5ed..69250ab5c3 100644 --- a/packages/compiler-vapor/src/transforms/transformChildren.ts +++ b/packages/compiler-vapor/src/transforms/transformChildren.ts @@ -59,19 +59,17 @@ export const transformChildren: NodeTransform = (node, context) => { function processDynamicChildren(context: TransformContext) { let prevDynamics: IRDynamicInfo[] = [] - let hasStaticTemplate = false - let dynamicCount = 0 + let staticCount = 0 const children = context.dynamic.children for (const [index, child] of children.entries()) { if (child.flags & DynamicFlag.INSERT) { prevDynamics.push(child) - dynamicCount++ } if (!(child.flags & DynamicFlag.NON_TEMPLATE)) { if (prevDynamics.length) { - if (hasStaticTemplate) { + if (staticCount) { context.childrenTemplate[index - prevDynamics.length] = `` prevDynamics[0].flags -= DynamicFlag.NON_TEMPLATE const anchor = (prevDynamics[0].anchor = context.increaseId()) @@ -81,25 +79,20 @@ function processDynamicChildren(context: TransformContext) { } prevDynamics = [] } - hasStaticTemplate = true + staticCount++ } } if (prevDynamics.length) { - registerInsertion( - prevDynamics, - context, - // When there is only one dynamic node, no anchor is needed, - // firstChild is used as the hydration node - dynamicCount === 1 && !hasStaticTemplate ? undefined : -2 /* append */, - ) + registerInsertion(prevDynamics, context, staticCount, true) } } function registerInsertion( dynamics: IRDynamicInfo[], context: TransformContext, - anchor?: number, + anchor: number, + append?: boolean, ) { for (const child of dynamics) { if (child.template != null) { @@ -108,12 +101,13 @@ function registerInsertion( type: IRNodeTypes.INSERT_NODE, elements: dynamics.map(child => child.id!), parent: context.reference(), - anchor: anchor === -2 ? undefined : anchor, + anchor: append ? undefined : anchor, }) } else if (child.operation && isBlockOperation(child.operation)) { // block types child.operation.parent = context.reference() child.operation.anchor = anchor + child.operation.append = append } } } diff --git a/packages/runtime-vapor/__tests__/hydration.spec.ts b/packages/runtime-vapor/__tests__/hydration.spec.ts index d558c690e5..72b7e07873 100644 --- a/packages/runtime-vapor/__tests__/hydration.spec.ts +++ b/packages/runtime-vapor/__tests__/hydration.spec.ts @@ -169,13 +169,21 @@ describe('Vapor Mode hydration', () => { `) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - `"foofoo"`, + ` + " + foofoo + " + `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - `"barbar"`, + ` + " + barbar + " + `, ) }) @@ -199,13 +207,21 @@ describe('Vapor Mode hydration', () => { `) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - `"fooAfooBfoo"`, + ` + " + fooAfooBfoo + " + `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - `"barAbarBbar"`, + ` + " + barAbarBbar + " + `, ) }) @@ -240,7 +256,7 @@ describe('Vapor Mode hydration', () => { ` " - " + " `, ) @@ -250,7 +266,7 @@ describe('Vapor Mode hydration', () => { ` " foo - " + " `, ) }) @@ -270,13 +286,21 @@ describe('Vapor Mode hydration', () => { `) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - `" Afoofoo"`, + ` + " + Afoofoo + " + `, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - `" Abarbar"`, + ` + " + Abarbar + " + `, ) }) @@ -350,21 +374,13 @@ describe('Vapor Mode hydration', () => { { Child: `` }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
- foo -
" - `, + `"
foo
"`, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
- bar -
" - `, + `"
bar
"`, ) }) @@ -378,7 +394,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
foo
-foo- +
foo
-foo-
" `, ) @@ -388,7 +404,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
bar
-bar- +
bar
-bar-
" `, ) @@ -404,7 +420,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
foo
-foo- +
foo
-foo-
" `, ) @@ -414,7 +430,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
bar
-bar- +
bar
-bar-
" `, ) @@ -433,7 +449,9 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
foo
-foo-
+
+
foo
-foo- +
" `, ) @@ -443,7 +461,9 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
bar
-bar-
+
+
bar
-bar- +
" `, ) @@ -464,21 +484,13 @@ describe('Vapor Mode hydration', () => { }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
- foo -
" - `, + `"
foo
"`, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
- bar -
" - `, + `"
bar
"`, ) }) @@ -493,21 +505,13 @@ describe('Vapor Mode hydration', () => { }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
-
foo
-
" - `, + `"
foo
"`, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
-
bar
-
" - `, + `"
bar
"`, ) }) @@ -522,61 +526,13 @@ describe('Vapor Mode hydration', () => { }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
-
-
foo
-
-
" - `, - ) - - data.value = 'bar' - await nextTick() - expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
-
-
bar
-
-
" - `, - ) - }) - - test('consecutive components with insertion anchor', async () => { - const { container, data } = await testHydration( - ` - `, - { - Child: ``, - }, - ) - expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
- foo - foo -
" - `, + `"
foo
"`, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
- bar - bar -
" - `, + `"
bar
"`, ) }) @@ -597,24 +553,14 @@ describe('Vapor Mode hydration', () => { data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
- foo - bar -
" - `, + `"
foobar
"`, ) data.foo = 'foo1' data.bar = 'bar1' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
- foo1 - bar1 -
" - `, + `"
foo1bar1
"`, ) }) @@ -629,23 +575,13 @@ describe('Vapor Mode hydration', () => { }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
-
foo
-
foo
-
" - `, + `"
foo
foo
"`, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
-
bar
-
bar
-
" - `, + `"
bar
bar
"`, ) }) @@ -660,27 +596,13 @@ describe('Vapor Mode hydration', () => { }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
-
-
foo
-
foo
-
-
" - `, + `"
foo
foo
"`, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
-
-
bar
-
bar
-
-
" - `, + `"
bar
bar
"`, ) }) @@ -701,64 +623,13 @@ describe('Vapor Mode hydration', () => { }, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
- foo - - foo -
" - `, - ) - - data.value = 'bar' - await nextTick() - expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
- bar - - bar -
" - `, - ) - }) - - test('mixed component and text with insertion anchor', async () => { - const { container, data } = await testHydration( - ` - `, - { - Child: ``, - }, - ) - expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
- foo - foo - foo -
" - `, + `"
foofoo
"`, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
- bar - bar - bar -
" - `, + `"
barbar
"`, ) }) @@ -779,7 +650,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
foo
-foo +
foo
-foo
" `, ) @@ -789,7 +660,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
bar
-bar +
bar
-bar
" `, ) @@ -808,7 +679,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
foo
-foo- +
foo
-foo-
" `, ) @@ -818,7 +689,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
bar
-bar- +
bar
-bar-
" `, ) @@ -836,11 +707,9 @@ describe('Vapor Mode hydration', () => { ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` - "
-
-
foo
-foo- -
-
" + "
+
foo
-foo- +
" `, ) @@ -848,11 +717,9 @@ describe('Vapor Mode hydration', () => { await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` - "
-
-
bar
-bar- -
-
" + "
+
bar
-bar- +
" `, ) }) @@ -875,8 +742,8 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
foo
-foo -
foo
-foo +
foo
-foo +
foo
-foo
" `, ) @@ -886,8 +753,8 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
bar
-bar -
bar
-bar +
bar
-bar +
bar
-bar
" `, ) @@ -906,8 +773,8 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
foo
-foo- -
foo
-foo- +
foo
-foo- +
foo
-foo-
" `, ) @@ -917,8 +784,8 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
bar
-bar- -
bar
-bar- +
bar
-bar- +
bar
-bar-
" `, ) @@ -936,12 +803,10 @@ describe('Vapor Mode hydration', () => { ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` - "
-
-
foo
-foo- -
foo
-foo- -
-
" + "
+
foo
-foo- +
foo
-foo- +
" `, ) @@ -949,12 +814,10 @@ describe('Vapor Mode hydration', () => { await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` - "
-
-
bar
-bar- -
bar
-bar- -
-
" + "
+
bar
-bar- +
bar
-bar- +
" `, ) }) @@ -972,7 +835,10 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
foo
-foo-
foo
-foo- + +
foo
-foo- +
foo
-foo- +
" `, ) @@ -982,7 +848,10 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
bar
-bar-
bar
-bar- + +
bar
-bar- +
bar
-bar- +
" `, ) @@ -1007,9 +876,9 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
foo
-foo +
foo
-foo -
foo
-foo +
foo
-foo
" `, ) @@ -1019,9 +888,9 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
bar
-bar +
bar
-bar -
bar
-bar +
bar
-bar
" `, ) @@ -1046,9 +915,9 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
foo
-foo +
foo
-foo foo -
foo
-foo +
foo
-foo
" `, ) @@ -1058,9 +927,9 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
bar
-bar +
bar
-bar bar -
bar
-bar +
bar
-bar
" `, ) @@ -1106,21 +975,13 @@ describe('Vapor Mode hydration', () => { ref('foo'), ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
-
foo
-
" - `, + `"
foo
"`, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
-
bar
-
" - `, + `"
bar
"`, ) }) @@ -1141,23 +1002,13 @@ describe('Vapor Mode hydration', () => { ref('foo'), ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
-
foo
-
foo
-
" - `, + `"
foo
foo
"`, ) data.value = 'bar' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
-
bar
-
bar
-
" - `, + `"
bar
bar
"`, ) }) @@ -1204,7 +1055,7 @@ describe('Vapor Mode hydration', () => { ` "
foo -
" +
" `, ) @@ -1214,7 +1065,7 @@ describe('Vapor Mode hydration', () => { ` "
bar -
" +
" `, ) }) @@ -1336,21 +1187,13 @@ describe('Vapor Mode hydration', () => { data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
outer -
inner
-
" - `, + `"
outer
inner
"`, ) data.inner = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
outer - -
" - `, + `"
outer
"`, ) data.outer = false @@ -1431,40 +1274,19 @@ describe('Vapor Mode hydration', () => { data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - " - foo - bar - baz - qux - quux" - `, + `"foobarbazquxquux"`, ) data.qux = 'qux1' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - " - foo - bar - baz - qux1 - quux" - `, + `"foobarbazqux1quux"`, ) data.foo = 'foo1' await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - " - foo1 - bar - baz - qux1 - quux" - `, + `"foo1barbazqux1quux"`, ) }) @@ -1520,21 +1342,13 @@ describe('Vapor Mode hydration', () => { data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
- foo -
" - `, + `"
foo
"`, ) data.value = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
- -
" - `, + `"
"`, ) }) @@ -1558,12 +1372,7 @@ describe('Vapor Mode hydration', () => { data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
- foo - bar -
" - `, + `"
foobar
"`, ) data.show = false @@ -1586,50 +1395,6 @@ describe('Vapor Mode hydration', () => { ) }) - test('consecutive v-if on component with insertion anchor', async () => { - const data = ref(true) - const { container } = await testHydration( - ``, - { Child: `` }, - data, - ) - expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
- foo - foo -
" - `, - ) - - data.value = false - await nextTick() - expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
- - -
" - `, - ) - - data.value = true - await nextTick() - expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(` - "
- foo - foo -
" - `) - }) - test('on fragment component', async () => { const data = ref(true) const { container } = await testHydration( @@ -1644,19 +1409,31 @@ describe('Vapor Mode hydration', () => { data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - `"
true
-true-
"`, + ` + "
+
true
-true- +
" + `, ) data.value = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - `"
"`, + ` + "
+ +
" + `, ) data.value = true await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - `"
true
-true-
"`, + ` + "
+
true
-true- +
" + `, ) }) @@ -1678,7 +1455,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
true
-true- +
true
-true-
" `, ) @@ -1688,7 +1465,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- +
" `, ) @@ -1697,7 +1474,7 @@ describe('Vapor Mode hydration', () => { await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(` "
-
true
-true- +
true
-true-
" `) }) @@ -1721,8 +1498,8 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
true
-true- -
true
-true- +
true
-true- +
true
-true-
" `, ) @@ -1732,8 +1509,8 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- - + +
" `, ) @@ -1742,8 +1519,8 @@ describe('Vapor Mode hydration', () => { await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(` "
-
true
-true- -
true
-true- +
true
-true- +
true
-true-
" `) }) @@ -1762,30 +1539,20 @@ describe('Vapor Mode hydration', () => { data, ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
- foo -
" - `, + `"
foo
"`, ) data.value = false await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - ` - "
- -
" - `, + `"
"`, ) data.value = true await nextTick() - expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(` - "
- foo -
" - `) + expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( + `"
foo
"`, + ) }) }) @@ -1799,13 +1566,21 @@ describe('Vapor Mode hydration', () => { ref(['a', 'b', 'c']), ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - `"abc"`, + ` + " + abc + " + `, ) data.value.push('d') await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - `"abcd"`, + ` + " + abcd + " + `, ) }) @@ -1823,13 +1598,25 @@ describe('Vapor Mode hydration', () => { ref(['a', 'b', 'c']), ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - `"
abc
3
"`, + ` + " +
+ abc +
3
+ " + `, ) data.value.push('d') await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - `"
abcd
4
"`, + ` + " +
+ abcd +
4
+ " + `, ) }) @@ -1848,7 +1635,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- abc + abc
" `, ) @@ -1858,7 +1645,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- abcd + abcd
" `, ) @@ -1868,7 +1655,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- bcd + bcd
" `, ) @@ -1890,8 +1677,8 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- abc - abc + abc + abc
" `, ) @@ -1901,8 +1688,8 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- abcd - abcd + abcd + abcd
" `, ) @@ -1912,8 +1699,8 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- cd - cd + cd + cd
" `, ) @@ -1933,13 +1720,21 @@ describe('Vapor Mode hydration', () => { ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - `"
comp
comp
comp
"`, + ` + "
+
comp
comp
comp
+
" + `, ) data.value.push('d') await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - `"
comp
comp
comp
comp
"`, + ` + "
+
comp
comp
comp
comp
+
" + `, ) }) @@ -1960,12 +1755,12 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
+ a - b - c -
" + +
" `, ) @@ -1974,12 +1769,12 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
+ a - b - - c - d
" + cd + + " `, ) }) @@ -1997,13 +1792,29 @@ describe('Vapor Mode hydration', () => { ref(['a', 'b', 'c']), ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - `"
foo
-bar-
foo
-bar-
foo
-bar-
"`, + ` + "
+ +
foo
-bar- +
foo
-bar- +
foo
-bar- + +
" + `, ) data.value.push('d') await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( - `"
foo
-bar-
foo
-bar-
foo
-bar-
foo
-bar-
"`, + ` + "
+ +
foo
-bar- +
foo
-bar- +
foo
-bar-
foo
-bar- + +
" + `, ) }) }) @@ -2024,7 +1835,7 @@ describe('Vapor Mode hydration', () => { ` " foo - " + " `, ) @@ -2034,7 +1845,7 @@ describe('Vapor Mode hydration', () => { ` " bar - " + " `, ) }) @@ -2055,10 +1866,11 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " + - foo - " + + " `, ) @@ -2067,10 +1879,11 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " + - bar - " + + " `, ) }) @@ -2092,7 +1905,7 @@ describe('Vapor Mode hydration', () => { ` " foo - " + " `, ) @@ -2102,7 +1915,7 @@ describe('Vapor Mode hydration', () => { ` " - " + " `, ) @@ -2110,8 +1923,8 @@ describe('Vapor Mode hydration', () => { await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(` " - - true" + true + " `) }) @@ -2136,8 +1949,10 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " - abc - " + + abc + + " `, ) @@ -2146,8 +1961,9 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " + - " + " `, ) @@ -2156,8 +1972,9 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " - - abc" + + abc + " `, ) }) @@ -2179,7 +1996,7 @@ describe('Vapor Mode hydration', () => { ` " foo - " + " `, ) @@ -2189,7 +2006,7 @@ describe('Vapor Mode hydration', () => { ` " bar - " + " `, ) }) @@ -2216,9 +2033,11 @@ describe('Vapor Mode hydration', () => { ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` - "
+ " +
foo -
" +
+ " `, ) @@ -2226,9 +2045,11 @@ describe('Vapor Mode hydration', () => { await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` - "
+ " +
bar -
" +
+ " `, ) }) @@ -2253,9 +2074,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- foo - hi
" `, ) @@ -2265,9 +2084,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- foo - bar
" `, ) @@ -2292,9 +2109,11 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` - "foo + " + foo foo - hi" + hi + " `, ) @@ -2302,9 +2121,11 @@ describe('Vapor Mode hydration', () => { await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` - "foo + " + foo foo - bar" + bar + " `, ) }) @@ -2330,12 +2151,8 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- foo - - bar -
hi
" `, ) @@ -2345,12 +2162,8 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- foo - - bar -
bar
" `, ) @@ -2376,9 +2189,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- foo -
hi
" `, ) @@ -2388,9 +2199,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- foo -
bar
" `, ) @@ -2425,13 +2234,9 @@ describe('Vapor Mode hydration', () => { ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` - "
-
bar
- + "
bar
foo - -
bar
-
" +
bar
" `, ) @@ -2439,13 +2244,9 @@ describe('Vapor Mode hydration', () => { await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` - "
-
hello
- + "
hello
foo - -
hello
-
" +
hello
" `, ) }) @@ -2480,11 +2281,9 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
foo
bar - +
foo
bar foo - -
foo
bar +
foo
bar
" `, ) @@ -2495,11 +2294,9 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
-
hello
vapor - +
hello
vapor hello - -
hello
vapor +
hello
vapor
" `, ) @@ -2529,9 +2326,11 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` - "
foo
+ " +
foo
foo -
foo
" +
foo
+ " `, ) @@ -2539,18 +2338,22 @@ describe('Vapor Mode hydration', () => { await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` - " + " + foo - " + + " `, ) data.show = true await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(` - "
foo
+ " +
foo
foo -
foo
" +
foo
+ " `) }) @@ -2578,9 +2381,13 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` - "
a
b
c
+ " + +
a
b
c
foo -
a
b
c
" +
a
b
c
+ + " `, ) @@ -2588,9 +2395,13 @@ describe('Vapor Mode hydration', () => { await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` - "
a
b
c
d
+ " + +
a
b
c
d
foo -
a
b
c
d
" +
a
b
c
d
+ + " `, ) }) @@ -2619,10 +2430,11 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " + foo - bar - " + + " `, ) @@ -2632,10 +2444,11 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` " + hello - vapor - " + + " `, ) }) @@ -2671,12 +2484,8 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- foo - - bar -
" `, ) @@ -2687,12 +2496,8 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- hello - - vapor -
" `, ) @@ -2731,12 +2536,8 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- foo - - bar -
baz
" `, ) @@ -2747,12 +2548,8 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- hello - - vapor -
baz
" `, ) @@ -2776,7 +2573,7 @@ describe('Vapor Mode hydration', () => { ` " foo - " + " `, ) @@ -2786,7 +2583,7 @@ describe('Vapor Mode hydration', () => { ` " bar - " + " `, ) }) @@ -2813,13 +2610,11 @@ describe('Vapor Mode hydration', () => { ) expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` - "
-
+ "
foo - -
-
bar
" + +
bar
" `, ) @@ -2828,13 +2623,11 @@ describe('Vapor Mode hydration', () => { await nextTick() expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` - "
-
+ "
foo1 - -
-
bar1
" + +
bar1
" `, ) }) @@ -2856,8 +2649,8 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- foo -
" + foo + " `, ) @@ -2866,8 +2659,8 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- foo1 -
" + foo1 + " `, ) }) @@ -2915,9 +2708,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- - - +
foo
" `, ) @@ -2927,9 +2718,7 @@ describe('Vapor Mode hydration', () => { expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot( ` "
- - - +
bar
" `, ) @@ -3155,7 +2944,7 @@ describe('VDOM interop', () => { ` " true vapor fallback - " + " `, ) @@ -3165,7 +2954,7 @@ describe('VDOM interop', () => { ` " false vapor fallback - " + " `, ) }) @@ -3197,7 +2986,7 @@ describe('VDOM interop', () => { ` "
true -
" + " `, ) @@ -3207,7 +2996,7 @@ describe('VDOM interop', () => { ` "
false -
" + " `, ) }) @@ -3242,7 +3031,7 @@ describe('VDOM interop', () => { ` "
true
-
" + " `, ) @@ -3254,7 +3043,7 @@ describe('VDOM interop', () => { ` "
false
-
" + " `, ) }) diff --git a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts index 659f8487a0..cc7bd6aaf2 100644 --- a/packages/runtime-vapor/src/apiCreateDynamicComponent.ts +++ b/packages/runtime-vapor/src/apiCreateDynamicComponent.ts @@ -9,7 +9,6 @@ import { insertionParent, resetInsertionState, } from './insertionState' -import { DYNAMIC_COMPONENT_ANCHOR_LABEL } from '@vue/shared' import { advanceHydrationNode, isHydrating } from './dom/hydration' import { DynamicFragment, type VaporFragment } from './fragment' @@ -27,7 +26,7 @@ export function createDynamicComponent( const frag = isHydrating || __DEV__ - ? new DynamicFragment(DYNAMIC_COMPONENT_ANCHOR_LABEL) + ? new DynamicFragment('dynamic-component') : new DynamicFragment() renderEffect(() => { diff --git a/packages/runtime-vapor/src/apiCreateFor.ts b/packages/runtime-vapor/src/apiCreateFor.ts index 7a9342e909..7954dcb056 100644 --- a/packages/runtime-vapor/src/apiCreateFor.ts +++ b/packages/runtime-vapor/src/apiCreateFor.ts @@ -11,7 +11,7 @@ import { toReadonly, watch, } from '@vue/reactivity' -import { FOR_ANCHOR_LABEL, isArray, isObject, isString } from '@vue/shared' +import { isArray, isObject, isString } from '@vue/shared' import { createComment, createTextNode } from './dom/node' import { type Block, @@ -27,9 +27,8 @@ import { renderEffect } from './renderEffect' import { VaporVForFlags } from '../../shared/src/vaporFlags' import { advanceHydrationNode, - currentHydrationNode, isHydrating, - locateFragmentAnchor, + locateFragmentEndAnchor, locateHydrationNode, } from './dom/hydration' import { ForFragment, VaporFragment } from './fragment' @@ -135,10 +134,8 @@ export const createFor = ( } if (isHydrating) { - parentAnchor = locateFragmentAnchor( - currentHydrationNode!, - FOR_ANCHOR_LABEL, - )! + parentAnchor = locateFragmentEndAnchor()! + // TODO: special handling vFor not render as a fragment. (inside Transition/TransitionGroup) if (__DEV__ && !parentAnchor) { throw new Error(`v-for fragment anchor node was not found.`) } diff --git a/packages/runtime-vapor/src/apiCreateIf.ts b/packages/runtime-vapor/src/apiCreateIf.ts index d9432b497e..2183cb9677 100644 --- a/packages/runtime-vapor/src/apiCreateIf.ts +++ b/packages/runtime-vapor/src/apiCreateIf.ts @@ -1,4 +1,3 @@ -import { IF_ANCHOR_LABEL } from '@vue/shared' import { type Block, type BlockFn, insert } from './block' import { advanceHydrationNode, isHydrating } from './dom/hydration' import { @@ -24,9 +23,7 @@ export function createIf( frag = condition() ? b1() : b2 ? b2() : [] } else { frag = - isHydrating || __DEV__ - ? new DynamicFragment(IF_ANCHOR_LABEL) - : new DynamicFragment() + isHydrating || __DEV__ ? new DynamicFragment('if') : new DynamicFragment() renderEffect(() => (frag as DynamicFragment).update(condition() ? b1 : b2)) } diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts index 04b0a4881c..75679004c3 100644 --- a/packages/runtime-vapor/src/block.ts +++ b/packages/runtime-vapor/src/block.ts @@ -19,6 +19,7 @@ import { type VaporFragment, isFragment, } from './fragment' +import { child } from './dom/node' export interface TransitionOptions { $key?: any @@ -71,7 +72,7 @@ export function insert( anchor: Node | null | 0 = null, // 0 means prepend parentSuspense?: any, // TODO Suspense ): void { - anchor = anchor === 0 ? parent.firstChild : anchor + anchor = anchor === 0 ? child(parent) : anchor if (block instanceof Node) { if (!isHydrating) { // only apply transition on Element nodes diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts index 1e8b30128e..52ccf18cae 100644 --- a/packages/runtime-vapor/src/componentSlots.ts +++ b/packages/runtime-vapor/src/componentSlots.ts @@ -1,11 +1,4 @@ -import { - EMPTY_OBJ, - NO, - SLOT_ANCHOR_LABEL, - hasOwn, - isArray, - isFunction, -} from '@vue/shared' +import { EMPTY_OBJ, NO, hasOwn, isArray, isFunction } from '@vue/shared' import { type Block, type BlockFn, insert, setScopeId } from './block' import { rawPropsProxyHandlers } from './componentProps' import { currentInstance, isRef } from '@vue/runtime-dom' @@ -142,7 +135,7 @@ export function createSlot( } else { fragment = isHydrating || __DEV__ - ? new DynamicFragment(SLOT_ANCHOR_LABEL) + ? new DynamicFragment('slot') : new DynamicFragment() const isDynamicName = isFunction(name) const renderSlot = () => { diff --git a/packages/runtime-vapor/src/dom/hydration.ts b/packages/runtime-vapor/src/dom/hydration.ts index eeb5c8590f..aab0e76e6c 100644 --- a/packages/runtime-vapor/src/dom/hydration.ts +++ b/packages/runtime-vapor/src/dom/hydration.ts @@ -1,18 +1,17 @@ import { warn } from '@vue/runtime-dom' import { + type ChildItem, + getHydrationState, insertionAnchor, insertionParent, resetInsertionState, setInsertionState, } from '../insertionState' import { - _child, - _next, createTextNode, disableHydrationNodeLookup, enableHydrationNodeLookup, } from './node' -import { BLOCK_ANCHOR_END_LABEL, BLOCK_ANCHOR_START_LABEL } from '@vue/shared' const isHydratingStack = [] as boolean[] export let isHydrating = false @@ -30,7 +29,7 @@ function performHydration( locateHydrationNode = locateHydrationNodeImpl // optimize anchor cache lookup ;(Comment.prototype as any).$fe = undefined - ;(Node.prototype as any).$lbn = undefined + ;(Node.prototype as any).$idx = undefined isOptimized = true } enableHydrationNodeLookup() @@ -135,14 +134,48 @@ function adoptTemplateImpl(node: Node, template: string): Node | null { function locateHydrationNodeImpl(): void { let node: Node | null if (insertionAnchor !== undefined) { - // prepend / insert / append - node = insertionParent!.$lbn = locateNextBlockNode( - insertionParent!.$lbn || _child(insertionParent!), - )! + const hydrationState = getHydrationState(insertionParent!)! + const { + prevDynamicCount, + logicalChildren, + appendAnchor, + insertionAnchors, + } = hydrationState + // prepend + if (insertionAnchor === 0) { + node = logicalChildren[prevDynamicCount] + } + // insert + else if (insertionAnchor instanceof Node) { + const seen = + (insertionAnchors && insertionAnchors.get(insertionAnchor)) || 0 + node = seen + ? logicalChildren[(insertionAnchor as ChildItem).$idx + seen] + : insertionAnchor + + hydrationState.insertionAnchors = ( + hydrationState.insertionAnchors || new Map() + ).set(insertionAnchor, seen + 1) + } + // append + else { + if (appendAnchor) { + node = logicalChildren[(appendAnchor as ChildItem).$idx + 1] + } else { + node = + insertionAnchor === null + ? logicalChildren[0] + : // insertionAnchor is a number > 0 + // indicates how many static nodes precede the node to append + logicalChildren[prevDynamicCount + insertionAnchor] + } + hydrationState.appendAnchor = node + } + hydrationState.prevDynamicCount++ } else { node = currentHydrationNode if (insertionParent && (!node || node.parentNode !== insertionParent)) { - node = _child(insertionParent) + node = insertionParent.firstChild } } @@ -181,43 +214,11 @@ export function locateEndAnchor( return null } -export function locateFragmentAnchor( - node: Node, - label: string, -): Comment | null { - while (node && node.nodeType === 8) { +export function locateFragmentEndAnchor(label: string = ']'): Comment | null { + let node = currentHydrationNode! + while (node) { if (isComment(node, label)) return node node = node.nextSibling! } return null } - -function locateNextBlockNode(node: Node): Node | null { - while (node) { - if (isComment(node, BLOCK_ANCHOR_START_LABEL)) return node.nextSibling - node = node.nextSibling! - } - - if (__DEV__) { - throw new Error( - `Could not locate hydration node with anchor label: ${BLOCK_ANCHOR_START_LABEL}`, - ) - } - return null -} - -export function advanceToNonBlockNode(node: Node): Node { - while (node) { - if (isComment(node, BLOCK_ANCHOR_START_LABEL)) { - node = locateEndAnchor( - node, - BLOCK_ANCHOR_START_LABEL, - BLOCK_ANCHOR_END_LABEL, - )! - continue - } - - break - } - return node -} diff --git a/packages/runtime-vapor/src/dom/node.ts b/packages/runtime-vapor/src/dom/node.ts index fe267f27b9..6a99e95bac 100644 --- a/packages/runtime-vapor/src/dom/node.ts +++ b/packages/runtime-vapor/src/dom/node.ts @@ -1,33 +1,37 @@ -import { advanceToNonBlockNode } from './hydration' -import { isBlockStartAnchor } from '@vue/shared' +/* @__NO_SIDE_EFFECTS__ */ + +import { + type ChildItem, + getHydrationState, + getTemplateChildren, +} from '../insertionState' -/*! #__NO_SIDE_EFFECTS__ */ export function createElement(tagName: string): HTMLElement { return document.createElement(tagName) } -/*! #__NO_SIDE_EFFECTS__ */ +/* @__NO_SIDE_EFFECTS__ */ export function createTextNode(value = ''): Text { return document.createTextNode(value) } -/*! #__NO_SIDE_EFFECTS__ */ +/* @__NO_SIDE_EFFECTS__ */ export function createComment(data: string): Comment { return document.createComment(data) } -/*! #__NO_SIDE_EFFECTS__ */ +/* @__NO_SIDE_EFFECTS__ */ export function querySelector(selectors: string): Element | null { return document.querySelector(selectors) } -/*! #__NO_SIDE_EFFECTS__ */ +/* @__NO_SIDE_EFFECTS__ */ const _txt: typeof _child = _child /** * Hydration-specific version of `child`. */ -/*! #__NO_SIDE_EFFECTS__ */ +/* @__NO_SIDE_EFFECTS__ */ const __txt: typeof __child = (node: ParentNode): Node => { let n = node.firstChild! @@ -41,54 +45,72 @@ const __txt: typeof __child = (node: ParentNode): Node => { return n } -/*! #__NO_SIDE_EFFECTS__ */ +/* @__NO_SIDE_EFFECTS__ */ export function _child(node: ParentNode): Node { - return node.firstChild! + const templateChildren = getTemplateChildren(node) + return templateChildren ? templateChildren[0] : node.firstChild! } /** * Hydration-specific version of `child`. */ -/*! #__NO_SIDE_EFFECTS__ */ -export function __child(node: ParentNode): Node { - let n: Node = node.firstChild! - while (n && isBlockStartAnchor(n)) { - n = advanceToNonBlockNode(n) - n = n.nextSibling! - } - - return n +/* @__NO_SIDE_EFFECTS__ */ +export function __child(node: ParentNode & { $lpn?: Node }): Node { + return __nthChild(node, 0)! } -/*! #__NO_SIDE_EFFECTS__ */ +/* @__NO_SIDE_EFFECTS__ */ export function _nthChild(node: Node, i: number): Node { - return node.childNodes[i] + const templateChildren = getTemplateChildren(node as ParentNode) + return templateChildren ? templateChildren[i] : node.childNodes[i] } /** * Hydration-specific version of `nthChild`. */ -/*! #__NO_SIDE_EFFECTS__ */ +/* @__NO_SIDE_EFFECTS__ */ export function __nthChild(node: Node, i: number): Node { - let n = __child(node as ParentNode) - for (let start = 0; start < i; start++) { - n = __next(n) as ChildNode + const hydrationState = getHydrationState(node as ParentNode) + if (hydrationState) { + const { prevDynamicCount, insertionAnchors, logicalChildren } = + hydrationState + // prevDynamicCount tracks how many dynamic nodes have been processed + // so far (prepend/insert/append). + // For anchor-based insert, the first time an anchor is used we adopt the + // anchor node itself and do NOT consume the next child in `logicalChildren`, + // yet prevDynamicCount is still incremented. This overcounts the base + // offset by 1 per unique anchor that has appeared. + // insertionAnchors.size equals the number of unique anchors seen, so we + // subtract it to neutralize those "first-use doesn't consume" cases: + // base = prevDynamicCount - insertionAnchors.size + // Then index from this base: logicalChildren[base + i]. + const size = insertionAnchors ? insertionAnchors.size : 0 + return logicalChildren[prevDynamicCount - size + i] } - return n + return node.childNodes[i] } -/*! #__NO_SIDE_EFFECTS__ */ +/* @__NO_SIDE_EFFECTS__ */ export function _next(node: Node): Node { - return node.nextSibling! + const templateChildren = getTemplateChildren(node.parentNode!) + return templateChildren + ? templateChildren[(node as ChildItem).$idx + 1] + : node.nextSibling! } /** * Hydration-specific version of `next`. */ -/*! #__NO_SIDE_EFFECTS__ */ +/* @__NO_SIDE_EFFECTS__ */ export function __next(node: Node): Node { - if (isBlockStartAnchor(node)) { - node = advanceToNonBlockNode(node) + const hydrationState = getHydrationState(node.parentNode!) + if (hydrationState) { + const { logicalChildren, insertionAnchors } = hydrationState + const seenCount = (insertionAnchors && insertionAnchors.get(node)) || 0 + // If node is used as an anchor, the first hydration uses node itself, + // but seenCount increases, so here needs -1 + const insertedNodesCount = seenCount === 0 ? 0 : seenCount - 1 + return logicalChildren[(node as ChildItem).$idx + insertedNodesCount + 1] } return node.nextSibling! } @@ -97,25 +119,25 @@ type DelegatedFunction any> = T & { impl: T } -/*! #__NO_SIDE_EFFECTS__ */ +/* @__NO_SIDE_EFFECTS__ */ export const txt: DelegatedFunction = node => { return txt.impl(node) } txt.impl = _child -/*! #__NO_SIDE_EFFECTS__ */ +/* @__NO_SIDE_EFFECTS__ */ export const child: DelegatedFunction = node => { return child.impl(node) } child.impl = _child -/*! #__NO_SIDE_EFFECTS__ */ +/* @__NO_SIDE_EFFECTS__ */ export const next: DelegatedFunction = node => { return next.impl(node) } next.impl = _next -/*! #__NO_SIDE_EFFECTS__ */ +/* @__NO_SIDE_EFFECTS__ */ export const nthChild: DelegatedFunction = (node, i) => { return nthChild.impl(node, i) } diff --git a/packages/runtime-vapor/src/dom/template.ts b/packages/runtime-vapor/src/dom/template.ts index e00e9d213f..e2acbc96e6 100644 --- a/packages/runtime-vapor/src/dom/template.ts +++ b/packages/runtime-vapor/src/dom/template.ts @@ -18,6 +18,7 @@ export function template(html: string, root?: boolean) { if (root) (adopted as any).$root = true return adopted } + // fast path for text nodes if (html[0] !== '<') { return createTextNode(html) diff --git a/packages/runtime-vapor/src/fragment.ts b/packages/runtime-vapor/src/fragment.ts index e349196e70..da92f6b4ec 100644 --- a/packages/runtime-vapor/src/fragment.ts +++ b/packages/runtime-vapor/src/fragment.ts @@ -12,16 +12,16 @@ import { import type { TransitionHooks } from '@vue/runtime-dom' import { advanceHydrationNode, - currentHydrationNode, isHydrating, - locateFragmentAnchor, + locateFragmentEndAnchor, locateHydrationNode, } from './dom/hydration' import { applyTransitionHooks, applyTransitionLeaveHooks, } from './components/Transition' -import type { VaporComponentInstance } from './component' +import { type VaporComponentInstance, isVaporComponent } from './component' +import { isArray } from '@vue/shared' export class VaporFragment implements TransitionOptions @@ -74,7 +74,7 @@ export class DynamicFragment extends VaporFragment { update(render?: BlockFn, key: any = render): void { if (key === this.current) { - if (isHydrating) this.hydrate(this.anchorLabel!) + if (isHydrating) this.hydrate(true) return } this.current = key @@ -139,19 +139,41 @@ export class DynamicFragment extends VaporFragment { setActiveSub(prevSub) - if (isHydrating) this.hydrate(this.anchorLabel!) + if (isHydrating) this.hydrate() } - hydrate = (label: string): void => { - // avoid repeated hydration during rendering fallback + hydrate = (isEmpty = false): void => { + // avoid repeated hydration during fallback rendering if (this.anchor) return - this.anchor = locateFragmentAnchor(currentHydrationNode!, label)! - if (this.anchor) { - advanceHydrationNode(this.anchor) - } else if (__DEV__) { - throw new Error(`${label} fragment anchor node was not found.`) + // reuse the empty comment node as the anchor for empty if + if (this.anchorLabel === 'if' && isEmpty) { + this.anchor = locateFragmentEndAnchor('')! + if (!this.anchor) { + throw new Error('Failed to locate if anchor') + } else { + ;(this.anchor as Comment).data = this.anchorLabel + return + } + } + + // reuse the vdom fragment end anchor for slots + if (this.anchorLabel === 'slot') { + this.anchor = locateFragmentEndAnchor()! + if (!this.anchor) { + throw new Error('Failed to locate slot anchor') + } else { + return + } } + + // create an anchor + const { parentNode, nextSibling } = findLastChild(this)! + parentNode!.insertBefore( + (this.anchor = createComment(this.anchorLabel!)), + nextSibling, + ) + advanceHydrationNode(this.anchor) } } @@ -195,3 +217,16 @@ function findInvalidFragment(fragment: VaporFragment): VaporFragment | null { ? findInvalidFragment(fragment.nodes) || fragment : fragment } + +export function findLastChild(node: Block): Node | undefined | null { + if (node && node instanceof Node) { + return node + } else if (isArray(node)) { + return findLastChild(node[node.length - 1]) + } else if (isVaporComponent(node)) { + return findLastChild(node.block!) + } else { + if (node instanceof DynamicFragment && node.anchor) return node.anchor + return findLastChild(node.nodes!) + } +} diff --git a/packages/runtime-vapor/src/insertionState.ts b/packages/runtime-vapor/src/insertionState.ts index f703efffd9..bcfd900a6c 100644 --- a/packages/runtime-vapor/src/insertionState.ts +++ b/packages/runtime-vapor/src/insertionState.ts @@ -1,11 +1,20 @@ -export let insertionParent: - | (ParentNode & { - // the last hydrated block node - $lbn?: Node - }) - | undefined +import { isHydrating } from './dom/hydration' +export interface ChildItem extends ChildNode { + $idx: number +} +type HydrationState = { + logicalChildren: ChildItem[] + prevDynamicCount: number + insertionAnchors: Map | null + appendAnchor: Node | null +} +export let insertionParent: ParentNode | undefined export let insertionAnchor: Node | 0 | undefined | null +const templateChildrenCache = new WeakMap() + +const hydrationStateCache = new WeakMap() + /** * This function is called before a block type that requires insertion * (component, slot outlet, if, for) is created. The state is used for actual @@ -13,12 +22,102 @@ export let insertionAnchor: Node | 0 | undefined | null */ export function setInsertionState( parent: ParentNode, - anchor?: Node | 0 | null, + anchor?: Node | 0 | null | number, ): void { insertionParent = parent - insertionAnchor = anchor + if (isHydrating) { + initializeHydrationState(anchor, parent) + } else { + cacheTemplateChildren(anchor, parent) + } +} + +function initializeHydrationState( + anchor: number | Node | null | undefined, + parent: ParentNode, +) { + insertionAnchor = anchor as Node + if (!hydrationStateCache.has(parent)) { + const childNodes = parent.childNodes + const len = childNodes.length + const logicalChildren = new Array(len) as ChildItem[] + // Build logical children: + // - static node: keep the node as a child + // - fragment: keep only the start anchor ('') as a child + let index = 0 + for (let i = 0; i < len; i++) { + const n = childNodes[i] as ChildItem + if (n.nodeType === 8) { + const data = (n as any as Comment).data + // vdom fragment + if (data === '[') { + n.$idx = index + logicalChildren[index++] = n + // find matching end anchor, accounting for nested fragments + let depth = 1 + let j = i + 1 + for (; j < len; j++) { + const c = childNodes[j] as Comment + if (c.nodeType === 8) { + const d = c.data + if (d === '[') depth++ + else if (d === ']') { + depth-- + if (depth === 0) break + } + } + } + // jump i to the end anchor + i = j + continue + } + } + n.$idx = index + logicalChildren[index++] = n + } + logicalChildren.length = index + hydrationStateCache.set(parent, { + logicalChildren, + prevDynamicCount: 0, + insertionAnchors: null, + appendAnchor: null, + }) + } +} + +function cacheTemplateChildren( + anchor: number | Node | null | undefined, + parent: ParentNode, +) { + // special handling append anchor value to null + insertionAnchor = + typeof anchor === 'number' && anchor > 0 ? null : (anchor as Node) + + if (!templateChildrenCache.has(parent)) { + const nodes = parent.childNodes + const len = nodes.length + const children = new Array(len) + for (let i = 0; i < len; i++) { + const node = nodes[i] as ChildItem + node.$idx = i + children[i] = node + } + templateChildrenCache.set(parent, children) + } } export function resetInsertionState(): void { insertionParent = insertionAnchor = undefined } + +export function getTemplateChildren( + parent: ParentNode, +): ChildItem[] | undefined { + return templateChildrenCache.get(parent) +} + +export function getHydrationState( + parent: ParentNode, +): HydrationState | undefined { + return hydrationStateCache.get(parent) +} diff --git a/packages/runtime-vapor/src/vdomInterop.ts b/packages/runtime-vapor/src/vdomInterop.ts index 04a2881185..9a979c4e4c 100644 --- a/packages/runtime-vapor/src/vdomInterop.ts +++ b/packages/runtime-vapor/src/vdomInterop.ts @@ -59,7 +59,7 @@ import { currentHydrationNode, isComment, isHydrating, - locateFragmentAnchor, + locateFragmentEndAnchor, locateHydrationNode, setCurrentHydrationNode, hydrateNode as vaporHydrateNode, @@ -198,18 +198,14 @@ const vaporInteropImpl: Omit< const propsRef = (vnode.vs!.ref = shallowRef(vnode.props)) vaporHydrateNode(node, () => { vnode.vb = slot(new Proxy(propsRef, vaporSlotPropsProxyHandler)) - vnode.el = vnode.anchor = locateFragmentAnchor( - currentHydrationNode!, - // locate the vdom fragment end anchor (), since no vapor slot - // anchor () is injected in vdom component - ']', - ) + vnode.el = currentHydrationNode! + vnode.anchor = locateFragmentEndAnchor() if (__DEV__ && !vnode.anchor) { - throw new Error(`vapor slot anchor node was not found.`) + throw new Error(`Failed to locate slot anchor`) } }) - return _next(node) + return _next(vnode.anchor as Node) }, } diff --git a/packages/server-renderer/src/helpers/ssrRenderSlot.ts b/packages/server-renderer/src/helpers/ssrRenderSlot.ts index 0e2ee5d6c2..19aa4ce63b 100644 --- a/packages/server-renderer/src/helpers/ssrRenderSlot.ts +++ b/packages/server-renderer/src/helpers/ssrRenderSlot.ts @@ -5,7 +5,7 @@ import { type SSRBufferItem, renderVNodeChildren, } from '../render' -import { isArray, isString } from '@vue/shared' +import { isArray } from '@vue/shared' const { ensureValidVNode } = ssrUtils @@ -83,20 +83,7 @@ export function ssrRenderSlotInner( isEmptySlot = false } else { for (let i = 0; i < slotBuffer.length; i++) { - const buffer = slotBuffer[i] - - // preserve empty slot anchor in vapor components - // DynamicFragment requires this anchor - if ( - parentComponent.type.__vapor && - isString(buffer) && - buffer === '' - ) { - push(buffer) - continue - } - - if (!isComment(buffer)) { + if (!isComment(slotBuffer[i])) { isEmptySlot = false break } diff --git a/packages/server-renderer/src/render.ts b/packages/server-renderer/src/render.ts index 2ff0c499f2..221d3895e2 100644 --- a/packages/server-renderer/src/render.ts +++ b/packages/server-renderer/src/render.ts @@ -236,17 +236,11 @@ export function renderVNode( push(escapeHtml(children as string)) break case Comment: - if (children) { - const content = children as string - // avoid escaping comments - if (content.startsWith('')) { - push(content) - } else { - push(``) - } - } else { - push(``) - } + push( + children + ? `` + : ``, + ) break case Static: push(children as string) diff --git a/packages/shared/src/domAnchors.ts b/packages/shared/src/domAnchors.ts deleted file mode 100644 index 691aebd73b..0000000000 --- a/packages/shared/src/domAnchors.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const BLOCK_ANCHOR_START_LABEL = '[[' -export const BLOCK_ANCHOR_END_LABEL = ']]' -export const IF_ANCHOR_LABEL: string = 'if' -export const DYNAMIC_COMPONENT_ANCHOR_LABEL: string = 'dynamic-component' -export const FOR_ANCHOR_LABEL: string = 'for' -export const SLOT_ANCHOR_LABEL: string = 'slot' - -export function isBlockStartAnchor(node: Node): node is Comment { - if (node.nodeType !== 8) return false - const data = (node as Comment).data - return data === `${BLOCK_ANCHOR_START_LABEL}` -} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 9372b8e1a9..0c38d640ba 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -13,5 +13,4 @@ export * from './looseEqual' export * from './toDisplayString' export * from './typeUtils' export * from './subSequence' -export * from './domAnchors' export * from './cssVars'