wip(compiler-ssr): component slots

This commit is contained in:
Evan You 2020-02-06 12:05:53 -05:00
parent 39d1acf417
commit f3e70b3733
4 changed files with 178 additions and 15 deletions

View File

@ -45,4 +45,121 @@ describe('ssr: components', () => {
}"
`)
})
describe('slots', () => {
test('implicit default slot', () => {
expect(compile(`<foo>hello<div/></foo>`).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<div></div>\`)
},
_compiled: true
}, _parent)
}"
`)
})
test('explicit default slot', () => {
expect(compile(`<foo v-slot="{ msg }">{{ msg + outer }}</foo>`).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>
<template v-slot>foo</template>
<template v-slot:named>bar</template>
</foo>`).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>
<template v-slot:named v-if="ok">foo</template>
</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(`<foo>
<template v-for="key in names" v-slot:[key]="{ msg }">{{ msg + key + bar }}</template>
</foo>`).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)
}"
`)
})
})
})

View File

@ -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<ComponentNode, WIPSlotEntry[]>()
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!)
}

View File

@ -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), [

View File

@ -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)
)
]),