wip(compiler-ssr): component slots
This commit is contained in:
parent
39d1acf417
commit
f3e70b3733
@ -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)
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -7,12 +7,33 @@ import {
|
|||||||
buildProps,
|
buildProps,
|
||||||
ComponentNode,
|
ComponentNode,
|
||||||
PORTAL,
|
PORTAL,
|
||||||
SUSPENSE
|
SUSPENSE,
|
||||||
|
SlotFnBuilder,
|
||||||
|
createFunctionExpression,
|
||||||
|
createBlockStatement,
|
||||||
|
buildSlots,
|
||||||
|
FunctionExpression,
|
||||||
|
TemplateChildNode
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
|
import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
|
||||||
import { SSRTransformContext } from '../ssrCodegenTransform'
|
import {
|
||||||
|
SSRTransformContext,
|
||||||
|
createChildContext,
|
||||||
|
processChildren
|
||||||
|
} from '../ssrCodegenTransform'
|
||||||
import { isSymbol } from '@vue/shared'
|
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) => {
|
export const ssrTransformComponent: NodeTransform = (node, context) => {
|
||||||
if (
|
if (
|
||||||
node.type !== NodeTypes.ELEMENT ||
|
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
|
// note we are not passing ssr: true here because for components, v-on
|
||||||
// handlers should still be passed
|
// 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 option for slots bail out
|
||||||
// TODO scopeId
|
// TODO scopeId
|
||||||
|
|
||||||
node.ssrCodegenNode = createCallExpression(
|
node.ssrCodegenNode = createCallExpression(
|
||||||
context.helper(SSR_RENDER_COMPONENT),
|
context.helper(SSR_RENDER_COMPONENT),
|
||||||
[
|
[component, props, slots, `_parent`]
|
||||||
component,
|
|
||||||
props || `null`,
|
|
||||||
`null`, // TODO slots
|
|
||||||
`_parent`
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,5 +98,13 @@ export function ssrProcessComponent(
|
|||||||
node: ComponentNode,
|
node: ComponentNode,
|
||||||
context: SSRTransformContext
|
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!)
|
context.pushStatement(node.ssrCodegenNode!)
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
|
|||||||
const res: DirectiveTransformResult = { props: [] }
|
const res: DirectiveTransformResult = { props: [] }
|
||||||
const defaultProps = [
|
const defaultProps = [
|
||||||
// default value binding for text type inputs
|
// default value binding for text type inputs
|
||||||
createObjectProperty(createSimpleExpression(`value`, true), model)
|
createObjectProperty(`value`, model)
|
||||||
]
|
]
|
||||||
if (node.tag === 'input') {
|
if (node.tag === 'input') {
|
||||||
const type = findProp(node, 'type')
|
const type = findProp(node, 'type')
|
||||||
@ -62,7 +62,7 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
|
|||||||
case 'radio':
|
case 'radio':
|
||||||
res.props = [
|
res.props = [
|
||||||
createObjectProperty(
|
createObjectProperty(
|
||||||
createSimpleExpression(`checked`, true),
|
`checked`,
|
||||||
createCallExpression(context.helper(SSR_LOOSE_EQUAL), [
|
createCallExpression(context.helper(SSR_LOOSE_EQUAL), [
|
||||||
model,
|
model,
|
||||||
value
|
value
|
||||||
@ -73,7 +73,7 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
|
|||||||
case 'checkbox':
|
case 'checkbox':
|
||||||
res.props = [
|
res.props = [
|
||||||
createObjectProperty(
|
createObjectProperty(
|
||||||
createSimpleExpression(`checked`, true),
|
`checked`,
|
||||||
createConditionalExpression(
|
createConditionalExpression(
|
||||||
createCallExpression(`Array.isArray`, [model]),
|
createCallExpression(`Array.isArray`, [model]),
|
||||||
createCallExpression(context.helper(SSR_LOOSE_CONTAIN), [
|
createCallExpression(context.helper(SSR_LOOSE_CONTAIN), [
|
||||||
|
@ -17,13 +17,13 @@ export const ssrTransformShow: DirectiveTransform = (dir, node, context) => {
|
|||||||
return {
|
return {
|
||||||
props: [
|
props: [
|
||||||
createObjectProperty(
|
createObjectProperty(
|
||||||
createSimpleExpression(`style`, true),
|
`style`,
|
||||||
createConditionalExpression(
|
createConditionalExpression(
|
||||||
dir.exp!,
|
dir.exp!,
|
||||||
createSimpleExpression(`null`, false),
|
createSimpleExpression(`null`, false),
|
||||||
createObjectExpression([
|
createObjectExpression([
|
||||||
createObjectProperty(
|
createObjectProperty(
|
||||||
createSimpleExpression(`display`, true),
|
`display`,
|
||||||
createSimpleExpression(`none`, true)
|
createSimpleExpression(`none`, true)
|
||||||
)
|
)
|
||||||
]),
|
]),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user