wip(ssr): ssr slot vnode fallback
This commit is contained in:
@@ -57,10 +57,13 @@ describe('ssr: components', () => {
|
||||
|
||||
_push(_ssrRenderComponent(_component_foo, null, {
|
||||
default: (_, _push, _parent, _scopeId) => {
|
||||
if (_scopeId) {
|
||||
_push(\`hello<div \${_scopeId}></div>\`)
|
||||
if (_push) {
|
||||
_push(\`hello<div\${_scopeId}></div>\`)
|
||||
} else {
|
||||
_push(\`hello<div></div>\`)
|
||||
return [
|
||||
createTextVNode(\\"hello\\"),
|
||||
createVNode(\\"div\\")
|
||||
]
|
||||
}
|
||||
},
|
||||
_compiled: true
|
||||
@@ -80,7 +83,13 @@ describe('ssr: components', () => {
|
||||
|
||||
_push(_ssrRenderComponent(_component_foo, null, {
|
||||
default: ({ msg }, _push, _parent, _scopeId) => {
|
||||
_push(\`\${_ssrInterpolate(msg + _ctx.outer)}\`)
|
||||
if (_push) {
|
||||
_push(\`\${_ssrInterpolate(msg + _ctx.outer)}\`)
|
||||
} else {
|
||||
return [
|
||||
createTextVNode(toDisplayString(_ctx.msg + _ctx.outer))
|
||||
]
|
||||
}
|
||||
},
|
||||
_compiled: true
|
||||
}, _parent))
|
||||
@@ -103,10 +112,22 @@ describe('ssr: components', () => {
|
||||
|
||||
_push(_ssrRenderComponent(_component_foo, null, {
|
||||
default: (_, _push, _parent, _scopeId) => {
|
||||
_push(\`foo\`)
|
||||
if (_push) {
|
||||
_push(\`foo\`)
|
||||
} else {
|
||||
return [
|
||||
createTextVNode(\\"foo\\")
|
||||
]
|
||||
}
|
||||
},
|
||||
named: (_, _push, _parent, _scopeId) => {
|
||||
_push(\`bar\`)
|
||||
if (_push) {
|
||||
_push(\`bar\`)
|
||||
} else {
|
||||
return [
|
||||
createTextVNode(\\"bar\\")
|
||||
]
|
||||
}
|
||||
},
|
||||
_compiled: true
|
||||
}, _parent))
|
||||
@@ -131,7 +152,13 @@ describe('ssr: components', () => {
|
||||
? {
|
||||
name: \\"named\\",
|
||||
fn: (_, _push, _parent, _scopeId) => {
|
||||
_push(\`foo\`)
|
||||
if (_push) {
|
||||
_push(\`foo\`)
|
||||
} else {
|
||||
return [
|
||||
createTextVNode(\\"foo\\")
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
|
||||
@@ -31,7 +31,13 @@ describe('ssr: scopeId', () => {
|
||||
|
||||
_push(_ssrRenderComponent(_component_foo, null, {
|
||||
default: (_, _push, _parent, _scopeId) => {
|
||||
_push(\`foo\`)
|
||||
if (_push) {
|
||||
_push(\`foo\`)
|
||||
} else {
|
||||
return [
|
||||
createTextVNode(\\"foo\\")
|
||||
]
|
||||
}
|
||||
},
|
||||
_compiled: true
|
||||
}, _parent))
|
||||
@@ -53,10 +59,12 @@ describe('ssr: scopeId', () => {
|
||||
|
||||
_push(_ssrRenderComponent(_component_foo, null, {
|
||||
default: (_, _push, _parent, _scopeId) => {
|
||||
if (_scopeId) {
|
||||
_push(\`<span data-v-xxxxxxx \${_scopeId}>hello</span>\`)
|
||||
if (_push) {
|
||||
_push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`)
|
||||
} else {
|
||||
_push(\`<span data-v-xxxxxxx>hello</span>\`)
|
||||
return [
|
||||
createVNode(\\"span\\", null, \\"hello\\")
|
||||
]
|
||||
}
|
||||
},
|
||||
_compiled: true
|
||||
@@ -80,30 +88,30 @@ describe('ssr: scopeId', () => {
|
||||
|
||||
_push(_ssrRenderComponent(_component_foo, null, {
|
||||
default: (_, _push, _parent, _scopeId) => {
|
||||
if (_scopeId) {
|
||||
_push(\`<span data-v-xxxxxxx \${_scopeId}>hello</span>\`)
|
||||
if (_push) {
|
||||
_push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`)
|
||||
_push(_ssrRenderComponent(_component_bar, null, {
|
||||
default: (_, _push, _parent, _scopeId) => {
|
||||
if (_scopeId) {
|
||||
_push(\`<span data-v-xxxxxxx \${_scopeId}></span>\`)
|
||||
if (_push) {
|
||||
_push(\`<span data-v-xxxxxxx\${_scopeId}></span>\`)
|
||||
} else {
|
||||
_push(\`<span data-v-xxxxxxx></span>\`)
|
||||
return [
|
||||
createVNode(\\"span\\")
|
||||
]
|
||||
}
|
||||
},
|
||||
_compiled: true
|
||||
}, _parent))
|
||||
} else {
|
||||
_push(\`<span data-v-xxxxxxx>hello</span>\`)
|
||||
_push(_ssrRenderComponent(_component_bar, null, {
|
||||
default: (_, _push, _parent, _scopeId) => {
|
||||
if (_scopeId) {
|
||||
_push(\`<span data-v-xxxxxxx \${_scopeId}></span>\`)
|
||||
} else {
|
||||
_push(\`<span data-v-xxxxxxx></span>\`)
|
||||
}
|
||||
},
|
||||
_compiled: true
|
||||
}, _parent))
|
||||
return [
|
||||
createVNode(\\"span\\", null, \\"hello\\"),
|
||||
createVNode(_component_bar, null, {
|
||||
default: () => [
|
||||
createVNode(\\"span\\")
|
||||
],
|
||||
_compiled: true
|
||||
})
|
||||
]
|
||||
}
|
||||
},
|
||||
_compiled: true
|
||||
|
||||
@@ -28,7 +28,7 @@ import { ssrProcessElement } from './transforms/ssrTransformElement'
|
||||
// passing it to codegen.
|
||||
|
||||
export function ssrCodegenTransform(ast: RootNode, options: CompilerOptions) {
|
||||
const context = createSSRTransformContext(options)
|
||||
const context = createSSRTransformContext(ast, options)
|
||||
const isFragment =
|
||||
ast.children.length > 1 && ast.children.some(c => !isText(c))
|
||||
processChildren(ast.children, context, isFragment)
|
||||
@@ -46,6 +46,7 @@ export function ssrCodegenTransform(ast: RootNode, options: CompilerOptions) {
|
||||
export type SSRTransformContext = ReturnType<typeof createSSRTransformContext>
|
||||
|
||||
function createSSRTransformContext(
|
||||
root: RootNode,
|
||||
options: CompilerOptions,
|
||||
helpers: Set<symbol> = new Set(),
|
||||
withSlotScopeId = false
|
||||
@@ -54,6 +55,7 @@ function createSSRTransformContext(
|
||||
let currentString: TemplateLiteral | null = null
|
||||
|
||||
return {
|
||||
root,
|
||||
options,
|
||||
body,
|
||||
helpers,
|
||||
@@ -91,6 +93,7 @@ function createChildContext(
|
||||
): SSRTransformContext {
|
||||
// ensure child inherits parent helpers
|
||||
return createSSRTransformContext(
|
||||
parent.root,
|
||||
parent.options,
|
||||
parent.helpers,
|
||||
withSlotScopeId
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
ComponentNode,
|
||||
SlotFnBuilder,
|
||||
createFunctionExpression,
|
||||
createBlockStatement,
|
||||
buildSlots,
|
||||
FunctionExpression,
|
||||
TemplateChildNode,
|
||||
@@ -17,7 +16,14 @@ import {
|
||||
TRANSITION_GROUP,
|
||||
createIfStatement,
|
||||
createSimpleExpression,
|
||||
isText
|
||||
getDOMTransformPreset,
|
||||
transform,
|
||||
createReturnStatement,
|
||||
ReturnStatement,
|
||||
Namespaces,
|
||||
locStub,
|
||||
RootNode,
|
||||
TransformContext
|
||||
} from '@vue/compiler-dom'
|
||||
import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
|
||||
import {
|
||||
@@ -25,7 +31,7 @@ import {
|
||||
processChildren,
|
||||
processChildrenAsStatement
|
||||
} from '../ssrCodegenTransform'
|
||||
import { isSymbol } from '@vue/shared'
|
||||
import { isSymbol, isObject, isArray } 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
|
||||
@@ -36,6 +42,7 @@ const wipMap = new WeakMap<ComponentNode, WIPSlotEntry[]>()
|
||||
interface WIPSlotEntry {
|
||||
fn: FunctionExpression
|
||||
children: TemplateChildNode[]
|
||||
vnodeBranch: ReturnStatement
|
||||
}
|
||||
|
||||
const componentTypeMap = new WeakMap<ComponentNode, symbol>()
|
||||
@@ -55,26 +62,32 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
|
||||
return // built-in component: fallthrough
|
||||
}
|
||||
|
||||
// note we are not passing ssr: true here because for components, v-on
|
||||
// handlers should still be passed
|
||||
const props =
|
||||
node.props.length > 0 ? buildProps(node, context).props || `null` : `null`
|
||||
node.props.length > 0
|
||||
? // note we are not passing ssr: true here because for components, v-on
|
||||
// handlers should still be passed
|
||||
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, _scopeId) => void
|
||||
// See server-renderer/src/helpers/renderSlot.ts
|
||||
const fn = createFunctionExpression(
|
||||
[props || `_`, `_push`, `_parent`, `_scopeId`],
|
||||
undefined, // no return, assign body later
|
||||
true, // newline
|
||||
false, // isSlot: pass false since we don't need client scopeId codegen
|
||||
true, // isSlot
|
||||
loc
|
||||
)
|
||||
wipEntries.push({ fn, children })
|
||||
wipEntries.push({
|
||||
fn,
|
||||
children,
|
||||
// build the children using normal vnode-based transforms
|
||||
// TODO fixme: `children` here has already been mutated at this point
|
||||
// so the sub-transform runs into errors :/
|
||||
vnodeBranch: createVNodeSlotBranch(clone(children), context)
|
||||
})
|
||||
return fn
|
||||
}
|
||||
|
||||
@@ -82,9 +95,6 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
|
||||
? buildSlots(node, context, buildSSRSlotFn).slots
|
||||
: `null`
|
||||
|
||||
// TODO option for slots bail out
|
||||
// TODO scopeId
|
||||
|
||||
node.ssrCodegenNode = createCallExpression(
|
||||
context.helper(SSR_RENDER_COMPONENT),
|
||||
[component, props, slots, `_parent`]
|
||||
@@ -113,26 +123,79 @@ export function ssrProcessComponent(
|
||||
// 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 hasNonTextChild = children.some(c => !isText(c))
|
||||
if (hasNonTextChild) {
|
||||
// SSR slots need to handled potential presence of scopeId of the child
|
||||
// component. To avoid the cost of concatenation when it's unnecessary,
|
||||
// we split the code into two paths, one with slot scopeId and one without.
|
||||
fn.body = createBlockStatement([
|
||||
createIfStatement(
|
||||
createSimpleExpression(`_scopeId`, false),
|
||||
// branch with scopeId concatenation
|
||||
processChildrenAsStatement(children, context, false, true),
|
||||
// branch without scopeId concatenation
|
||||
processChildrenAsStatement(children, context, false, false)
|
||||
)
|
||||
])
|
||||
} else {
|
||||
// only text, no need for scopeId branching.
|
||||
fn.body = processChildrenAsStatement(children, context)
|
||||
}
|
||||
const { fn, children, 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
|
||||
// is called by `_ssrRenderSlot`.
|
||||
fn.body = createIfStatement(
|
||||
createSimpleExpression(`_push`, false),
|
||||
processChildrenAsStatement(
|
||||
children,
|
||||
context,
|
||||
false,
|
||||
true /* withSlotScopeId */
|
||||
),
|
||||
vnodeBranch
|
||||
)
|
||||
}
|
||||
context.pushStatement(createCallExpression(`_push`, [node.ssrCodegenNode]))
|
||||
}
|
||||
}
|
||||
|
||||
function createVNodeSlotBranch(
|
||||
children: TemplateChildNode[],
|
||||
context: TransformContext
|
||||
): ReturnStatement {
|
||||
// we need to process the slot children using client-side transforms.
|
||||
// in order to do that we need to construct a fresh root.
|
||||
// in addition, wrap the children with a wrapper template for proper child
|
||||
// treatment.
|
||||
const { root } = context
|
||||
const childRoot: RootNode = {
|
||||
...root,
|
||||
children: [
|
||||
{
|
||||
type: NodeTypes.ELEMENT,
|
||||
ns: Namespaces.HTML,
|
||||
tag: 'template',
|
||||
tagType: ElementTypes.TEMPLATE,
|
||||
isSelfClosing: false,
|
||||
props: [],
|
||||
children,
|
||||
loc: locStub,
|
||||
codegenNode: undefined
|
||||
}
|
||||
]
|
||||
}
|
||||
const [nodeTransforms, directiveTransforms] = getDOMTransformPreset(true)
|
||||
transform(childRoot, {
|
||||
...context, // copy transform options on context
|
||||
nodeTransforms,
|
||||
directiveTransforms
|
||||
})
|
||||
|
||||
// merge helpers/components/directives/imports from the childRoot
|
||||
// back to current root
|
||||
;(['helpers', 'components', 'directives', 'imports'] as const).forEach(
|
||||
key => {
|
||||
root[key] = [...new Set([...root[key], ...childRoot[key]])] as any
|
||||
}
|
||||
)
|
||||
|
||||
return createReturnStatement(children)
|
||||
}
|
||||
|
||||
function clone(v: any): any {
|
||||
if (isArray(v)) {
|
||||
return v.map(clone)
|
||||
} else if (isObject(v)) {
|
||||
const res: any = {}
|
||||
for (const key in v) {
|
||||
res[key] = v[key]
|
||||
}
|
||||
return res
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,7 +309,6 @@ export function ssrProcessElement(
|
||||
|
||||
// Handle slot scopeId
|
||||
if (context.withSlotScopeId) {
|
||||
context.pushStringPart(` `)
|
||||
context.pushStringPart(createSimpleExpression(`_scopeId`, false))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user