diff --git a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts
index 672af193e92..2f279c090d2 100644
--- a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts
+++ b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts
@@ -280,46 +280,92 @@ describe('ssr: components', () => {
`)
})
- test('built-in fallthroughs', () => {
- expect(compile(``).code)
- .toMatchInlineSnapshot(`
- "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
+ describe('built-in fallthroughs', () => {
+ test('transition', () => {
+ expect(compile(``).code)
+ .toMatchInlineSnapshot(`
+ "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent, _attrs) {
+ _push(\`
\`)
+ }"
+ `)
+ })
- return function ssrRender(_ctx, _push, _parent, _attrs) {
- _push(\`\`)
- }"
- `)
+ test('keep-alive', () => {
+ expect(compile(``).code)
+ .toMatchInlineSnapshot(`
+ "const { resolveComponent: _resolveComponent } = require(\\"vue\\")
+ const { ssrRenderComponent: _ssrRenderComponent } = require(\\"vue/server-renderer\\")
- // should inject attrs if root with coomments
- expect(compile(``).code)
- .toMatchInlineSnapshot(`
- "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
+ return function ssrRender(_ctx, _push, _parent, _attrs) {
+ const _component_foo = _resolveComponent(\\"foo\\")
- return function ssrRender(_ctx, _push, _parent, _attrs) {
- _push(\`\`)
- }"
- `)
+ _push(_ssrRenderComponent(_component_foo, _attrs, null, _parent))
+ }"
+ `)
+ })
- // should not inject attrs if not root
- expect(compile(``).code)
- .toMatchInlineSnapshot(`
- "
- return function ssrRender(_ctx, _push, _parent, _attrs) {
- _push(\`\`)
- }"
- `)
+ test('should inject attrs if root with coomments', () => {
+ expect(compile(``).code)
+ .toMatchInlineSnapshot(`
+ "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
- expect(compile(``).code)
- .toMatchInlineSnapshot(`
- "const { resolveComponent: _resolveComponent } = require(\\"vue\\")
- const { ssrRenderComponent: _ssrRenderComponent } = require(\\"vue/server-renderer\\")
+ return function ssrRender(_ctx, _push, _parent, _attrs) {
+ _push(\`\`)
+ }"
+ `)
+ })
- return function ssrRender(_ctx, _push, _parent, _attrs) {
- const _component_foo = _resolveComponent(\\"foo\\")
+ test('should not inject attrs if not root', () => {
+ expect(compile(``).code)
+ .toMatchInlineSnapshot(`
+ "
+ return function ssrRender(_ctx, _push, _parent, _attrs) {
+ _push(\`\`)
+ }"
+ `)
+ })
- _push(_ssrRenderComponent(_component_foo, _attrs, null, _parent))
- }"
- `)
+ // #5352
+ test('should push marker string if is slot root', () => {
+ expect(
+ compile(``)
+ .code
+ ).toMatchInlineSnapshot(`
+ "const { resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode, Transition: _Transition, createVNode: _createVNode } = require(\\"vue\\")
+ const { ssrRenderComponent: _ssrRenderComponent } = require(\\"vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent, _attrs) {
+ const _component_foo = _resolveComponent(\\"foo\\")
+
+ _push(_ssrRenderComponent(_component_foo, _attrs, {
+ default: _withCtx((_, _push, _parent, _scopeId) => {
+ if (_push) {
+ _push(\`\`)
+ if (false) {
+ _push(\`\`)
+ } else {
+ _push(\`\`)
+ }
+ } else {
+ return [
+ _createVNode(_Transition, null, {
+ default: _withCtx(() => [
+ false
+ ? (_openBlock(), _createBlock(\\"div\\", { key: 0 }))
+ : _createCommentVNode(\\"v-if\\", true)
+ ]),
+ _: 1 /* STABLE */
+ })
+ ]
+ }
+ }),
+ _: 1 /* STABLE */
+ }, _parent))
+ }"
+ `)
+ })
})
// transition-group should flatten and concat its children fragments into
diff --git a/packages/compiler-ssr/src/ssrCodegenTransform.ts b/packages/compiler-ssr/src/ssrCodegenTransform.ts
index c30b90aa131..a7f3ada15b4 100644
--- a/packages/compiler-ssr/src/ssrCodegenTransform.ts
+++ b/packages/compiler-ssr/src/ssrCodegenTransform.ts
@@ -51,7 +51,7 @@ export function ssrCodegenTransform(ast: RootNode, options: CompilerOptions) {
const isFragment =
ast.children.length > 1 && ast.children.some(c => !isText(c))
- processChildren(ast.children, context, isFragment)
+ processChildren(ast, context, isFragment)
ast.codegenNode = createBlockStatement(context.body)
// Finalize helpers.
@@ -125,8 +125,12 @@ function createChildContext(
)
}
+interface Container {
+ children: TemplateChildNode[]
+}
+
export function processChildren(
- children: TemplateChildNode[],
+ parent: Container,
context: SSRTransformContext,
asFragment = false,
disableNestedFragments = false
@@ -134,6 +138,7 @@ export function processChildren(
if (asFragment) {
context.pushStringPart(``)
}
+ const { children } = parent
for (let i = 0; i < children.length; i++) {
const child = children[i]
switch (child.type) {
@@ -143,7 +148,7 @@ export function processChildren(
ssrProcessElement(child, context)
break
case ElementTypes.COMPONENT:
- ssrProcessComponent(child, context)
+ ssrProcessComponent(child, context, parent)
break
case ElementTypes.SLOT:
ssrProcessSlotOutlet(child, context)
@@ -208,12 +213,12 @@ export function processChildren(
}
export function processChildrenAsStatement(
- children: TemplateChildNode[],
+ parent: Container,
parentContext: SSRTransformContext,
asFragment = false,
withSlotScopeId = parentContext.withSlotScopeId
): BlockStatement {
const childContext = createChildContext(parentContext, withSlotScopeId)
- processChildren(children, childContext, asFragment)
+ processChildren(parent, childContext, asFragment)
return createBlockStatement(childContext.body)
}
diff --git a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
index dfb78136a9d..83d552103ca 100644
--- a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
+++ b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
@@ -58,7 +58,10 @@ import { buildSSRProps } from './ssrTransformElement'
// pass and complete them in the 2nd pass.
const wipMap = new WeakMap()
+const WIP_SLOT = Symbol()
+
interface WIPSlotEntry {
+ type: typeof WIP_SLOT
fn: FunctionExpression
children: TemplateChildNode[]
vnodeBranch: ReturnStatement
@@ -143,6 +146,7 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
loc
)
wipEntries.push({
+ type: WIP_SLOT,
fn,
children,
// also collect the corresponding vnode branch built earlier
@@ -182,7 +186,8 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
export function ssrProcessComponent(
node: ComponentNode,
- context: SSRTransformContext
+ context: SSRTransformContext,
+ parent: { children: TemplateChildNode[] }
) {
const component = componentTypeMap.get(node)!
if (!node.ssrCodegenNode) {
@@ -196,13 +201,19 @@ export function ssrProcessComponent(
} else {
// real fall-through: Transition / KeepAlive
// just render its children.
- processChildren(node.children, context)
+ // #5352: if is at root level of a slot, push an empty string.
+ // this does not affect the final output, but avoids all-comment slot
+ // content of being treated as empty by ssrRenderSlot().
+ if ((parent as WIPSlotEntry).type === WIP_SLOT) {
+ context.pushStringPart(``)
+ }
+ processChildren(node, context)
}
} else {
// finish up slot function expressions from the 1st pass.
const wipEntries = wipMap.get(node) || []
for (let i = 0; i < wipEntries.length; i++) {
- const { fn, children, vnodeBranch } = wipEntries[i]
+ const { fn, vnodeBranch } = wipEntries[i]
// For each slot, we generate two branches: one SSR-optimized branch and
// one normal vnode-based branch. The branches are taken based on the
// presence of the 2nd `_push` argument (which is only present if the slot
@@ -210,7 +221,7 @@ export function ssrProcessComponent(
fn.body = createIfStatement(
createSimpleExpression(`_push`, false),
processChildrenAsStatement(
- children,
+ wipEntries[i],
context,
false,
true /* withSlotScopeId */
diff --git a/packages/compiler-ssr/src/transforms/ssrTransformElement.ts b/packages/compiler-ssr/src/transforms/ssrTransformElement.ts
index a082d94eb57..2c42a83ba37 100644
--- a/packages/compiler-ssr/src/transforms/ssrTransformElement.ts
+++ b/packages/compiler-ssr/src/transforms/ssrTransformElement.ts
@@ -428,7 +428,7 @@ export function ssrProcessElement(
if (rawChildren) {
context.pushStringPart(rawChildren)
} else if (node.children.length) {
- processChildren(node.children, context)
+ processChildren(node, context)
}
if (!isVoidTag(node.tag)) {
diff --git a/packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts b/packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts
index 21c33831ccc..3486f355102 100644
--- a/packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts
+++ b/packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts
@@ -65,7 +65,7 @@ export function ssrProcessSlotOutlet(
// has fallback content
if (node.children.length) {
const fallbackRenderFn = createFunctionExpression([])
- fallbackRenderFn.body = processChildrenAsStatement(node.children, context)
+ fallbackRenderFn.body = processChildrenAsStatement(node, context)
// _renderSlot(slots, name, props, fallback, ...)
renderCall.arguments[3] = fallbackRenderFn
}
diff --git a/packages/compiler-ssr/src/transforms/ssrTransformSuspense.ts b/packages/compiler-ssr/src/transforms/ssrTransformSuspense.ts
index 33543326922..207e9348eef 100644
--- a/packages/compiler-ssr/src/transforms/ssrTransformSuspense.ts
+++ b/packages/compiler-ssr/src/transforms/ssrTransformSuspense.ts
@@ -66,8 +66,8 @@ export function ssrProcessSuspense(
}
const { slotsExp, wipSlots } = wipEntry
for (let i = 0; i < wipSlots.length; i++) {
- const { fn, children } = wipSlots[i]
- fn.body = processChildrenAsStatement(children, context)
+ const slot = wipSlots[i]
+ slot.fn.body = processChildrenAsStatement(slot, context)
}
// _push(ssrRenderSuspense(slots))
context.pushStatement(
diff --git a/packages/compiler-ssr/src/transforms/ssrTransformTeleport.ts b/packages/compiler-ssr/src/transforms/ssrTransformTeleport.ts
index d9cfdbb0274..f470ca711d4 100644
--- a/packages/compiler-ssr/src/transforms/ssrTransformTeleport.ts
+++ b/packages/compiler-ssr/src/transforms/ssrTransformTeleport.ts
@@ -58,7 +58,7 @@ export function ssrProcessTeleport(
false, // isSlot
node.loc
)
- contentRenderFn.body = processChildrenAsStatement(node.children, context)
+ contentRenderFn.body = processChildrenAsStatement(node, context)
context.pushStatement(
createCallExpression(context.helper(SSR_RENDER_TELEPORT), [
`_push`,
diff --git a/packages/compiler-ssr/src/transforms/ssrTransformTransitionGroup.ts b/packages/compiler-ssr/src/transforms/ssrTransformTransitionGroup.ts
index 80a26c98a49..378c4f333d4 100644
--- a/packages/compiler-ssr/src/transforms/ssrTransformTransitionGroup.ts
+++ b/packages/compiler-ssr/src/transforms/ssrTransformTransitionGroup.ts
@@ -14,7 +14,7 @@ export function ssrProcessTransitionGroup(
context.pushStringPart(`>`)
processChildren(
- node.children,
+ node,
context,
false,
/**
@@ -31,11 +31,11 @@ export function ssrProcessTransitionGroup(
} else {
// static tag
context.pushStringPart(`<${tag.value!.content}>`)
- processChildren(node.children, context, false, true)
+ processChildren(node, context, false, true)
context.pushStringPart(`${tag.value!.content}>`)
}
} else {
// fragment
- processChildren(node.children, context, true, true)
+ processChildren(node, context, true, true)
}
}
diff --git a/packages/compiler-ssr/src/transforms/ssrVFor.ts b/packages/compiler-ssr/src/transforms/ssrVFor.ts
index 583873b66ff..0515993d475 100644
--- a/packages/compiler-ssr/src/transforms/ssrVFor.ts
+++ b/packages/compiler-ssr/src/transforms/ssrVFor.ts
@@ -33,7 +33,7 @@ export function ssrProcessFor(
createForLoopParams(node.parseResult)
)
renderLoop.body = processChildrenAsStatement(
- node.children,
+ node,
context,
needFragmentWrapper
)
diff --git a/packages/compiler-ssr/src/transforms/ssrVIf.ts b/packages/compiler-ssr/src/transforms/ssrVIf.ts
index 57f77eafd30..9de1d0e9a2d 100644
--- a/packages/compiler-ssr/src/transforms/ssrVIf.ts
+++ b/packages/compiler-ssr/src/transforms/ssrVIf.ts
@@ -72,5 +72,5 @@ function processIfBranch(
(children.length !== 1 || children[0].type !== NodeTypes.ELEMENT) &&
// optimize away nested fragments when the only child is a ForNode
!(children.length === 1 && children[0].type === NodeTypes.FOR)
- return processChildrenAsStatement(children, context, needFragmentWrapper)
+ return processChildrenAsStatement(branch, context, needFragmentWrapper)
}
diff --git a/packages/server-renderer/src/helpers/ssrRenderSlot.ts b/packages/server-renderer/src/helpers/ssrRenderSlot.ts
index e421578d7af..9234e517325 100644
--- a/packages/server-renderer/src/helpers/ssrRenderSlot.ts
+++ b/packages/server-renderer/src/helpers/ssrRenderSlot.ts
@@ -84,5 +84,9 @@ export function ssrRenderSlotInner(
const commentRE = //g
function isComment(item: SSRBufferItem) {
- return typeof item === 'string' && !item.replace(commentRE, '').trim()
+ return (
+ typeof item === 'string' &&
+ commentRE.test(item) &&
+ !item.replace(commentRE, '').trim()
+ )
}