diff --git a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts
index b47c4f945..3edbb2e24 100644
--- a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts
+++ b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts
@@ -45,4 +45,121 @@ describe('ssr: components', () => {
}"
`)
})
+
+ describe('slots', () => {
+ test('implicit default slot', () => {
+ expect(compile(`hello`).code).toMatchInlineSnapshot(`
+ "const { resolveComponent } = require(\\"vue\\")
+ const { _renderComponent } = require(\\"@vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent) {
+ const _component_foo = resolveComponent(\\"foo\\")
+
+ _renderComponent(_component_foo, null, {
+ default: (_, _push, _parent) => {
+ _push(\`hello
\`)
+ },
+ _compiled: true
+ }, _parent)
+ }"
+ `)
+ })
+
+ test('explicit default slot', () => {
+ expect(compile(`{{ msg + outer }}`).code)
+ .toMatchInlineSnapshot(`
+ "const { resolveComponent } = require(\\"vue\\")
+ const { _renderComponent, _interpolate } = require(\\"@vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent) {
+ const _component_foo = resolveComponent(\\"foo\\")
+
+ _renderComponent(_component_foo, null, {
+ default: ({ msg }, _push, _parent) => {
+ _push(\`\${_interpolate(msg + _ctx.outer)}\`)
+ },
+ _compiled: true
+ }, _parent)
+ }"
+ `)
+ })
+
+ test('named slots', () => {
+ expect(
+ compile(`
+ foo
+ bar
+ `).code
+ ).toMatchInlineSnapshot(`
+ "const { resolveComponent } = require(\\"vue\\")
+ const { _renderComponent } = require(\\"@vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent) {
+ const _component_foo = resolveComponent(\\"foo\\")
+
+ _renderComponent(_component_foo, null, {
+ default: (_, _push, _parent) => {
+ _push(\`foo\`)
+ },
+ named: (_, _push, _parent) => {
+ _push(\`bar\`)
+ },
+ _compiled: true
+ }, _parent)
+ }"
+ `)
+ })
+
+ test('v-if slot', () => {
+ expect(
+ compile(`
+ foo
+ `).code
+ ).toMatchInlineSnapshot(`
+ "const { resolveComponent, createSlots } = require(\\"vue\\")
+ const { _renderComponent } = require(\\"@vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent) {
+ const _component_foo = resolveComponent(\\"foo\\")
+
+ _renderComponent(_component_foo, null, createSlots({ _compiled: true }, [
+ (_ctx.ok)
+ ? {
+ name: \\"named\\",
+ fn: (_, _push, _parent) => {
+ _push(\`foo\`)
+ }
+ }
+ : undefined
+ ]), _parent)
+ }"
+ `)
+ })
+
+ test('v-for slot', () => {
+ expect(
+ compile(`
+ {{ msg + key + bar }}
+ `).code
+ ).toMatchInlineSnapshot(`
+ "const { resolveComponent, renderList, createSlots } = require(\\"vue\\")
+ const { _renderComponent, _interpolate } = require(\\"@vue/server-renderer\\")
+
+ return function ssrRender(_ctx, _push, _parent) {
+ const _component_foo = resolveComponent(\\"foo\\")
+
+ _renderComponent(_component_foo, null, createSlots({ _compiled: true }, [
+ renderList(_ctx.names, (key) => {
+ return {
+ name: key,
+ fn: ({ msg }, _push, _parent) => {
+ _push(\`\${_interpolate(msg + key + _ctx.bar)}\`)
+ }
+ }
+ })
+ ]), _parent)
+ }"
+ `)
+ })
+ })
})
diff --git a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
index 034f1efd4..6916515d8 100644
--- a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
+++ b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
@@ -7,12 +7,33 @@ import {
buildProps,
ComponentNode,
PORTAL,
- SUSPENSE
+ SUSPENSE,
+ SlotFnBuilder,
+ createFunctionExpression,
+ createBlockStatement,
+ buildSlots,
+ FunctionExpression,
+ TemplateChildNode
} from '@vue/compiler-dom'
import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
-import { SSRTransformContext } from '../ssrCodegenTransform'
+import {
+ SSRTransformContext,
+ createChildContext,
+ processChildren
+} from '../ssrCodegenTransform'
import { isSymbol } from '@vue/shared'
+// We need to construct the slot functions in the 1st pass to ensure proper
+// scope tracking, but the children of each slot cannot be processed until
+// the 2nd pass, so we store the WIP slot functions in a weakmap during the 1st
+// pass and complete them in the 2nd pass.
+const wipMap = new WeakMap()
+
+interface WIPSlotEntry {
+ fn: FunctionExpression
+ children: TemplateChildNode[]
+}
+
export const ssrTransformComponent: NodeTransform = (node, context) => {
if (
node.type !== NodeTypes.ELEMENT ||
@@ -38,20 +59,37 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
// note we are not passing ssr: true here because for components, v-on
// handlers should still be passed
- const { props } = buildProps(node, context)
+ const props =
+ node.props.length > 0 ? buildProps(node, context).props || `null` : `null`
+
+ const wipEntries: WIPSlotEntry[] = []
+ wipMap.set(node, wipEntries)
+
+ const buildSSRSlotFn: SlotFnBuilder = (props, children, loc) => {
+ // An SSR slot function has the signature of
+ // (props, _push, _parent) => void
+ // See server-renderer/src/helpers/renderSlot.ts
+ const fn = createFunctionExpression(
+ [props || `_`, `_push`, `_parent`],
+ undefined, // no return, assign body later
+ true, // newline
+ false, // isSlot: pass false since we don't need client scopeId codegen
+ loc
+ )
+ wipEntries.push({ fn, children })
+ return fn
+ }
+
+ const slots = node.children.length
+ ? buildSlots(node, context, buildSSRSlotFn).slots
+ : `null`
- // TODO slots
// TODO option for slots bail out
// TODO scopeId
node.ssrCodegenNode = createCallExpression(
context.helper(SSR_RENDER_COMPONENT),
- [
- component,
- props || `null`,
- `null`, // TODO slots
- `_parent`
- ]
+ [component, props, slots, `_parent`]
)
}
}
@@ -60,5 +98,13 @@ export function ssrProcessComponent(
node: ComponentNode,
context: SSRTransformContext
) {
+ // 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 } = wipEntries[i]
+ const childContext = createChildContext(context)
+ processChildren(children, childContext)
+ fn.body = createBlockStatement(childContext.body)
+ }
context.pushStatement(node.ssrCodegenNode!)
}
diff --git a/packages/compiler-ssr/src/transforms/ssrVModel.ts b/packages/compiler-ssr/src/transforms/ssrVModel.ts
index 1af92879c..f23c62fed 100644
--- a/packages/compiler-ssr/src/transforms/ssrVModel.ts
+++ b/packages/compiler-ssr/src/transforms/ssrVModel.ts
@@ -41,7 +41,7 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
const res: DirectiveTransformResult = { props: [] }
const defaultProps = [
// default value binding for text type inputs
- createObjectProperty(createSimpleExpression(`value`, true), model)
+ createObjectProperty(`value`, model)
]
if (node.tag === 'input') {
const type = findProp(node, 'type')
@@ -62,7 +62,7 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
case 'radio':
res.props = [
createObjectProperty(
- createSimpleExpression(`checked`, true),
+ `checked`,
createCallExpression(context.helper(SSR_LOOSE_EQUAL), [
model,
value
@@ -73,7 +73,7 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
case 'checkbox':
res.props = [
createObjectProperty(
- createSimpleExpression(`checked`, true),
+ `checked`,
createConditionalExpression(
createCallExpression(`Array.isArray`, [model]),
createCallExpression(context.helper(SSR_LOOSE_CONTAIN), [
diff --git a/packages/compiler-ssr/src/transforms/ssrVShow.ts b/packages/compiler-ssr/src/transforms/ssrVShow.ts
index 314d8a234..ecd1c0d60 100644
--- a/packages/compiler-ssr/src/transforms/ssrVShow.ts
+++ b/packages/compiler-ssr/src/transforms/ssrVShow.ts
@@ -17,13 +17,13 @@ export const ssrTransformShow: DirectiveTransform = (dir, node, context) => {
return {
props: [
createObjectProperty(
- createSimpleExpression(`style`, true),
+ `style`,
createConditionalExpression(
dir.exp!,
createSimpleExpression(`null`, false),
createObjectExpression([
createObjectProperty(
- createSimpleExpression(`display`, true),
+ `display`,
createSimpleExpression(`none`, true)
)
]),