-// import {
-// BLOCK_APPEND_ANCHOR_LABEL,
-// BLOCK_INSERTION_ANCHOR_LABEL,
-// BLOCK_PREPEND_ANCHOR_LABEL,
-// } from '@vue/shared'
import { getCompiledString } from './utils'
describe('insertion anchors', () => {
`)
})
- test('prepend anchor with v-if', () => {
+ test('prepend anchor with v-if/else-if/else', () => {
expect(
- getCompiledString('<div><span v-if="foo"/><span/></div>', {
- vapor: true,
- }),
+ getCompiledString(
+ `<div>
+ <span v-if="foo"/>
+ <span v-else-if="bar"/>
+ <span v-else/>
+ <span/>
+ </div>`,
+ {
+ vapor: true,
+ },
+ ),
).toMatchInlineSnapshot(`
"\`<div><!--[p-->\`)
if (_ctx.foo) {
_push(\`<span></span>\`)
_push(\`<!--if-->\`)
+ } else if (_ctx.bar) {
+ _push(\`<span></span>\`)
+ _push(\`<!--if-->\`)
} else {
- _push(\`<!---->\`)
+ _push(\`<span></span>\`)
+ _push(\`<!--if-->\`)
}
_push(\`<!--p]--><span></span></div>\`"
`)
})
- test('prepend anchor with v-if in ssr slot vnode fallback', () => {
+ 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/></div>
- </component>`,
+ <div>
+ <span v-if="foo"/>
+ <span v-else-if="bar"/>
+ <span v-else/>
+ <span/>
+ </div>
+ </component>`,
{ vapor: true },
),
).toMatchInlineSnapshot(`
if (_ctx.foo) {
_push(\`<span\${_scopeId}></span>\`)
_push(\`<!--if-->\`)
+ } else if (_ctx.bar) {
+ _push(\`<span\${_scopeId}></span>\`)
+ _push(\`<!--if-->\`)
} else {
- _push(\`<!---->\`)
+ _push(\`<span\${_scopeId}></span>\`)
+ _push(\`<!--if-->\`)
}
_push(\`<!--p]--><span\${_scopeId}></span></div>\`)
} else {
_createCommentVNode("[p"),
(_ctx.foo)
? (_openBlock(), _createBlock("span", { key: 0 }))
- : _createCommentVNode("v-if", true),
+ : (_ctx.bar)
+ ? (_openBlock(), _createBlock("span", { key: 1 }))
+ : (_openBlock(), _createBlock("span", { key: 2 })),
+ _createCommentVNode("p]"),
+ _createVNode("span")
+ ])
+ ]
+ }
+ }),
+ _: 1 /* STABLE */
+ }), _parent)
+ _push(\`<!--dynamic-component--><!--a]-->\`"
+ `)
+ })
+
+ 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><!--[p-->\`)
+ if (_ctx.foo) {
+ _push(\`<span><!--[p-->\`)
+ if (_ctx.foo1) {
+ _push(\`<span></span>\`)
+ _push(\`<!--if-->\`)
+ } else {
+ _push(\`<!---->\`)
+ }
+ _push(\`<!--p]--><span></span></span>\`)
+ _push(\`<!--if-->\`)
+ } else if (_ctx.bar) {
+ _push(\`<span><!--[p-->\`)
+ if (_ctx.bar1) {
+ _push(\`<span></span>\`)
+ _push(\`<!--if-->\`)
+ } else {
+ _push(\`<!---->\`)
+ }
+ _push(\`<!--p]--><span></span></span>\`)
+ _push(\`<!--if-->\`)
+ } else {
+ _push(\`<span><!--[p-->\`)
+ if (_ctx.bar2) {
+ _push(\`<span></span>\`)
+ _push(\`<!--if-->\`)
+ } else {
+ _push(\`<!---->\`)
+ }
+ _push(\`<!--p]--><span></span></span>\`)
+ _push(\`<!--if-->\`)
+ }
+ _push(\`<!--p]--><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(`
+ "\`<!--[a-->\`)
+ _ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent('div'), null, {
+ default: _withCtx((_, _push, _parent, _scopeId) => {
+ if (_push) {
+ _push(\`<div\${_scopeId}><!--[p-->\`)
+ if (_ctx.foo) {
+ _push(\`<span\${_scopeId}><!--[p-->\`)
+ if (_ctx.foo1) {
+ _push(\`<span\${_scopeId}></span>\`)
+ _push(\`<!--if-->\`)
+ } else {
+ _push(\`<!---->\`)
+ }
+ _push(\`<!--p]--><span\${_scopeId}></span></span>\`)
+ _push(\`<!--if-->\`)
+ } else if (_ctx.bar) {
+ _push(\`<span\${_scopeId}><!--[p-->\`)
+ if (_ctx.bar1) {
+ _push(\`<span\${_scopeId}></span>\`)
+ _push(\`<!--if-->\`)
+ } else {
+ _push(\`<!---->\`)
+ }
+ _push(\`<!--p]--><span\${_scopeId}></span></span>\`)
+ _push(\`<!--if-->\`)
+ } else {
+ _push(\`<span\${_scopeId}><!--[p-->\`)
+ if (_ctx.bar2) {
+ _push(\`<span\${_scopeId}></span>\`)
+ _push(\`<!--if-->\`)
+ } else {
+ _push(\`<!---->\`)
+ }
+ _push(\`<!--p]--><span\${_scopeId}></span></span>\`)
+ _push(\`<!--if-->\`)
+ }
+ _push(\`<!--p]--><span\${_scopeId}></span></div>\`)
+ } else {
+ return [
+ _createVNode("div", null, [
+ _createCommentVNode("[p"),
+ (_ctx.foo)
+ ? (_openBlock(), _createBlock("span", { key: 0 }, [
+ _createCommentVNode("[p"),
+ (_ctx.foo1)
+ ? (_openBlock(), _createBlock("span", { key: 0 }))
+ : _createCommentVNode("v-if", true),
+ _createCommentVNode("p]"),
+ _createVNode("span")
+ ]))
+ : (_ctx.bar)
+ ? (_openBlock(), _createBlock("span", { key: 1 }, [
+ _createCommentVNode("[p"),
+ (_ctx.bar1)
+ ? (_openBlock(), _createBlock("span", { key: 0 }))
+ : _createCommentVNode("v-if", true),
+ _createCommentVNode("p]"),
+ _createVNode("span")
+ ]))
+ : (_openBlock(), _createBlock("span", { key: 2 }, [
+ _createCommentVNode("[p"),
+ (_ctx.bar2)
+ ? (_openBlock(), _createBlock("span", { key: 0 }))
+ : _createCommentVNode("v-if", true),
+ _createCommentVNode("p]"),
+ _createVNode("span")
+ ])),
_createCommentVNode("p]"),
_createVNode("span")
])
import {
+ type AttributeNode,
type BlockStatement,
type CallExpression,
type CompilerError,
type CompilerOptions,
+ type DirectiveNode,
+ type ElementNode,
ElementTypes,
type IfStatement,
type JSChildNode,
context.pushStringPart(`<!--[-->`)
}
- const { children, type, tagType } = parent as PlainElementNode
+ const { children } = parent
if (
context.options.vapor &&
- type === NodeTypes.ELEMENT &&
- tagType === ElementTypes.ELEMENT
+ isElementWithChildren(parent as PlainElementNode)
) {
processBlockNodeAnchor(children)
}
}
}
+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.ELEMENT &&
(child.tagType === ElementTypes.COMPONENT ||
child.tagType === ElementTypes.SLOT ||
- child.props.some(
- p =>
- p.name === 'if' ||
- p.name === 'else-if' ||
- p.name === 'else' ||
- p.name === 'for',
- )))
+ hasBlockDir(child.props)))
)
}
child.type === NodeTypes.COMMENT ||
(child.type === NodeTypes.ELEMENT &&
child.tagType === ElementTypes.ELEMENT &&
- !child.props.some(
- p =>
- p.name === 'if' ||
- p.name === 'else-if' ||
- p.name === 'else' ||
- p.name === 'for',
- ))
+ !hasBlockDir(child.props))
+ )
+}
+
+export function isElementWithChildren(
+ node: TemplateChildNode,
+): node is ElementNode {
+ return (
+ node.type === NodeTypes.ELEMENT &&
+ node.tagType === ElementTypes.ELEMENT &&
+ node.children.length > 0
)
}
type DirectiveNode,
ElementTypes,
type ExpressionNode,
+ type ForNode,
type FunctionExpression,
+ type IfNode,
type JSChildNode,
Namespaces,
type NodeTransform,
import { SSR_RENDER_COMPONENT, SSR_RENDER_VNODE } from '../runtimeHelpers'
import {
type SSRTransformContext,
+ isElementWithChildren,
processBlockNodeAnchor,
processChildren,
processChildrenAsStatement,
if (vFor) {
wrapperProps.push(extend({}, vFor))
}
+
+ if (parentContext.vapor) {
+ children = injectVaporInsertionAnchors(children)
+ }
+
const wrapperNode: TemplateNode = {
type: NodeTypes.ELEMENT,
ns: Namespaces.HTML,
codegenNode: undefined,
}
- if (parentContext.vapor) {
- injectVaporInsertionAnchors(children)
- }
-
subTransform(wrapperNode, subOptions, parentContext)
return createReturnStatement(children)
}
// - hoists are not enabled for the client branch here
}
-function injectVaporInsertionAnchors(children: TemplateChildNode[]) {
+function injectVaporInsertionAnchors(
+ children: TemplateChildNode[],
+): TemplateChildNode[] {
processBlockNodeAnchor(children)
+ const newChildren: TemplateChildNode[] = new Array(children.length * 3)
+ let newIndex = 0
+
for (let i = 0; i < children.length; i++) {
const child = children[i]
- switch (child.type) {
- case NodeTypes.ELEMENT:
- switch (child.tagType) {
- case ElementTypes.COMPONENT:
- case ElementTypes.SLOT:
- if (child.anchor) {
- children.splice(i, 0, {
- type: NodeTypes.COMMENT,
- content: `[${child.anchor}`,
- loc: locStub,
- })
- children.splice(i + 2, 0, {
- type: NodeTypes.COMMENT,
- content: `${child.anchor}]`,
- loc: locStub,
- })
- i += 2
- }
- break
- default: {
- const { props } = child
- if (
- props.some(
- p =>
- p.name === 'if' ||
- p.name === 'else-if' ||
- p.name === 'else' ||
- p.name === 'for',
- )
- ) {
- // @ts-expect-error
- if (child.anchor) {
- children.splice(i, 0, {
- type: NodeTypes.COMMENT,
- // @ts-expect-error
- content: `[${child.anchor}`,
- loc: locStub,
- })
- children.splice(i + 2, 0, {
- type: NodeTypes.COMMENT,
- // @ts-expect-error
- content: `${child.anchor}]`,
- loc: locStub,
- })
+
+ if (child.type !== NodeTypes.ELEMENT) {
+ newChildren[newIndex++] = child
+ continue
+ }
+
+ const { tagType, props } = child
+ let anchor: string | undefined
+
+ if (tagType === ElementTypes.COMPONENT || tagType === ElementTypes.SLOT) {
+ anchor = child.anchor
+ } 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) {
+ anchor = (child as any as IfNode).anchor
+ if (anchor) {
+ // find sibling else-if/else branches
+ // inject anchor after else-if/else branch if founded
+ // otherwise inject after if node
+ const lastBranchIndex = findLastIfBranchIndex(children, i)
+ if (lastBranchIndex > i) {
+ // inject anchor before if node
+ newChildren[newIndex++] = createAnchorComment(`[${anchor}`)
+
+ // copy branch nodes
+ for (let j = i; j <= lastBranchIndex; j++) {
+ const node = children[j]
+ newChildren[newIndex++] = node
+
+ if (isElementWithChildren(node)) {
+ node.children = injectVaporInsertionAnchors(node.children)
}
- i += 2
- break
}
+
+ // inject anchor after branch nodes
+ newChildren[newIndex++] = createAnchorComment(`${anchor}]`)
+
+ i = lastBranchIndex
+ continue
}
}
+ } else if (hasFor) {
+ anchor = (child as any as ForNode).anchor
+ }
}
- if (
- child.type === NodeTypes.ELEMENT &&
- child.tagType === ElementTypes.ELEMENT
- ) {
- injectVaporInsertionAnchors(child.children)
+ // inject anchor before and after the child
+ if (anchor) newChildren[newIndex++] = createAnchorComment(`[${anchor}`)
+ newChildren[newIndex++] = child
+ if (anchor) newChildren[newIndex++] = createAnchorComment(`${anchor}]`)
+
+ if (isElementWithChildren(child)) {
+ child.children = injectVaporInsertionAnchors(child.children)
+ }
+ }
+
+ newChildren.length = newIndex
+ return newChildren
+}
+
+function createAnchorComment(content: string): TemplateChildNode {
+ 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 clone(v: any): any {