fix(ssr): fix ssr on-the-fly compilation + slot fallback branch helper injection

This commit is contained in:
Evan You 2020-03-06 14:52:15 -05:00
parent 274f81c5db
commit 3be3785f94
6 changed files with 63 additions and 51 deletions

View File

@ -70,7 +70,7 @@ describe('ssr: components', () => {
test('explicit default slot', () => { test('explicit default slot', () => {
expect(compile(`<foo v-slot="{ msg }">{{ msg + outer }}</foo>`).code) expect(compile(`<foo v-slot="{ msg }">{{ msg + outer }}</foo>`).code)
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"const { resolveComponent: _resolveComponent, createTextVNode: _createTextVNode } = require(\\"vue\\") "const { resolveComponent: _resolveComponent, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\") const { ssrRenderComponent: _ssrRenderComponent, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent) {
@ -82,7 +82,7 @@ describe('ssr: components', () => {
_push(\`\${_ssrInterpolate(msg + _ctx.outer)}\`) _push(\`\${_ssrInterpolate(msg + _ctx.outer)}\`)
} else { } else {
return [ return [
_createTextVNode(_toDisplayString(msg + _ctx.outer)) _createTextVNode(_toDisplayString(msg + _ctx.outer), 1 /* TEXT */)
] ]
} }
}, },
@ -168,7 +168,7 @@ describe('ssr: components', () => {
<template v-for="key in names" v-slot:[key]="{ msg }">{{ msg + key + bar }}</template> <template v-for="key in names" v-slot:[key]="{ msg }">{{ msg + key + bar }}</template>
</foo>`).code </foo>`).code
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"const { resolveComponent: _resolveComponent, createTextVNode: _createTextVNode, renderList: _renderList, createSlots: _createSlots } = require(\\"vue\\") "const { resolveComponent: _resolveComponent, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, renderList: _renderList, createSlots: _createSlots } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\") const { ssrRenderComponent: _ssrRenderComponent, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent) {
@ -183,7 +183,7 @@ describe('ssr: components', () => {
_push(\`\${_ssrInterpolate(msg + key + _ctx.bar)}\`) _push(\`\${_ssrInterpolate(msg + key + _ctx.bar)}\`)
} else { } else {
return [ return [
_createTextVNode(_toDisplayString(msg + _ctx.key + _ctx.bar)) _createTextVNode(_toDisplayString(msg + _ctx.key + _ctx.bar), 1 /* TEXT */)
] ]
} }
} }

View File

@ -80,6 +80,9 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
const clonedNode = clone(node) const clonedNode = clone(node)
return function ssrPostTransformComponent() { return function ssrPostTransformComponent() {
// Using the cloned node, build the normal VNode-based branches (for
// fallback in case the child is render-fn based). Store them in an array
// for later use.
buildSlots(clonedNode, context, (props, children) => { buildSlots(clonedNode, context, (props, children) => {
vnodeBranches.push(createVNodeSlotBranch(props, children, context)) vnodeBranches.push(createVNodeSlotBranch(props, children, context))
return createFunctionExpression(undefined) return createFunctionExpression(undefined)
@ -106,9 +109,7 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
wipEntries.push({ wipEntries.push({
fn, fn,
children, children,
// build the children using normal vnode-based transforms // also collect the corresponding vnode branch built earlier
// TODO fixme: `children` here has already been mutated at this point
// so the sub-transform runs into errors :/
vnodeBranch: vnodeBranches[wipEntries.length] vnodeBranch: vnodeBranches[wipEntries.length]
}) })
return fn return fn
@ -266,6 +267,9 @@ function subTransform(
) { ) {
const childRoot = createRoot([node]) const childRoot = createRoot([node])
const childContext = createTransformContext(childRoot, options) const childContext = createTransformContext(childRoot, options)
// this sub transform is for vnode fallback branch so it should be handled
// like normal render functions
childContext.ssr = false
// inherit parent scope analysis state // inherit parent scope analysis state
childContext.scopes = { ...parentContext.scopes } childContext.scopes = { ...parentContext.scopes }
childContext.identifiers = { ...parentContext.identifiers } childContext.identifiers = { ...parentContext.identifiers }

View File

@ -1 +0,0 @@
// TODO

View File

@ -300,7 +300,7 @@ export function setupComponent(
// setup stateful logic // setup stateful logic
let setupResult let setupResult
if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
setupResult = setupStatefulComponent(instance, parentSuspense) setupResult = setupStatefulComponent(instance, parentSuspense, isSSR)
} }
isInSSRComponentSetup = false isInSSRComponentSetup = false
return setupResult return setupResult
@ -308,7 +308,8 @@ export function setupComponent(
function setupStatefulComponent( function setupStatefulComponent(
instance: ComponentInternalInstance, instance: ComponentInternalInstance,
parentSuspense: SuspenseBoundary | null parentSuspense: SuspenseBoundary | null,
isSSR: boolean
) { ) {
const Component = instance.type as ComponentOptions const Component = instance.type as ComponentOptions
@ -362,7 +363,7 @@ function setupStatefulComponent(
if (isInSSRComponentSetup) { if (isInSSRComponentSetup) {
// return the promise so server-renderer can wait on it // return the promise so server-renderer can wait on it
return setupResult.then(resolvedResult => { return setupResult.then(resolvedResult => {
handleSetupResult(instance, resolvedResult, parentSuspense) handleSetupResult(instance, resolvedResult, parentSuspense, isSSR)
}) })
} else if (__FEATURE_SUSPENSE__) { } else if (__FEATURE_SUSPENSE__) {
// async setup returned Promise. // async setup returned Promise.
@ -375,17 +376,18 @@ function setupStatefulComponent(
) )
} }
} else { } else {
handleSetupResult(instance, setupResult, parentSuspense) handleSetupResult(instance, setupResult, parentSuspense, isSSR)
} }
} else { } else {
finishComponentSetup(instance, parentSuspense) finishComponentSetup(instance, parentSuspense, isSSR)
} }
} }
export function handleSetupResult( export function handleSetupResult(
instance: ComponentInternalInstance, instance: ComponentInternalInstance,
setupResult: unknown, setupResult: unknown,
parentSuspense: SuspenseBoundary | null parentSuspense: SuspenseBoundary | null,
isSSR: boolean
) { ) {
if (isFunction(setupResult)) { if (isFunction(setupResult)) {
// setup returned an inline render function // setup returned an inline render function
@ -407,7 +409,7 @@ export function handleSetupResult(
}` }`
) )
} }
finishComponentSetup(instance, parentSuspense) finishComponentSetup(instance, parentSuspense, isSSR)
} }
type CompileFunction = ( type CompileFunction = (
@ -424,10 +426,17 @@ export function registerRuntimeCompiler(_compile: any) {
function finishComponentSetup( function finishComponentSetup(
instance: ComponentInternalInstance, instance: ComponentInternalInstance,
parentSuspense: SuspenseBoundary | null parentSuspense: SuspenseBoundary | null,
isSSR: boolean
) { ) {
const Component = instance.type as ComponentOptions const Component = instance.type as ComponentOptions
if (!instance.render) {
// template / render function normalization
if (__NODE_JS__ && isSSR) {
if (Component.render) {
instance.render = Component.render as RenderFunction
}
} else if (!instance.render) {
if (__RUNTIME_COMPILE__ && Component.template && !Component.render) { if (__RUNTIME_COMPILE__ && Component.template && !Component.render) {
// __RUNTIME_COMPILE__ ensures `compile` is provided // __RUNTIME_COMPILE__ ensures `compile` is provided
Component.render = compile!(Component.template, { Component.render = compile!(Component.template, {
@ -437,7 +446,7 @@ function finishComponentSetup(
;(Component.render as RenderFunction).isRuntimeCompiled = true ;(Component.render as RenderFunction).isRuntimeCompiled = true
} }
if (__DEV__ && !Component.render && !Component.ssrRender) { if (__DEV__ && !Component.render) {
/* istanbul ignore if */ /* istanbul ignore if */
if (!__RUNTIME_COMPILE__ && Component.template) { if (!__RUNTIME_COMPILE__ && Component.template) {
warn( warn(

View File

@ -410,7 +410,7 @@ function createSuspenseBoundary<HostNode, HostElement>(
if (__DEV__) { if (__DEV__) {
pushWarningContext(vnode) pushWarningContext(vnode)
} }
handleSetupResult(instance, asyncSetupResult, suspense) handleSetupResult(instance, asyncSetupResult, suspense, false)
// unset placeholder, otherwise this will be treated as a hydration mount // unset placeholder, otherwise this will be treated as a hydration mount
vnode.el = null vnode.el = null
setupRenderEffect( setupRenderEffect(

View File

@ -155,6 +155,37 @@ function renderComponentVNode(
} }
} }
function renderComponentSubTree(
instance: ComponentInternalInstance
): ResolvedSSRBuffer | Promise<ResolvedSSRBuffer> {
const comp = instance.type as Component
const { getBuffer, push } = createBuffer()
if (isFunction(comp)) {
renderVNode(push, renderComponentRoot(instance), instance)
} else {
if (!instance.render && !comp.ssrRender && isString(comp.template)) {
comp.ssrRender = ssrCompile(comp.template, instance)
}
if (comp.ssrRender) {
// optimized
// set current rendering instance for asset resolution
setCurrentRenderingInstance(instance)
comp.ssrRender(instance.proxy, push, instance)
setCurrentRenderingInstance(null)
} else if (instance.render) {
renderVNode(push, renderComponentRoot(instance), instance)
} else {
throw new Error(
`Component ${
comp.name ? `${comp.name} ` : ``
} is missing template or render function.`
)
}
}
return getBuffer()
}
type SSRRenderFunction = ( type SSRRenderFunction = (
context: any, context: any,
push: (item: any) => void, push: (item: any) => void,
@ -190,38 +221,7 @@ function ssrCompile(
} }
} }
}) })
return (compileCache[template] = Function(code)()) return (compileCache[template] = Function('require', code)(require))
}
function renderComponentSubTree(
instance: ComponentInternalInstance
): ResolvedSSRBuffer | Promise<ResolvedSSRBuffer> {
const comp = instance.type as Component
const { getBuffer, push } = createBuffer()
if (isFunction(comp)) {
renderVNode(push, renderComponentRoot(instance), instance)
} else {
if (!instance.render && !comp.ssrRender && isString(comp.template)) {
comp.ssrRender = ssrCompile(comp.template, instance)
}
if (comp.ssrRender) {
// optimized
// set current rendering instance for asset resolution
setCurrentRenderingInstance(instance)
comp.ssrRender(instance.proxy, push, instance)
setCurrentRenderingInstance(null)
} else if (instance.render) {
renderVNode(push, renderComponentRoot(instance), instance)
} else {
throw new Error(
`Component ${
comp.name ? `${comp.name} ` : ``
} is missing template or render function.`
)
}
}
return getBuffer()
} }
function renderVNode( function renderVNode(