expect(
getCompiledString(
`<component :is="'div'">
- <div><Comp/><Comp/><span/></div>
+ <div>
+ <Comp/><Comp/><span/>
+ </div>
</component>`,
{ vapor: true },
),
expect(
getCompiledString(
`<component :is="'div'">
- <div><slot name="foo"/><slot/><span/></div>
+ <div>
+ <slot name="foo"/>
+ <slot/>
+ <span/>
+ </div>
</component>`,
{ vapor: true },
),
expect(
getCompiledString(
`<component :is="'div'">
- <div>
- <span v-if="foo"/>
- <span v-else-if="bar"/>
- <span v-else/>
- <span/>
- </div>
- </component>`,
+ <div>
+ <span v-if="foo"/>
+ <span v-else-if="bar"/>
+ <span v-else/>
+ <span/>
+ </div>
+ </component>`,
{ vapor: true },
),
).toMatchInlineSnapshot(`
expect(
getCompiledString(
`<component :is="'div'">
- <div>
+ <div>
<span v-if="foo">
<span v-if="foo1" />
<span />
expect(
getCompiledString(
`<component :is="'div'">
- <div><span v-for="item in items"/><span/></div>
+ <div>
+ <span v-for="item in items"/><span/>
+ </div>
</component>`,
{ vapor: true },
),
test('mixed anchors in ssr slot vnode fallback', () => {
expect(
getCompiledString(
- `<component :is="'div'"><Comp/><span/><Comp/><span/><Comp/></component>`,
+ `<component :is="'div'">
+ <div>
+ <Comp/><span/>
+ <Comp/><span/>
+ <Comp/>
+ </div>
+ </component>`,
{
vapor: true,
},
_ssrRenderVNode(_push, _createVNode(_resolveDynamicComponent('div'), null, {
default: _withCtx((_, _push, _parent, _scopeId) => {
if (_push) {
+ _push(\`<div\${_scopeId}><!--[p-->\`)
_push(_ssrRenderComponent(_component_Comp, null, null, _parent, _scopeId))
- _push(\`<span\${_scopeId}></span>\`)
+ _push(\`<!--p]--><span\${_scopeId}></span><!--[i-->\`)
_push(_ssrRenderComponent(_component_Comp, null, null, _parent, _scopeId))
- _push(\`<span\${_scopeId}></span>\`)
+ _push(\`<!--i]--><span\${_scopeId}></span><!--[a-->\`)
_push(_ssrRenderComponent(_component_Comp, null, null, _parent, _scopeId))
+ _push(\`<!--a]--></div>\`)
} else {
return [
- _createCommentVNode("[p"),
- _createVNode(_component_Comp),
- _createCommentVNode("p]"),
- _createVNode("span"),
- _createCommentVNode("[i"),
- _createVNode(_component_Comp),
- _createCommentVNode("i]"),
- _createVNode("span"),
- _createCommentVNode("[a"),
- _createVNode(_component_Comp),
- _createCommentVNode("a]")
+ _createVNode("div", null, [
+ _createCommentVNode("[p"),
+ _createVNode(_component_Comp),
+ _createCommentVNode("p]"),
+ _createVNode("span"),
+ _createCommentVNode("[i"),
+ _createVNode(_component_Comp),
+ _createCommentVNode("i]"),
+ _createVNode("span"),
+ _createCommentVNode("[a"),
+ _createVNode(_component_Comp),
+ _createCommentVNode("a]")
+ ])
]
}
}),
Namespaces,
type NodeTransform,
NodeTypes,
+ type PlainElementNode,
RESOLVE_DYNAMIC_COMPONENT,
type ReturnStatement,
type RootNode,
if (clonedNode.children.length) {
buildSlots(clonedNode, context, (props, vFor, children) => {
vnodeBranches.push(
- createVNodeSlotBranch(props, vFor, children, context),
+ createVNodeSlotBranch(props, vFor, children, context, clonedNode),
)
return createFunctionExpression(undefined)
})
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 (parentContext.vapor) {
- children = injectVaporInsertionAnchors(children)
+ children = injectVaporInsertionAnchors(children, parent)
}
const wrapperNode: TemplateNode = {
function injectVaporInsertionAnchors(
children: TemplateChildNode[],
+ parent: TemplateChildNode,
): TemplateChildNode[] {
- processBlockNodeAnchor(children)
+ if (isElementWithChildren(parent)) {
+ processBlockNodeAnchor(children)
+ }
+
const newChildren: TemplateChildNode[] = new Array(children.length * 3)
let newIndex = 0
// copy branch nodes
for (let j = i; j <= lastBranchIndex; j++) {
- const node = children[j]
+ const node = children[j] as PlainElementNode
newChildren[newIndex++] = node
- if (isElementWithChildren(node)) {
- node.children = injectVaporInsertionAnchors(node.children)
- }
+ node.children = injectVaporInsertionAnchors(node.children, node)
}
// inject anchor after branch nodes
newChildren[newIndex++] = child
if (anchor) newChildren[newIndex++] = createAnchorComment(`${anchor}]`)
- if (isElementWithChildren(child)) {
- child.children = injectVaporInsertionAnchors(child.children)
- }
+ child.children = injectVaporInsertionAnchors(child.children, child)
}
newChildren.length = newIndex
)
})
+ test('forwarded slot with fallback', async () => {
+ const data = reactive({
+ foo: 'foo',
+ })
+ const { container } = await testHydration(
+ `<template>
+ <components.Parent/>
+ </template>`,
+ {
+ Parent: `<template><components.Child><slot/></components.Child></template>`,
+ Child: `<template><div><slot>{{data.foo}}</slot></div></template>`,
+ },
+ data,
+ )
+ expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
+ `
+ "<div>
+ <!--[a-->
+ <!--[--><!--slot-->foo<!--]-->
+ <!--slot--><!--a]-->
+ </div>"
+ `,
+ )
+
+ data.foo = 'foo1'
+ await nextTick()
+ expect(formatHtml(container.innerHTML)).toMatchInlineSnapshot(
+ `
+ "<div>
+ <!--[a-->
+ <!--[--><!--slot-->foo1<!--]-->
+ <!--slot--><!--a]-->
+ </div>"
+ `,
+ )
+ })
+
test('forwarded slot with empty content', async () => {
const data = reactive({
foo: 'foo',
`
"<div>
<!--[p-->
- <!--[--><!--]-->
- <!--slot--><!--slot--><!--slot--><!--slot--><!--p]-->
+ <!--[--><!--slot--><!--slot--><!--slot--><!--]-->
+ <!--slot--><!--p]-->
<div>foo</div></div>"
`,
)
`
"<div>
<!--[p-->
- <!--[--><!--]-->
- <!--slot--><!--slot--><!--slot--><!--slot--><!--p]-->
+ <!--[--><!--slot--><!--slot--><!--slot--><!--]-->
+ <!--slot--><!--p]-->
<div>bar</div></div>"
`,
)
}
hydrate = (label: string, isEmpty: boolean = false): void => {
+ // avoid repeated hydration during rendering fallback
+ if (this.anchor) return
+
const createAnchor = () => {
const { parentNode, nextSibling } = findLastChild(this.nodes)!
parentNode!.insertBefore(
// manually create anchors for:
// 1. else-if branch
- // 2. empty forwarded slot
// (not present in SSR output)
- if (
- label === ELSE_IF_ANCHOR_LABEL ||
- (this.nodes instanceof DynamicFragment &&
- this.nodes.forwarded &&
- !isValidBlock(this.nodes))
- ) {
+ if (label === ELSE_IF_ANCHOR_LABEL) {
createAnchor()
} else {
// for `v-if="false"`, the node will be an empty comment, use it as the anchor.
type SSRBufferItem,
renderVNodeChildren,
} from '../render'
-import { isArray } from '@vue/shared'
+import { isArray, isString } from '@vue/shared'
const { ensureValidVNode } = ssrUtils
isEmptySlot = false
} else {
for (let i = 0; i < slotBuffer.length; i++) {
- if (!isComment(slotBuffer[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)) {
isEmptySlot = false
break
}