| MemoExpression // when cached by v-memo
| undefined
ssrCodegenNode?: CallExpression
- needAnchor?: boolean
}
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 {
type: NodeTypes.IF
branches: IfBranchNode[]
codegenNode?: IfConditionalExpression | CacheExpression // <div v-if v-once>
- needAnchor?: boolean
}
export interface IfBranchNode extends Node {
parseResult: ForParseResult
children: TemplateChildNode[]
codegenNode?: ForCodegenNode
- needAnchor?: boolean
}
export interface ForParseResult {
ssr = false,
isTS = false,
inSSR = false,
- vapor = false,
}: CodegenOptions,
): CodegenContext {
const context: CodegenContext = {
ssr,
isTS,
inSSR,
- vapor,
source: ast.source,
code: ``,
column: 1,
* @default 'template.vue.html'
*/
filename?: string
-
- /**
- * Indicates vapor component
- */
- vapor?: boolean
}
export interface TransformOptions
slotted = true,
ssr = false,
inSSR = false,
- vapor = false,
ssrCssVars = ``,
bindingMetadata = EMPTY_OBJ,
inline = false,
slotted,
ssr,
inSSR,
- vapor,
ssrCssVars,
bindingMetadata,
inline,
vFor: DirectiveNode | undefined,
slotChildren: TemplateChildNode[],
loc: SourceLocation,
- parent: ElementNode,
) => FunctionExpression
const buildClientSlotFn: SlotFnBuilder = (props, _vForExp, children, loc) =>
slotsProperties.push(
createObjectProperty(
arg || createSimpleExpression('default', true),
- buildSlotFn(exp, undefined, children, loc, node),
+ buildSlotFn(exp, undefined, children, loc),
),
)
}
}
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
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
}
slotted,
sourceMap: true,
...compilerOptions,
- vapor,
hmr: !isProd,
nodeTransforms: nodeTransforms.concat(
compilerOptions.nodeTransforms || [],
+++ /dev/null
-import { getCompiledString } from './utils'
-
-describe('block anchors', () => {
- describe('prepend', () => {
- test('prepend anchor with component', () => {
- expect(
- getCompiledString('<div><Comp/><Comp/><span/></div>', { vapor: true }),
- ).toMatchInlineSnapshot(`
- "\`<div><!--[[-->\`)
- _push(_ssrRenderComponent(_component_Comp, null, null, _parent))
- _push(\`<!--]]--><!--[[-->\`)
- _push(_ssrRenderComponent(_component_Comp, null, null, _parent))
- _push(\`<!--]]--><span></span></div>\`"
- `)
- })
-
- test('prepend anchor with component in ssr slot vnode fallback', () => {
- expect(
- getCompiledString(
- `<component :is="'div'">
- <div>
- <Comp/><Comp/><span/>
- </div>
- </component>`,
- { vapor: true },
- ),
- ).toMatchInlineSnapshot(`
- "\`\`)
- _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent('div'), null, {
- default: _withCtx((_, _push, _parent, _scopeId) => {
- if (_push) {
- _push(\`<div\${_scopeId}><!--[[-->\`)
- _push(_ssrRenderComponent(_component_Comp, null, null, _parent, _scopeId))
- _push(\`<!--]]--><!--[[-->\`)
- _push(_ssrRenderComponent(_component_Comp, null, null, _parent, _scopeId))
- _push(\`<!--]]--><span\${_scopeId}></span></div>\`)
- } else {
- return [
- _createVNode("div", null, [
- _createCommentVNode("[["),
- _createVNode(_component_Comp),
- _createCommentVNode("]]"),
- _createCommentVNode("[["),
- _createVNode(_component_Comp),
- _createCommentVNode("]]"),
- _createVNode("span")
- ])
- ]
- }
- }),
- _: 1 /* STABLE */
- }), _parent)
- _push(\`<!--dynamic-component-->\`"
- `)
- })
-
- test('prepend anchor with slot', () => {
- expect(
- getCompiledString('<div><slot name="foo"/><slot/><span/></div>', {
- vapor: true,
- }),
- ).toMatchInlineSnapshot(`
- "\`<div><!--[[-->\`)
- _ssrRenderSlot(_ctx.$slots, "foo", {}, null, _push, _parent)
- _push(\`<!--slot--><!--]]--><!--[[-->\`)
- _ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent)
- _push(\`<!--slot--><!--]]--><span></span></div>\`"
- `)
- })
-
- test('prepend anchor with slot in ssr slot vnode fallback', () => {
- expect(
- getCompiledString(
- `<component :is="'div'">
- <div>
- <slot name="foo"/>
- <slot/>
- <span/>
- </div>
- </component>`,
- { vapor: true },
- ),
- ).toMatchInlineSnapshot(`
- "\`\`)
- _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent('div'), null, {
- default: _withCtx((_, _push, _parent, _scopeId) => {
- if (_push) {
- _push(\`<div\${_scopeId}><!--[[-->\`)
- _ssrRenderSlot(_ctx.$slots, "foo", {}, null, _push, _parent, _scopeId)
- _push(\`<!--slot--><!--]]--><!--[[-->\`)
- _ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent, _scopeId)
- _push(\`<!--slot--><!--]]--><span\${_scopeId}></span></div>\`)
- } 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(\`<!--dynamic-component-->\`"
- `)
- })
-
- test('prepend anchor with v-if/else-if/else', () => {
- expect(
- getCompiledString(
- `<div>
- <span v-if="foo"/>
- <span v-else-if="bar"/>
- <span v-else/>
- <span/>
- </div>`,
- {
- vapor: true,
- },
- ),
- ).toMatchInlineSnapshot(`
- "\`<div><!--[[-->\`)
- if (_ctx.foo) {
- _push(\`<span></span>\`)
- _push(\`<!--if-->\`)
- } else if (_ctx.bar) {
- _push(\`<span></span>\`)
- _push(\`<!--if--><!--if-->\`)
- } else {
- _push(\`<span></span>\`)
- _push(\`<!--if--><!--if-->\`)
- }
- _push(\`<!--]]--><span></span></div>\`"
- `)
- })
-
- test('prepend anchor with v-if/else-if/else in ssr slot vnode fallback', () => {
- expect(
- getCompiledString(
- `<component :is="'div'">
- <div>
- <span v-if="foo"/>
- <span v-else-if="bar"/>
- <span v-else/>
- <span/>
- </div>
- </component>`,
- { vapor: true },
- ),
- ).toMatchInlineSnapshot(`
- "\`\`)
- _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent('div'), null, {
- default: _withCtx((_, _push, _parent, _scopeId) => {
- if (_push) {
- _push(\`<div\${_scopeId}><!--[[-->\`)
- if (_ctx.foo) {
- _push(\`<span\${_scopeId}></span>\`)
- _push(\`<!--if-->\`)
- } else if (_ctx.bar) {
- _push(\`<span\${_scopeId}></span>\`)
- _push(\`<!--if--><!--if-->\`)
- } else {
- _push(\`<span\${_scopeId}></span>\`)
- _push(\`<!--if--><!--if-->\`)
- }
- _push(\`<!--]]--><span\${_scopeId}></span></div>\`)
- } else {
- return [
- _createVNode("div", null, [
- _createCommentVNode("[["),
- (_ctx.foo)
- ? (_openBlock(), _createBlock(_Fragment, { key: 0 }, [
- _createVNode("span"),
- _createCommentVNode("<!--if-->")
- ], 64 /* STABLE_FRAGMENT */))
- : (_ctx.bar)
- ? (_openBlock(), _createBlock(_Fragment, { key: 1 }, [
- _createVNode("span"),
- _createCommentVNode("<!--if--><!--if-->")
- ], 64 /* STABLE_FRAGMENT */))
- : (_openBlock(), _createBlock(_Fragment, { key: 2 }, [
- _createVNode("span"),
- _createCommentVNode("<!--if--><!--if-->")
- ], 64 /* STABLE_FRAGMENT */)),
- _createCommentVNode("]]"),
- _createVNode("span")
- ])
- ]
- }
- }),
- _: 1 /* STABLE */
- }), _parent)
- _push(\`<!--dynamic-component-->\`"
- `)
- })
-
- test('prepend anchor with nested v-if', () => {
- expect(
- getCompiledString(
- `<div>
- <span v-if="foo">
- <span v-if="foo1" />
- <span />
- </span>
- <span v-else-if="bar">
- <span v-if="bar1" />
- <span />
- </span>
- <span v-else>
- <span v-if="bar2" />
- <span />
- </span>
- <span />
- </div>`,
- {
- vapor: true,
- },
- ),
- ).toMatchInlineSnapshot(`
- "\`<div><!--[[-->\`)
- if (_ctx.foo) {
- _push(\`<span><!--[[-->\`)
- if (_ctx.foo1) {
- _push(\`<span></span>\`)
- _push(\`<!--if-->\`)
- } else {
- _push(\`<!--if-->\`)
- }
- _push(\`<!--]]--><span></span></span>\`)
- _push(\`<!--if-->\`)
- } else if (_ctx.bar) {
- _push(\`<span><!--[[-->\`)
- if (_ctx.bar1) {
- _push(\`<span></span>\`)
- _push(\`<!--if-->\`)
- } else {
- _push(\`<!--if-->\`)
- }
- _push(\`<!--]]--><span></span></span>\`)
- _push(\`<!--if--><!--if-->\`)
- } else {
- _push(\`<span><!--[[-->\`)
- if (_ctx.bar2) {
- _push(\`<span></span>\`)
- _push(\`<!--if-->\`)
- } else {
- _push(\`<!--if-->\`)
- }
- _push(\`<!--]]--><span></span></span>\`)
- _push(\`<!--if--><!--if-->\`)
- }
- _push(\`<!--]]--><span></span></div>\`"
- `)
- })
-
- test('prepend anchor with nested v-if in ssr slot vnode fallback', () => {
- expect(
- getCompiledString(
- `<component :is="'div'">
- <div>
- <span v-if="foo">
- <span v-if="foo1" />
- <span />
- </span>
- <span v-else-if="bar">
- <span v-if="bar1" />
- <span />
- </span>
- <span v-else>
- <span v-if="bar2" />
- <span />
- </span>
- <span />
- </div>
- </component>`,
- { vapor: true },
- ),
- ).toMatchInlineSnapshot(`
- "\`\`)
- _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent('div'), null, {
- default: _withCtx((_, _push, _parent, _scopeId) => {
- if (_push) {
- _push(\`<div\${_scopeId}><!--[[-->\`)
- if (_ctx.foo) {
- _push(\`<span\${_scopeId}><!--[[-->\`)
- if (_ctx.foo1) {
- _push(\`<span\${_scopeId}></span>\`)
- _push(\`<!--if-->\`)
- } else {
- _push(\`<!--if-->\`)
- }
- _push(\`<!--]]--><span\${_scopeId}></span></span>\`)
- _push(\`<!--if-->\`)
- } else if (_ctx.bar) {
- _push(\`<span\${_scopeId}><!--[[-->\`)
- if (_ctx.bar1) {
- _push(\`<span\${_scopeId}></span>\`)
- _push(\`<!--if-->\`)
- } else {
- _push(\`<!--if-->\`)
- }
- _push(\`<!--]]--><span\${_scopeId}></span></span>\`)
- _push(\`<!--if--><!--if-->\`)
- } else {
- _push(\`<span\${_scopeId}><!--[[-->\`)
- if (_ctx.bar2) {
- _push(\`<span\${_scopeId}></span>\`)
- _push(\`<!--if-->\`)
- } else {
- _push(\`<!--if-->\`)
- }
- _push(\`<!--]]--><span\${_scopeId}></span></span>\`)
- _push(\`<!--if--><!--if-->\`)
- }
- _push(\`<!--]]--><span\${_scopeId}></span></div>\`)
- } 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("<!--if-->")
- ], 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("<!--if--><!--if-->")
- ], 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("<!--if--><!--if-->")
- ], 64 /* STABLE_FRAGMENT */)),
- _createCommentVNode("]]"),
- _createVNode("span")
- ])
- ]
- }
- }),
- _: 1 /* STABLE */
- }), _parent)
- _push(\`<!--dynamic-component-->\`"
- `)
- })
-
- test('prepend anchor with template v-if', () => {
- expect(
- getCompiledString(
- `<component :is="tag">
- <div v-if="foo">
- <template v-if="depth < 5">
- foo
- </template>
- <div></div>
- </div>
- </component>`,
- { vapor: true },
- ),
- ).toMatchInlineSnapshot(`
- "\`\`)
- _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.tag), null, {
- default: _withCtx((_, _push, _parent, _scopeId) => {
- if (_push) {
- if (_ctx.foo) {
- _push(\`<div\${_scopeId}><!--[[-->\`)
- if (_ctx.depth < 5) {
- _push(\` foo \`)
- _push(\`<!--if-->\`)
- } else {
- _push(\`<!--if-->\`)
- }
- _push(\`<!--]]--><div\${_scopeId}></div></div>\`)
- _push(\`<!--if-->\`)
- } else {
- _push(\`<!--if-->\`)
- }
- } 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(\`<!--dynamic-component-->\`"
- `)
- })
-
- test('prepend anchor with v-for', () => {
- expect(
- getCompiledString('<div><span v-for="item in items"/><span/></div>', {
- vapor: true,
- }),
- ).toMatchInlineSnapshot(`
- "\`<div><!--[[-->\`)
- _ssrRenderList(_ctx.items, (item) => {
- _push(\`<span></span>\`)
- })
- _push(\`<!--for--><!--]]--><span></span></div>\`"
- `)
- })
-
- test('prepend anchor with v-for in ssr slot vnode fallback', () => {
- expect(
- getCompiledString(
- `<component :is="'div'">
- <div>
- <span v-for="item in items"/><span/>
- </div>
- </component>`,
- { vapor: true },
- ),
- ).toMatchInlineSnapshot(`
- "\`\`)
- _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent('div'), null, {
- default: _withCtx((_, _push, _parent, _scopeId) => {
- if (_push) {
- _push(\`<div\${_scopeId}><!--[[-->\`)
- _ssrRenderList(_ctx.items, (item) => {
- _push(\`<span\${_scopeId}></span>\`)
- })
- _push(\`<!--for--><!--]]--><span\${_scopeId}></span></div>\`)
- } 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(\`<!--dynamic-component-->\`"
- `)
- })
- })
-
- // TODO add more tests
- describe('insert', () => {
- test('insertion anchor with component', () => {
- expect(
- getCompiledString('<div><span/><Comp/><span/></div>', { vapor: true }),
- ).toMatchInlineSnapshot(`
- "\`<div><span></span><!--[[-->\`)
- _push(_ssrRenderComponent(_component_Comp, null, null, _parent))
- _push(\`<!--]]--><span></span></div>\`"
- `)
- })
- })
-
- // TODO add more tests
- describe('append', () => {
- test('append anchor', () => {
- expect(
- getCompiledString('<div><span/><Comp/><Comp/></div>', { vapor: true }),
- ).toMatchInlineSnapshot(`
- "\`<div><span></span><!--[[-->\`)
- _push(_ssrRenderComponent(_component_Comp, null, null, _parent))
- _push(\`<!--]]--><!--[[-->\`)
- _push(_ssrRenderComponent(_component_Comp, null, null, _parent))
- _push(\`<!--]]--></div>\`"
- `)
- })
- })
-
- test('mixed anchors', () => {
- expect(
- getCompiledString('<div><Comp/><span/><Comp/><span/><Comp/></div>', {
- vapor: true,
- }),
- ).toMatchInlineSnapshot(`
- "\`<div><!--[[-->\`)
- _push(_ssrRenderComponent(_component_Comp, null, null, _parent))
- _push(\`<!--]]--><span></span><!--[[-->\`)
- _push(_ssrRenderComponent(_component_Comp, null, null, _parent))
- _push(\`<!--]]--><span></span><!--[[-->\`)
- _push(_ssrRenderComponent(_component_Comp, null, null, _parent))
- _push(\`<!--]]--></div>\`"
- `)
- })
-
- test('mixed anchors in ssr slot vnode fallback', () => {
- expect(
- getCompiledString(
- `<component :is="'div'">
- <div>
- <Comp/><span/>
- <Comp/><span/>
- <Comp/>
- </div>
- </component>`,
- {
- vapor: true,
- },
- ),
- ).toMatchInlineSnapshot(`
- "\`\`)
- _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent('div'), null, {
- default: _withCtx((_, _push, _parent, _scopeId) => {
- if (_push) {
- _push(\`<div\${_scopeId}><!--[[-->\`)
- _push(_ssrRenderComponent(_component_Comp, null, null, _parent, _scopeId))
- _push(\`<!--]]--><span\${_scopeId}></span><!--[[-->\`)
- _push(_ssrRenderComponent(_component_Comp, null, null, _parent, _scopeId))
- _push(\`<!--]]--><span\${_scopeId}></span><!--[[-->\`)
- _push(_ssrRenderComponent(_component_Comp, null, null, _parent, _scopeId))
- _push(\`<!--]]--></div>\`)
- } 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(\`<!--dynamic-component-->\`"
- `)
- })
-})
-
-describe('fragment anchors', () => {
- test('if', () => {
- expect(
- getCompiledString(
- `<component :is="tag">
- <span v-if="count === 1">1</span>
- <span v-else-if="count === 2">2</span>
- <span v-else-if="count === 3">3</span>
- <span v-else>4</span>
- </component>`,
- {
- vapor: true,
- },
- ),
- ).toMatchInlineSnapshot(`
- "\`\`)
- _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.tag), null, {
- default: _withCtx((_, _push, _parent, _scopeId) => {
- if (_push) {
- if (_ctx.count === 1) {
- _push(\`<span\${_scopeId}>1</span>\`)
- _push(\`<!--if-->\`)
- } else if (_ctx.count === 2) {
- _push(\`<span\${_scopeId}>2</span>\`)
- _push(\`<!--if--><!--if-->\`)
- } else if (_ctx.count === 3) {
- _push(\`<span\${_scopeId}>3</span>\`)
- _push(\`<!--if--><!--if--><!--if-->\`)
- } else {
- _push(\`<span\${_scopeId}>4</span>\`)
- _push(\`<!--if--><!--if--><!--if-->\`)
- }
- } else {
- return [
- (_ctx.count === 1)
- ? (_openBlock(), _createBlock(_Fragment, { key: 0 }, [
- _createVNode("span", null, "1"),
- _createCommentVNode("<!--if-->")
- ], 64 /* STABLE_FRAGMENT */))
- : (_ctx.count === 2)
- ? (_openBlock(), _createBlock(_Fragment, { key: 1 }, [
- _createVNode("span", null, "2"),
- _createCommentVNode("<!--if--><!--if-->")
- ], 64 /* STABLE_FRAGMENT */))
- : (_ctx.count === 3)
- ? (_openBlock(), _createBlock(_Fragment, { key: 2 }, [
- _createVNode("span", null, "3"),
- _createCommentVNode("<!--if--><!--if--><!--if-->")
- ], 64 /* STABLE_FRAGMENT */))
- : (_openBlock(), _createBlock(_Fragment, { key: 3 }, [
- _createVNode("span", null, "4"),
- _createCommentVNode("<!--if--><!--if--><!--if-->")
- ], 64 /* STABLE_FRAGMENT */))
- ]
- }
- }),
- _: 1 /* STABLE */
- }), _parent)
- _push(\`<!--dynamic-component-->\`"
- `)
- })
-
- test('if + v-html/v-text', () => {
- expect(
- getCompiledString(
- `<component :is="tag">
- <span v-if="count === 1" v-html="html"></span>
- <span v-else-if="count === 2" v-text="txt"></span>
- <span v-else>4</span>
- </component>`,
- {
- vapor: true,
- },
- ),
- ).toMatchInlineSnapshot(`
- "\`\`)
- _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.tag), null, {
- default: _withCtx((_, _push, _parent, _scopeId) => {
- if (_push) {
- if (_ctx.count === 1) {
- _push(\`<span\${
- _scopeId
- }>\${
- (_ctx.html) ?? ''
- }</span>\`)
- _push(\`<!--if-->\`)
- } else if (_ctx.count === 2) {
- _push(\`<span\${
- _scopeId
- }>\${
- _ssrInterpolate(_ctx.txt)
- }</span>\`)
- _push(\`<!--if--><!--if-->\`)
- } else {
- _push(\`<span\${_scopeId}>4</span>\`)
- _push(\`<!--if--><!--if-->\`)
- }
- } else {
- return [
- (_ctx.count === 1)
- ? (_openBlock(), _createBlock(_Fragment, { key: 0 }, [
- _createVNode("span", { innerHTML: _ctx.html }, null, 8 /* PROPS */, ["innerHTML"]),
- _createCommentVNode("<!--if-->")
- ], 64 /* STABLE_FRAGMENT */))
- : (_ctx.count === 2)
- ? (_openBlock(), _createBlock(_Fragment, { key: 1 }, [
- _createVNode("span", {
- textContent: _toDisplayString(_ctx.txt)
- }, null, 8 /* PROPS */, ["textContent"]),
- _createCommentVNode("<!--if--><!--if-->")
- ], 64 /* STABLE_FRAGMENT */))
- : (_openBlock(), _createBlock(_Fragment, { key: 2 }, [
- _createVNode("span", null, "4"),
- _createCommentVNode("<!--if--><!--if-->")
- ], 64 /* STABLE_FRAGMENT */))
- ]
- }
- }),
- _: 1 /* STABLE */
- }), _parent)
- _push(\`<!--dynamic-component-->\`"
- `)
- })
-
- test('for', () => {
- expect(
- getCompiledString(
- `<component :is="tag">
- <span v-for="item in items">{{item}}</span>
- </component>`,
- {
- vapor: true,
- },
- ),
- ).toMatchInlineSnapshot(`
- "\`\`)
- _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent(_ctx.tag), null, {
- default: _withCtx((_, _push, _parent, _scopeId) => {
- if (_push) {
- _ssrRenderList(_ctx.items, (item) => {
- _push(\`<span\${
- _scopeId
- }>\${
- _ssrInterpolate(item)
- }</span>\`)
- })
- _push(\`<!--for-->\`)
- } 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(\`<!--dynamic-component-->\`"
- `)
- })
-
- test('slot', () => {
- expect(
- getCompiledString(
- `<div>
- <slot name="foo"/>
- <slot/>
- </div>`,
- { vapor: true },
- ),
- ).toMatchInlineSnapshot(`
- "\`<div><!--[[-->\`)
- _ssrRenderSlot(_ctx.$slots, "foo", {}, null, _push, _parent)
- _push(\`<!--slot--><!--]]--><!--[[-->\`)
- _ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent)
- _push(\`<!--slot--><!--]]--></div>\`"
- `)
- })
-
- test('forwarded slot', () => {
- expect(
- getCompiledString(
- `<component :is="tag">
- <slot name="foo"/>
- <slot/>
- </component>`,
- { 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(\`<!--slot-->\`)
- _ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent, _scopeId)
- _push(\`<!--slot-->\`)
- } else {
- return [
- _renderSlot(_ctx.$slots, "foo"),
- _createCommentVNode("slot"),
- _renderSlot(_ctx.$slots, "default"),
- _createCommentVNode("slot")
- ]
- }
- }),
- _: 3 /* FORWARDED */
- }), _parent)
- _push(\`<!--dynamic-component-->\`"
- `)
- })
-
- test('dynamic component', () => {
- expect(
- getCompiledString(
- `<component is='tag'>
- <div>
- <component is="foo"/>
- </div>
- </component>`,
- { vapor: true },
- ),
- ).toMatchInlineSnapshot(`
- "\`\`)
- _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent("tag"), null, {
- default: _withCtx((_, _push, _parent, _scopeId) => {
- if (_push) {
- _push(\`<div\${_scopeId}>\`)
- _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent("foo"), null, null), _parent, _scopeId)
- _push(\`<!--dynamic-component--></div>\`)
- } else {
- return [
- _createVNode("div", null, [
- (_openBlock(), _createBlock(_resolveDynamicComponent("foo"))),
- _createCommentVNode("dynamic-component")
- ])
- ]
- }
- }),
- _: 1 /* STABLE */
- }), _parent)
- _push(\`<!--dynamic-component-->\`"
- `)
- })
-})
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,
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'
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) {
ssrProcessElement(child, context)
break
case ElementTypes.COMPONENT:
- if (child.needAnchor)
- context.pushStringPart(`<!--${BLOCK_ANCHOR_START_LABEL}-->`)
ssrProcessComponent(child, context, parent)
- if (child.needAnchor)
- context.pushStringPart(`<!--${BLOCK_ANCHOR_END_LABEL}-->`)
break
case ElementTypes.SLOT:
- if (child.needAnchor)
- context.pushStringPart(`<!--${BLOCK_ANCHOR_START_LABEL}-->`)
ssrProcessSlotOutlet(child, context)
- if (child.needAnchor)
- context.pushStringPart(`<!--${BLOCK_ANCHOR_END_LABEL}-->`)
break
case ElementTypes.TEMPLATE:
// TODO
)
break
case NodeTypes.IF:
- if (child.needAnchor)
- context.pushStringPart(`<!--${BLOCK_ANCHOR_START_LABEL}-->`)
ssrProcessIf(child, context, disableNestedFragments, disableComment)
- if (child.needAnchor)
- context.pushStringPart(`<!--${BLOCK_ANCHOR_END_LABEL}-->`)
break
case NodeTypes.FOR:
- if (child.needAnchor)
- context.pushStringPart(`<!--${BLOCK_ANCHOR_START_LABEL}-->`)
ssrProcessFor(child, context, disableNestedFragments)
- if (child.needAnchor)
- context.pushStringPart(`<!--${BLOCK_ANCHOR_END_LABEL}-->`)
break
case NodeTypes.IF_BRANCH:
// no-op - handled by ssrProcessIf
return exhaustiveCheck
}
}
- if (asFragment && !vapor) {
+ if (asFragment) {
context.pushStringPart(`<!--]-->`)
}
}
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<AttributeNode | DirectiveNode>,
-): 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 {
import {
CREATE_VNODE,
type CallExpression,
- type CommentNode,
type CompilerOptions,
type ComponentNode,
DOMDirectiveTransforms,
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,
import { SSR_RENDER_COMPONENT, SSR_RENDER_VNODE } from '../runtimeHelpers'
import {
type SSRTransformContext,
- isElementWithChildren,
- processBlockNodeAnchor,
processChildren,
processChildrenAsStatement,
} from '../ssrCodegenTransform'
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,
// 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)
})
// 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(`<!--${DYNAMIC_COMPONENT_ANCHOR_LABEL}-->`)
- }
}
}
}
vFor: DirectiveNode | undefined,
children: TemplateChildNode[],
parentContext: TransformContext,
- parent: TemplateChildNode,
): ReturnStatement {
// apply a sub-transform using vnode-based transforms.
const rawOptions = rawOptionsMap.get(parentContext.root)!
if (vFor) {
wrapperProps.push(extend({}, vFor))
}
- if (parentContext.vapor) {
- children = injectVaporAnchors(children, parent)
- }
const wrapperNode: TemplateNode = {
type: NodeTypes.ELEMENT,
// - 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(`<!--${fragmentAnchorLabel}-->`.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)
type SSRTransformContext,
processChildrenAsStatement,
} from '../ssrCodegenTransform'
-import { SLOT_ANCHOR_LABEL } from '@vue/shared'
export const ssrTransformSlotOutlet: NodeTransform = (node, context) => {
if (isSlotOutlet(node)) {
}
context.pushStatement(node.ssrCodegenNode!)
-
- // anchor for vapor slot
- if (context.options.vapor) {
- context.pushStringPart(`<!--${SLOT_ANCHOR_LABEL}-->`)
- }
}
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 =
needFragmentWrapper,
)
- const vapor = context.options.vapor
// v-for always renders a fragment unless explicitly disabled
- if (!disableNestedFragments && !vapor) {
+ if (!disableNestedFragments) {
context.pushStringPart(`<!--[-->`)
}
context.pushStatement(
renderLoop,
]),
)
- if (!disableNestedFragments && !vapor) {
+ if (!disableNestedFragments) {
context.pushStringPart(`<!--]-->`)
}
-
- // anchor for vapor v-for fragment
- if (vapor) {
- context.pushStringPart(`<!--${FOR_ANCHOR_LABEL}-->`)
- }
}
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(
)
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]
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 ? `\`<!--${IF_ANCHOR_LABEL}-->\`` : '`<!---->`',
- ]),
+ createCallExpression(`_push`, ['`<!---->`']),
])
}
}
return processChildrenAsStatement(branch, context, needFragmentWrapper)
}
-
-function createIfAnchors(count: number): string[] {
- const anchors: string[] = []
- for (let i = 0; i < count; i++) {
- anchors.push(`<!--${IF_ANCHOR_LABEL}-->`)
- }
- return [`\`${anchors.join('')}\``]
-}
"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
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))
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
}"
}"
`;
-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("<div></div>")
-const t1 = _template("<div><div></div><!><div></div><!><div><button></button></div></div>", 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(" ")
})
})
- describe('setInsertionState', () => {
- test('next, child and nthChild should be above the setInsertionState', () => {
- const code = compile(`
- <div>
- <div />
- <Comp />
- <div />
- <div v-if="true" />
- <div>
- <button :disabled="foo" />
- </div>
- </div>
- `)
- expect(code).toMatchSnapshot()
- })
- })
-
describe('execution order', () => {
test('basic', () => {
const code = compile(`<div :id="foo">{{ bar }}</div>`)
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)
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
}"
isTS: false,
inSSR: false,
inline: false,
- vapor: false,
bindingMetadata: {},
expressionPlugins: [],
}
}
for (const child of dynamic.children) {
if (!child.hasDynamicChild) {
- push(...genChildren(child, context, push, `n${child.id!}`))
+ push(...genChildren(child, context, `n${child.id!}`))
}
}
operation: InsertionStateTypes,
context: CodegenContext,
): CodeFragment[] {
- const { parent, anchor } = operation
+ const { parent, anchor, append } = operation
return [
NEWLINE,
...genCall(
? 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}`,
),
]
}
if (hasDynamicChild) {
- push(...genChildren(dynamic, context, push, `n${id}`))
+ push(...genChildren(dynamic, context, `n${id}`))
}
return frag
export function genChildren(
dynamic: IRDynamicInfo,
context: CodegenContext,
- pushBlock: (...items: CodeFragment[]) => number,
from: string = `n${dynamic.id}`,
): CodeFragment[] {
const { helper } = context
// 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)
} else if (elementIndex > 1) {
init = genCall(helper('nthChild'), from, String(elementIndex))
}
- pushBlock(...init)
+ push(...init)
}
}
}
prev = [variable, elementIndex]
- push(...genChildren(child, context, pushBlock, variable))
+ push(...genChildren(child, context, variable))
}
return frag
once?: boolean
parent?: number
anchor?: number
+ append?: boolean
childIndex?: number
}
onlyChild: boolean
parent?: number
anchor?: number
+ append?: boolean
childIndex?: number
}
dynamic?: SimpleExpressionNode
parent?: number
anchor?: number
+ append?: boolean
childIndex?: number
scopeId?: string | null
}
forwarded?: boolean
parent?: number
anchor?: number
+ append?: boolean
childIndex?: number
}
function processDynamicChildren(context: TransformContext<ElementNode>) {
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())
}
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) {
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
}
}
}
<template><span/>{{ data }}{{ data }}<span/></template>
`)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `"<span></span>foofoo<span></span>"`,
+ `
+ "
+ <!--[--><span></span>foofoo<span></span><!--]-->
+ "
+ `,
)
data.value = 'bar'
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `"<span></span>barbar<span></span>"`,
+ `
+ "
+ <!--[--><span></span>barbar<span></span><!--]-->
+ "
+ `,
)
})
<template><span/>{{ data }}A{{ data }}B{{ data }}<span/></template>
`)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `"<span></span>fooAfooBfoo<span></span>"`,
+ `
+ "
+ <!--[--><span></span>fooAfooBfoo<span></span><!--]-->
+ "
+ `,
)
data.value = 'bar'
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `"<span></span>barAbarBbar<span></span>"`,
+ `
+ "
+ <!--[--><span></span>barAbarBbar<span></span><!--]-->
+ "
+ `,
)
})
`
"
<!--[--> <!--]-->
- <!--slot-->"
+ "
`,
)
`
"
<!--[-->foo<!--]-->
- <!--slot-->"
+ "
`,
)
})
<template> A<span>{{ data }}</span>{{ data }}</template>
`)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `" A<span>foo</span>foo"`,
+ `
+ "
+ <!--[--> A<span>foo</span>foo<!--]-->
+ "
+ `,
)
data.value = 'bar'
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `" A<span>bar</span>bar"`,
+ `
+ "
+ <!--[--> A<span>bar</span>bar<!--]-->
+ "
+ `,
)
})
{ Child: `<template>{{ data }}</template>` },
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[-->foo<!--]]-->
- </div>"
- `,
+ `"<div><span></span>foo</div>"`,
)
data.value = 'bar'
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[-->bar<!--]]-->
- </div>"
- `,
+ `"<div><span></span>bar</div>"`,
)
})
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><div>foo</div>-foo-<!--]]-->
+ <!--[--><div>foo</div>-foo-<!--]-->
</div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><div>bar</div>-bar-<!--]]-->
+ <!--[--><div>bar</div>-bar-<!--]-->
</div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
- <!--[[--><div>foo</div>-foo-<!--]]-->
+ <!--[--><div>foo</div>-foo-<!--]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
- <!--[[--><div>bar</div>-bar-<!--]]-->
+ <!--[--><div>bar</div>-bar-<!--]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
- <!--[[--><div></div><div>foo</div>-foo-<div></div><!--]]-->
+ <!--[--><div></div>
+ <!--[--><div>foo</div>-foo-<!--]-->
+ <div></div><!--]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
- <!--[[--><div></div><div>bar</div>-bar-<div></div><!--]]-->
+ <!--[--><div></div>
+ <!--[--><div>bar</div>-bar-<!--]-->
+ <div></div><!--]-->
<span></span></div>"
`,
)
},
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[-->foo<!--]]-->
- <span></span></div>"
- `,
+ `"<div><span></span>foo<span></span></div>"`,
)
data.value = 'bar'
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[-->bar<!--]]-->
- <span></span></div>"
- `,
+ `"<div><span></span>bar<span></span></div>"`,
)
})
},
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[--><div>foo</div><!--]]-->
- <span></span></div>"
- `,
+ `"<div><span></span><div>foo</div><span></span></div>"`,
)
data.value = 'bar'
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[--><div>bar</div><!--]]-->
- <span></span></div>"
- `,
+ `"<div><span></span><div>bar</div><span></span></div>"`,
)
})
},
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[--><div><span></span>
- <!--[[--><div>foo</div><!--]]-->
- <span></span></div><!--]]-->
- <span></span></div>"
- `,
- )
-
- data.value = 'bar'
- await nextTick()
- expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[--><div><span></span>
- <!--[[--><div>bar</div><!--]]-->
- <span></span></div><!--]]-->
- <span></span></div>"
- `,
- )
- })
-
- test('consecutive components with insertion anchor', async () => {
- const { container, data } = await testHydration(
- `<template>
- <div>
- <span/>
- <components.Child/>
- <components.Child/>
- <span/>
- </div>
- </template>
- `,
- {
- Child: `<template>{{ data }}</template>`,
- },
- )
- expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[-->foo<!--]]-->
- <!--[[-->foo<!--]]-->
- <span></span></div>"
- `,
+ `"<div><span></span><div><span></span><div>foo</div><span></span></div><span></span></div>"`,
)
data.value = 'bar'
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[-->bar<!--]]-->
- <!--[[-->bar<!--]]-->
- <span></span></div>"
- `,
+ `"<div><span></span><div><span></span><div>bar</div><span></span></div><span></span></div>"`,
)
})
data,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div>
- <!--[[--><span>foo</span><!--]]-->
- <!--[[--><span>bar</span><!--]]-->
- </div>"
- `,
+ `"<div><span>foo</span><span>bar</span></div>"`,
)
data.foo = 'foo1'
data.bar = 'bar1'
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div>
- <!--[[--><span>foo1</span><!--]]-->
- <!--[[--><span>bar1</span><!--]]-->
- </div>"
- `,
+ `"<div><span>foo1</span><span>bar1</span></div>"`,
)
})
},
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[--><div>foo</div><!--]]-->
- <!--[[--><div>foo</div><!--]]-->
- <span></span></div>"
- `,
+ `"<div><span></span><div>foo</div><div>foo</div><span></span></div>"`,
)
data.value = 'bar'
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[--><div>bar</div><!--]]-->
- <!--[[--><div>bar</div><!--]]-->
- <span></span></div>"
- `,
+ `"<div><span></span><div>bar</div><div>bar</div><span></span></div>"`,
)
})
},
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[--><div><span></span>
- <!--[[--><div>foo</div><!--]]-->
- <!--[[--><div>foo</div><!--]]-->
- <span></span></div><!--]]-->
- <span></span></div>"
- `,
+ `"<div><span></span><div><span></span><div>foo</div><div>foo</div><span></span></div><span></span></div>"`,
)
data.value = 'bar'
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[--><div><span></span>
- <!--[[--><div>bar</div><!--]]-->
- <!--[[--><div>bar</div><!--]]-->
- <span></span></div><!--]]-->
- <span></span></div>"
- `,
+ `"<div><span></span><div><span></span><div>bar</div><div>bar</div><span></span></div><span></span></div>"`,
)
})
},
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[-->foo<!--]]-->
- <span></span>
- <!--[[-->foo<!--]]-->
- <span></span></div>"
- `,
- )
-
- data.value = 'bar'
- await nextTick()
- expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[-->bar<!--]]-->
- <span></span>
- <!--[[-->bar<!--]]-->
- <span></span></div>"
- `,
- )
- })
-
- test('mixed component and text with insertion anchor', async () => {
- const { container, data } = await testHydration(
- `<template>
- <div>
- <span/>
- <components.Child/>
- {{ data }}
- <components.Child/>
- <span/>
- </div>
- </template>
- `,
- {
- Child: `<template>{{ data }}</template>`,
- },
- )
- expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[-->foo<!--]]-->
- foo
- <!--[[-->foo<!--]]-->
- <span></span></div>"
- `,
+ `"<div><span></span>foo<span></span>foo<span></span></div>"`,
)
data.value = 'bar'
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[-->bar<!--]]-->
- bar
- <!--[[-->bar<!--]]-->
- <span></span></div>"
- `,
+ `"<div><span></span>bar<span></span>bar<span></span></div>"`,
)
})
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><div>foo</div>-foo<!--]]-->
+ <!--[--><div>foo</div>-foo<!--]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><div>bar</div>-bar<!--]]-->
+ <!--[--><div>bar</div>-bar<!--]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><div>foo</div>-foo-<!--]]-->
+ <!--[--><div>foo</div>-foo-<!--]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><div>bar</div>-bar-<!--]]-->
+ <!--[--><div>bar</div>-bar-<!--]-->
<span></span></div>"
`,
)
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
- "<div><span></span>
- <!--[[--><div><span></span>
- <!--[[--><div>foo</div>-foo-<!--]]-->
- <span></span></div><!--]]-->
- <span></span></div>"
+ "<div><span></span><div><span></span>
+ <!--[--><div>foo</div>-foo-<!--]-->
+ <span></span></div><span></span></div>"
`,
)
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
- "<div><span></span>
- <!--[[--><div><span></span>
- <!--[[--><div>bar</div>-bar-<!--]]-->
- <span></span></div><!--]]-->
- <span></span></div>"
+ "<div><span></span><div><span></span>
+ <!--[--><div>bar</div>-bar-<!--]-->
+ <span></span></div><span></span></div>"
`,
)
})
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><div>foo</div>-foo<!--]]-->
- <!--[[--><div>foo</div>-foo<!--]]-->
+ <!--[--><div>foo</div>-foo<!--]-->
+ <!--[--><div>foo</div>-foo<!--]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><div>bar</div>-bar<!--]]-->
- <!--[[--><div>bar</div>-bar<!--]]-->
+ <!--[--><div>bar</div>-bar<!--]-->
+ <!--[--><div>bar</div>-bar<!--]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><div>foo</div>-foo-<!--]]-->
- <!--[[--><div>foo</div>-foo-<!--]]-->
+ <!--[--><div>foo</div>-foo-<!--]-->
+ <!--[--><div>foo</div>-foo-<!--]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><div>bar</div>-bar-<!--]]-->
- <!--[[--><div>bar</div>-bar-<!--]]-->
+ <!--[--><div>bar</div>-bar-<!--]-->
+ <!--[--><div>bar</div>-bar-<!--]-->
<span></span></div>"
`,
)
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
- "<div><span></span>
- <!--[[--><div><span></span>
- <!--[[--><div>foo</div>-foo-<!--]]-->
- <!--[[--><div>foo</div>-foo-<!--]]-->
- <span></span></div><!--]]-->
- <span></span></div>"
+ "<div><span></span><div><span></span>
+ <!--[--><div>foo</div>-foo-<!--]-->
+ <!--[--><div>foo</div>-foo-<!--]-->
+ <span></span></div><span></span></div>"
`,
)
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
- "<div><span></span>
- <!--[[--><div><span></span>
- <!--[[--><div>bar</div>-bar-<!--]]-->
- <!--[[--><div>bar</div>-bar-<!--]]-->
- <span></span></div><!--]]-->
- <span></span></div>"
+ "<div><span></span><div><span></span>
+ <!--[--><div>bar</div>-bar-<!--]-->
+ <!--[--><div>bar</div>-bar-<!--]-->
+ <span></span></div><span></span></div>"
`,
)
})
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><div>foo</div>-foo-<div>foo</div>-foo-<!--]]-->
+ <!--[-->
+ <!--[--><div>foo</div>-foo-<!--]-->
+ <!--[--><div>foo</div>-foo-<!--]-->
+ <!--]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><div>bar</div>-bar-<div>bar</div>-bar-<!--]]-->
+ <!--[-->
+ <!--[--><div>bar</div>-bar-<!--]-->
+ <!--[--><div>bar</div>-bar-<!--]-->
+ <!--]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><div>foo</div>-foo<!--]]-->
+ <!--[--><div>foo</div>-foo<!--]-->
<span></span>
- <!--[[--><div>foo</div>-foo<!--]]-->
+ <!--[--><div>foo</div>-foo<!--]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><div>bar</div>-bar<!--]]-->
+ <!--[--><div>bar</div>-bar<!--]-->
<span></span>
- <!--[[--><div>bar</div>-bar<!--]]-->
+ <!--[--><div>bar</div>-bar<!--]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><div>foo</div>-foo<!--]]-->
+ <!--[--><div>foo</div>-foo<!--]-->
foo
- <!--[[--><div>foo</div>-foo<!--]]-->
+ <!--[--><div>foo</div>-foo<!--]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><div>bar</div>-bar<!--]]-->
+ <!--[--><div>bar</div>-bar<!--]-->
bar
- <!--[[--><div>bar</div>-bar<!--]]-->
+ <!--[--><div>bar</div>-bar<!--]-->
<span></span></div>"
`,
)
ref('foo'),
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[--><div>foo</div><!--dynamic-component--><!--]]-->
- <span></span></div>"
- `,
+ `"<div><span></span><div>foo</div><!--dynamic-component--><span></span></div>"`,
)
data.value = 'bar'
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[--><div>bar</div><!--dynamic-component--><!--]]-->
- <span></span></div>"
- `,
+ `"<div><span></span><div>bar</div><!--dynamic-component--><span></span></div>"`,
)
})
ref('foo'),
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[--><div>foo</div><!--dynamic-component--><!--]]-->
- <!--[[--><div>foo</div><!--dynamic-component--><!--]]-->
- <span></span></div>"
- `,
+ `"<div><span></span><div>foo</div><!--dynamic-component--><div>foo</div><!--dynamic-component--><span></span></div>"`,
)
data.value = 'bar'
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[--><div>bar</div><!--dynamic-component--><!--]]-->
- <!--[[--><div>bar</div><!--dynamic-component--><!--]]-->
- <span></span></div>"
- `,
+ `"<div><span></span><div>bar</div><!--dynamic-component--><div>bar</div><!--dynamic-component--><span></span></div>"`,
)
})
`
"<div>
<!--[--><span>foo</span><!--]-->
- <!--slot--></div><!--dynamic-component-->"
+ </div><!--dynamic-component-->"
`,
)
`
"<div>
<!--[--><span>bar</span><!--]-->
- <!--slot--></div><!--dynamic-component-->"
+ </div><!--dynamic-component-->"
`,
)
})
data,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span>outer</span>
- <!--[[--><div>inner</div><!--if--><!--]]-->
- </div><!--if-->"
- `,
+ `"<div><span>outer</span><div>inner</div><!--if--></div><!--if-->"`,
)
data.inner = false
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span>outer</span>
- <!--[[--><!--if--><!--]]-->
- </div><!--if-->"
- `,
+ `"<div><span>outer</span><!--if--></div><!--if-->"`,
)
data.outer = false
data,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<span>
- <!--[[--><span>foo</span><!--if--><!--]]-->
- <!--[[--><span>bar</span><!--if--><!--]]-->
- <span>baz</span>
- <!--[[--><span>qux</span><!--if--><!--]]-->
- <span>quux</span></span><!--if-->"
- `,
+ `"<span><span>foo</span><!--if--><span>bar</span><!--if--><span>baz</span><span>qux</span><!--if--><span>quux</span></span><!--if-->"`,
)
data.qux = 'qux1'
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<span>
- <!--[[--><span>foo</span><!--if--><!--]]-->
- <!--[[--><span>bar</span><!--if--><!--]]-->
- <span>baz</span>
- <!--[[--><span>qux1</span><!--if--><!--]]-->
- <span>quux</span></span><!--if-->"
- `,
+ `"<span><span>foo</span><!--if--><span>bar</span><!--if--><span>baz</span><span>qux1</span><!--if--><span>quux</span></span><!--if-->"`,
)
data.foo = 'foo1'
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<span>
- <!--[[--><span>foo1</span><!--if--><!--]]-->
- <!--[[--><span>bar</span><!--if--><!--]]-->
- <span>baz</span>
- <!--[[--><span>qux1</span><!--if--><!--]]-->
- <span>quux</span></span><!--if-->"
- `,
+ `"<span><span>foo1</span><!--if--><span>bar</span><!--if--><span>baz</span><span>qux1</span><!--if--><span>quux</span></span><!--if-->"`,
)
})
data,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[-->foo<!--if--><!--]]-->
- <span></span></div>"
- `,
+ `"<div><span></span>foo<!--if--><span></span></div>"`,
)
data.value = false
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[--><!--if--><!--]]-->
- <span></span></div>"
- `,
+ `"<div><span></span><!--if--><span></span></div>"`,
)
})
data,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div>
- <!--[[--><span>foo</span><!--]]-->
- <!--[[--><span>bar</span><!--]]-->
- </div><!--if-->"
- `,
+ `"<div><span>foo</span><span>bar</span></div><!--if-->"`,
)
data.show = false
)
})
- test('consecutive v-if on component with insertion anchor', async () => {
- const data = ref(true)
- const { container } = await testHydration(
- `<template>
- <div>
- <span/>
- <components.Child v-if="data"/>
- <components.Child v-if="data"/>
- <span/>
- </div>
- </template>`,
- { Child: `<template>foo</template>` },
- data,
- )
- expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[-->foo<!--if--><!--]]-->
- <!--[[-->foo<!--if--><!--]]-->
- <span></span></div>"
- `,
- )
-
- data.value = false
- await nextTick()
- expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[--><!--if--><!--]]-->
- <!--[[--><!--if--><!--]]-->
- <span></span></div>"
- `,
- )
-
- data.value = true
- await nextTick()
- expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(`
- "<div><span></span>
- <!--[[-->foo<!--if--><!--]]-->
- <!--[[-->foo<!--if--><!--]]-->
- <span></span></div>"
- `)
- })
-
test('on fragment component', async () => {
const data = ref(true)
const { container } = await testHydration(
data,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `"<div><div>true</div>-true-<!--if--></div>"`,
+ `
+ "<div>
+ <!--[--><div>true</div>-true-<!--if--><!--]-->
+ </div>"
+ `,
)
data.value = false
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `"<div><!--if--></div>"`,
+ `
+ "<div>
+ <!--[--><!--if--><!--]-->
+ </div>"
+ `,
)
data.value = true
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `"<div><div>true</div>-true-<!--if--></div>"`,
+ `
+ "<div>
+ <!--[--><div>true</div>-true-<!--if--><!--]-->
+ </div>"
+ `,
)
})
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><div>true</div>-true-<!--if--><!--]]-->
+ <!--[--><div>true</div>-true-<!--if--><!--]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><!--if--><!--]]-->
+ <!--[--><!--if--><!--]-->
<span></span></div>"
`,
)
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(`
"<div><span></span>
- <!--[[--><div>true</div>-true-<!--if--><!--]]-->
+ <!--[--><div>true</div>-true-<!--if--><!--]-->
<span></span></div>"
`)
})
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><div>true</div>-true-<!--if--><!--]]-->
- <!--[[--><div>true</div>-true-<!--if--><!--]]-->
+ <!--[--><div>true</div>-true-<!--if--><!--]-->
+ <!--[--><div>true</div>-true-<!--if--><!--]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><!--if--><!--]]-->
- <!--[[--><!--if--><!--]]-->
+ <!--[--><!--if--><!--]-->
+ <!--[--><!--if--><!--]-->
<span></span></div>"
`,
)
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(`
"<div><span></span>
- <!--[[--><div>true</div>-true-<!--if--><!--]]-->
- <!--[[--><div>true</div>-true-<!--if--><!--]]-->
+ <!--[--><div>true</div>-true-<!--if--><!--]-->
+ <!--[--><div>true</div>-true-<!--if--><!--]-->
<span></span></div>"
`)
})
data,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[-->foo<!--dynamic-component--><!--if--><!--]]-->
- <span></span></div>"
- `,
+ `"<div><span></span>foo<!--dynamic-component--><!--if--><span></span></div>"`,
)
data.value = false
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `
- "<div><span></span>
- <!--[[--><!--if--><!--]]-->
- <span></span></div>"
- `,
+ `"<div><span></span><!--if--><span></span></div>"`,
)
data.value = true
await nextTick()
- expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(`
- "<div><span></span>
- <!--[[-->foo<!--dynamic-component--><!--if--><!--]]-->
- <span></span></div>"
- `)
+ expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
+ `"<div><span></span>foo<!--dynamic-component--><!--if--><span></span></div>"`,
+ )
})
})
ref(['a', 'b', 'c']),
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `"<span>a</span><span>b</span><span>c</span><!--for-->"`,
+ `
+ "
+ <!--[--><span>a</span><span>b</span><span>c</span><!--]-->
+ "
+ `,
)
data.value.push('d')
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `"<span>a</span><span>b</span><span>c</span><span>d</span><!--for-->"`,
+ `
+ "
+ <!--[--><span>a</span><span>b</span><span>c</span><span>d</span><!--]-->
+ "
+ `,
)
})
ref(['a', 'b', 'c']),
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `"<div><span>a</span><span>b</span><span>c</span><!--for--></div><div>3</div>"`,
+ `
+ "
+ <!--[--><div>
+ <!--[--><span>a</span><span>b</span><span>c</span><!--]-->
+ </div><div>3</div><!--]-->
+ "
+ `,
)
data.value.push('d')
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `"<div><span>a</span><span>b</span><span>c</span><span>d</span><!--for--></div><div>4</div>"`,
+ `
+ "
+ <!--[--><div>
+ <!--[--><span>a</span><span>b</span><span>c</span><span>d</span><!--]-->
+ </div><div>4</div><!--]-->
+ "
+ `,
)
})
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><span>a</span><span>b</span><span>c</span><!--for--><!--]]-->
+ <!--[--><span>a</span><span>b</span><span>c</span><!--]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><span>a</span><span>b</span><span>c</span><span>d</span><!--for--><!--]]-->
+ <!--[--><span>a</span><span>b</span><span>c</span><span>d</span><!--]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><span>b</span><span>c</span><span>d</span><!--for--><!--]]-->
+ <!--[--><span>b</span><span>c</span><span>d</span><!--]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><span>a</span><span>b</span><span>c</span><!--for--><!--]]-->
- <!--[[--><span>a</span><span>b</span><span>c</span><!--for--><!--]]-->
+ <!--[--><span>a</span><span>b</span><span>c</span><!--]-->
+ <!--[--><span>a</span><span>b</span><span>c</span><!--]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><span>a</span><span>b</span><span>c</span><span>d</span><!--for--><!--]]-->
- <!--[[--><span>a</span><span>b</span><span>c</span><span>d</span><!--for--><!--]]-->
+ <!--[--><span>a</span><span>b</span><span>c</span><span>d</span><!--]-->
+ <!--[--><span>a</span><span>b</span><span>c</span><span>d</span><!--]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[--><span>c</span><span>d</span><!--for--><!--]]-->
- <!--[[--><span>c</span><span>d</span><!--for--><!--]]-->
+ <!--[--><span>c</span><span>d</span><!--]-->
+ <!--[--><span>c</span><span>d</span><!--]-->
<span></span></div>"
`,
)
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `"<div><div>comp</div><div>comp</div><div>comp</div><!--for--></div>"`,
+ `
+ "<div>
+ <!--[--><div>comp</div><div>comp</div><div>comp</div><!--]-->
+ </div>"
+ `,
)
data.value.push('d')
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `"<div><div>comp</div><div>comp</div><div>comp</div><div>comp</div><!--for--></div>"`,
+ `
+ "<div>
+ <!--[--><div>comp</div><div>comp</div><div>comp</div><div>comp</div><!--]-->
+ </div>"
+ `,
)
})
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
+ <!--[-->
<!--[--><span>a</span><!--]-->
- <!--slot-->
<!--[--><span>b</span><!--]-->
- <!--slot-->
<!--[--><span>c</span><!--]-->
- <!--slot--><!--for--></div>"
+ <!--]-->
+ </div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
+ <!--[-->
<!--[--><span>a</span><!--]-->
- <!--slot-->
<!--[--><span>b</span><!--]-->
- <!--slot-->
- <!--[--><span>c</span><!--]-->
- <!--slot--><span>d</span><!--slot--><!--for--></div>"
+ <!--[--><span>c</span><span>d</span><!--slot--><!--]-->
+ <!--]-->
+ </div>"
`,
)
})
ref(['a', 'b', 'c']),
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `"<div><div>foo</div>-bar-<div>foo</div>-bar-<div>foo</div>-bar-<!--for--></div>"`,
+ `
+ "<div>
+ <!--[-->
+ <!--[--><div>foo</div>-bar-<!--]-->
+ <!--[--><div>foo</div>-bar-<!--]-->
+ <!--[--><div>foo</div>-bar-<!--]-->
+ <!--]-->
+ </div>"
+ `,
)
data.value.push('d')
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
- `"<div><div>foo</div>-bar-<div>foo</div>-bar-<div>foo</div>-bar-<div>foo</div>-bar-<!--for--></div>"`,
+ `
+ "<div>
+ <!--[-->
+ <!--[--><div>foo</div>-bar-<!--]-->
+ <!--[--><div>foo</div>-bar-<!--]-->
+ <!--[--><div>foo</div>-bar-<div>foo</div>-bar-<!--]-->
+ <!--]-->
+ </div>"
+ `,
)
})
})
`
"
<!--[--><span>foo</span><!--]-->
- <!--slot-->"
+ "
`,
)
`
"
<!--[--><span>bar</span><!--]-->
- <!--slot-->"
+ "
`,
)
})
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"
+ <!--[-->
<!--[--><!--]-->
- <!--slot-->
<!--[--><span>foo</span><!--]-->
- <!--slot-->"
+ <!--]-->
+ "
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"
+ <!--[-->
<!--[--><!--]-->
- <!--slot-->
<!--[--><span>bar</span><!--]-->
- <!--slot-->"
+ <!--]-->
+ "
`,
)
})
`
"
<!--[--><span>foo</span><!--]-->
- <!--slot-->"
+ "
`,
)
`
"
<!--[--><!--]-->
- <!--slot-->"
+ "
`,
)
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(`
"
- <!--[--><!--]-->
- <span>true</span><!--slot-->"
+ <!--[--><span>true</span><!--]-->
+ "
`)
})
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"
- <!--[--><span>a</span><span>b</span><span>c</span><!--for--><!--]-->
- <!--slot-->"
+ <!--[-->
+ <!--[--><span>a</span><span>b</span><span>c</span><!--]-->
+ <!--]-->
+ "
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"
+ <!--[-->
<!--[--><!--]-->
- <!--slot-->"
+ "
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"
- <!--[--><!--]-->
- <span>a</span><span>b</span><span>c</span><!--for--><!--slot-->"
+ <!--[-->
+ <!--[--><span>a</span><span>b</span><span>c</span><!--for--><!--]-->
+ "
`,
)
})
`
"
<!--[--><span></span><span>foo</span><span></span><!--]-->
- <!--slot-->"
+ "
`,
)
`
"
<!--[--><span></span><span>bar</span><span></span><!--]-->
- <!--slot-->"
+ "
`,
)
})
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
- "<div></div><div></div>
+ "
+ <!--[--><div></div><div></div>
<!--[--><span></span><span>foo</span><span></span><!--]-->
- <!--slot--><div></div>"
+ <div></div><!--]-->
+ "
`,
)
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
- "<div></div><div></div>
+ "
+ <!--[--><div></div><div></div>
<!--[--><span></span><span>bar</span><span></span><!--]-->
- <!--slot--><div></div>"
+ <div></div><!--]-->
+ "
`,
)
})
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
- <!--[[-->
<!--[--><span>foo</span><!--]-->
- <!--slot--><!--]]-->
hi</div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
- <!--[[-->
<!--[--><span>foo</span><!--]-->
- <!--slot--><!--]]-->
bar</div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
- "foo
+ "
+ <!--[-->foo
<!--[--><span>foo</span><!--]-->
- <!--slot-->hi"
+ hi<!--]-->
+ "
`,
)
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
- "foo
+ "
+ <!--[-->foo
<!--[--><span>foo</span><!--]-->
- <!--slot-->bar"
+ bar<!--]-->
+ "
`,
)
})
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
- <!--[[-->
<!--[--><span>foo</span><!--]-->
- <!--slot--><!--]]-->
- <!--[[-->
<!--[--><span>bar</span><!--]-->
- <!--slot--><!--]]-->
<div>hi</div></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
- <!--[[-->
<!--[--><span>foo</span><!--]-->
- <!--slot--><!--]]-->
- <!--[[-->
<!--[--><span>bar</span><!--]-->
- <!--slot--><!--]]-->
<div>bar</div></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
- <!--[[-->
<!--[--><span>foo</span><!--]-->
- <!--slot--><!--]]-->
<div>hi</div></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
- <!--[[-->
<!--[--><span>foo</span><!--]-->
- <!--slot--><!--]]-->
<div>bar</div></div>"
`,
)
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
- "<div>
- <!--[[--><div>bar</div><!--]]-->
- <!--[[-->
+ "<div><div>bar</div>
<!--[--><span>foo</span><!--]-->
- <!--slot--><!--]]-->
- <!--[[--><div>bar</div><!--]]-->
- </div>"
+ <div>bar</div></div>"
`,
)
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
- "<div>
- <!--[[--><div>hello</div><!--]]-->
- <!--[[-->
+ "<div><div>hello</div>
<!--[--><span>foo</span><!--]-->
- <!--slot--><!--]]-->
- <!--[[--><div>hello</div><!--]]-->
- </div>"
+ <div>hello</div></div>"
`,
)
})
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
- <!--[[--><div>foo</div> bar<!--]]-->
- <!--[[-->
+ <!--[--><div>foo</div> bar<!--]-->
<!--[--><span>foo</span><!--]-->
- <!--slot--><!--]]-->
- <!--[[--><div>foo</div> bar<!--]]-->
+ <!--[--><div>foo</div> bar<!--]-->
</div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
- <!--[[--><div>hello</div> vapor<!--]]-->
- <!--[[-->
+ <!--[--><div>hello</div> vapor<!--]-->
<!--[--><span>hello</span><!--]-->
- <!--slot--><!--]]-->
- <!--[[--><div>hello</div> vapor<!--]]-->
+ <!--[--><div>hello</div> vapor<!--]-->
</div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
- "<div>foo</div><!--if-->
+ "
+ <!--[--><div>foo</div><!--if-->
<!--[--><span>foo</span><!--]-->
- <!--slot--><div>foo</div><!--if-->"
+ <div>foo</div><!--if--><!--]-->
+ "
`,
)
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
- "<!--if-->
+ "
+ <!--[--><!--if-->
<!--[--><span>foo</span><!--]-->
- <!--slot--><!--if-->"
+ <!--if--><!--]-->
+ "
`,
)
data.show = true
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(`
- "<div>foo</div><!--if-->
+ "
+ <!--[--><div>foo</div><!--if-->
<!--[--><span>foo</span><!--]-->
- <!--slot--><div>foo</div><!--if-->"
+ <div>foo</div><!--if--><!--]-->
+ "
`)
})
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
- "<div>a</div><div>b</div><div>c</div><!--for-->
+ "
+ <!--[-->
+ <!--[--><div>a</div><div>b</div><div>c</div><!--]-->
<!--[--><span>foo</span><!--]-->
- <!--slot--><div>a</div><div>b</div><div>c</div><!--for-->"
+ <!--[--><div>a</div><div>b</div><div>c</div><!--]-->
+ <!--]-->
+ "
`,
)
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
- "<div>a</div><div>b</div><div>c</div><div>d</div><!--for-->
+ "
+ <!--[-->
+ <!--[--><div>a</div><div>b</div><div>c</div><div>d</div><!--]-->
<!--[--><span>foo</span><!--]-->
- <!--slot--><div>a</div><div>b</div><div>c</div><div>d</div><!--for-->"
+ <!--[--><div>a</div><div>b</div><div>c</div><div>d</div><!--]-->
+ <!--]-->
+ "
`,
)
})
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"
+ <!--[-->
<!--[--><span>foo</span><!--]-->
- <!--slot-->
<!--[--><span>bar</span><!--]-->
- <!--slot-->"
+ <!--]-->
+ "
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"
+ <!--[-->
<!--[--><span>hello</span><!--]-->
- <!--slot-->
<!--[--><span>vapor</span><!--]-->
- <!--slot-->"
+ <!--]-->
+ "
`,
)
})
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[-->
<!--[--><span>foo</span><!--]-->
- <!--slot--><!--]]-->
- <!--[[-->
<!--[--><span>bar</span><!--]-->
- <!--slot--><!--]]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div><span></span>
- <!--[[-->
<!--[--><span>hello</span><!--]-->
- <!--slot--><!--]]-->
- <!--[[-->
<!--[--><span>vapor</span><!--]-->
- <!--slot--><!--]]-->
<span></span></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
- <!--[[-->
<!--[--><span>foo</span><!--]-->
- <!--slot--><!--]]-->
- <!--[[-->
<!--[--><span>bar</span><!--]-->
- <!--slot--><!--]]-->
<div>baz</div></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
- <!--[[-->
<!--[--><span>hello</span><!--]-->
- <!--slot--><!--]]-->
- <!--[[-->
<!--[--><span>vapor</span><!--]-->
- <!--slot--><!--]]-->
<div>baz</div></div>"
`,
)
`
"
<!--[--><span>foo</span><!--]-->
- <!--slot-->"
+ "
`,
)
`
"
<!--[--><span>bar</span><!--]-->
- <!--slot-->"
+ "
`,
)
})
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
- "<div>
- <!--[[--><div><div>
+ "<div><div><div>
<!--[-->
<!--[--><span>foo</span><!--]-->
- <!--slot--><!--]-->
- <!--slot--></div></div><!--]]-->
- <div>bar</div></div>"
+ <!--]-->
+ </div></div><div>bar</div></div>"
`,
)
await nextTick()
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
- "<div>
- <!--[[--><div><div>
+ "<div><div><div>
<!--[-->
<!--[--><span>foo1</span><!--]-->
- <!--slot--><!--]-->
- <!--slot--></div></div><!--]]-->
- <div>bar1</div></div>"
+ <!--]-->
+ </div></div><div>bar1</div></div>"
`,
)
})
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
- <!--[--><!--slot-->foo<!--]-->
- <!--slot--></div>"
+ <!--[-->foo<!--]-->
+ </div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
- <!--[--><!--slot-->foo1<!--]-->
- <!--slot--></div>"
+ <!--[-->foo1<!--]-->
+ </div>"
`,
)
})
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
- <!--[[-->
- <!--[--><!--slot--><!--slot--><!--slot--><!--]-->
- <!--slot--><!--]]-->
+ <!--[--><!--]-->
<div>foo</div></div>"
`,
)
expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
`
"<div>
- <!--[[-->
- <!--[--><!--slot--><!--slot--><!--slot--><!--]-->
- <!--slot--><!--]]-->
+ <!--[--><!--]-->
<div>bar</div></div>"
`,
)
`
"
<!--[--><span>true vapor fallback</span><!--]-->
- <!--slot-->"
+ "
`,
)
`
"
<!--[--><span>false vapor fallback</span><!--]-->
- <!--slot-->"
+ "
`,
)
})
`
"<div>
<!--[-->true<!--]-->
- <!--slot--></div>"
+ </div>"
`,
)
`
"<div>
<!--[-->false<!--]-->
- <!--slot--></div>"
+ </div>"
`,
)
})
`
"<div>
<!--[--><div><div>true</div></div><!--]-->
- <!--slot--></div>"
+ </div>"
`,
)
`
"<div>
<!--[--><div><div>false</div></div><!--]-->
- <!--slot--></div>"
+ </div>"
`,
)
})
insertionParent,
resetInsertionState,
} from './insertionState'
-import { DYNAMIC_COMPONENT_ANCHOR_LABEL } from '@vue/shared'
import { advanceHydrationNode, isHydrating } from './dom/hydration'
import { DynamicFragment, type VaporFragment } from './fragment'
const frag =
isHydrating || __DEV__
- ? new DynamicFragment(DYNAMIC_COMPONENT_ANCHOR_LABEL)
+ ? new DynamicFragment('dynamic-component')
: new DynamicFragment()
renderEffect(() => {
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,
import { VaporVForFlags } from '../../shared/src/vaporFlags'
import {
advanceHydrationNode,
- currentHydrationNode,
isHydrating,
- locateFragmentAnchor,
+ locateFragmentEndAnchor,
locateHydrationNode,
} from './dom/hydration'
import { ForFragment, VaporFragment } from './fragment'
}
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.`)
}
-import { IF_ANCHOR_LABEL } from '@vue/shared'
import { type Block, type BlockFn, insert } from './block'
import { advanceHydrationNode, isHydrating } from './dom/hydration'
import {
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))
}
type VaporFragment,
isFragment,
} from './fragment'
+import { child } from './dom/node'
export interface TransitionOptions {
$key?: any
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
-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'
} else {
fragment =
isHydrating || __DEV__
- ? new DynamicFragment(SLOT_ANCHOR_LABEL)
+ ? new DynamicFragment('slot')
: new DynamicFragment()
const isDynamicName = isFunction(name)
const renderSlot = () => {
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
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()
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
}
}
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
-}
-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!
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!
}
impl: T
}
-/*! #__NO_SIDE_EFFECTS__ */
+/* @__NO_SIDE_EFFECTS__ */
export const txt: DelegatedFunction<typeof _txt> = node => {
return txt.impl(node)
}
txt.impl = _child
-/*! #__NO_SIDE_EFFECTS__ */
+/* @__NO_SIDE_EFFECTS__ */
export const child: DelegatedFunction<typeof _child> = node => {
return child.impl(node)
}
child.impl = _child
-/*! #__NO_SIDE_EFFECTS__ */
+/* @__NO_SIDE_EFFECTS__ */
export const next: DelegatedFunction<typeof _next> = node => {
return next.impl(node)
}
next.impl = _next
-/*! #__NO_SIDE_EFFECTS__ */
+/* @__NO_SIDE_EFFECTS__ */
export const nthChild: DelegatedFunction<typeof _nthChild> = (node, i) => {
return nthChild.impl(node, i)
}
if (root) (adopted as any).$root = true
return adopted
}
+
// fast path for text nodes
if (html[0] !== '<') {
return createTextNode(html)
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<T extends Block = Block>
implements TransitionOptions
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
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)
}
}
? 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!)
+ }
+}
-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<Node, number> | null
+ appendAnchor: Node | null
+}
+export let insertionParent: ParentNode | undefined
export let insertionAnchor: Node | 0 | undefined | null
+const templateChildrenCache = new WeakMap<ParentNode, ChildItem[]>()
+
+const hydrationStateCache = new WeakMap<ParentNode, HydrationState>()
+
/**
* This function is called before a block type that requires insertion
* (component, slot outlet, if, for) is created. The state is used for actual
*/
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)
+}
currentHydrationNode,
isComment,
isHydrating,
- locateFragmentAnchor,
+ locateFragmentEndAnchor,
locateHydrationNode,
setCurrentHydrationNode,
hydrateNode as vaporHydrateNode,
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 (<!--slot-->) 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)
},
}
type SSRBufferItem,
renderVNodeChildren,
} from '../render'
-import { isArray, isString } from '@vue/shared'
+import { isArray } from '@vue/shared'
const { ensureValidVNode } = ssrUtils
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 === '<!--slot-->'
- ) {
- push(buffer)
- continue
- }
-
- if (!isComment(buffer)) {
+ if (!isComment(slotBuffer[i])) {
isEmptySlot = false
break
}
push(escapeHtml(children as string))
break
case Comment:
- if (children) {
- const content = children as string
- // avoid escaping comments
- if (content.startsWith('<!--') && content.endsWith('-->')) {
- push(content)
- } else {
- push(`<!--${escapeHtmlComment(content)}-->`)
- }
- } else {
- push(`<!---->`)
- }
+ push(
+ children
+ ? `<!--${escapeHtmlComment(children as string)}-->`
+ : `<!---->`,
+ )
break
case Static:
push(children as string)
+++ /dev/null
-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}`
-}
export * from './toDisplayString'
export * from './typeUtils'
export * from './subSequence'
-export * from './domAnchors'
export * from './cssVars'