From 3be3785f945253918469da456a14a2d9381bcbd0 Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 6 Mar 2020 14:52:15 -0500 Subject: [PATCH] fix(ssr): fix ssr on-the-fly compilation + slot fallback branch helper injection --- .../__tests__/ssrComponent.spec.ts | 8 +-- .../src/transforms/ssrTransformComponent.ts | 10 ++- .../compiler-ssr/src/transforms/ssrVSlot.ts | 1 - packages/runtime-core/src/component.ts | 29 ++++++--- .../runtime-core/src/components/Suspense.ts | 2 +- .../server-renderer/src/renderToString.ts | 64 +++++++++---------- 6 files changed, 63 insertions(+), 51 deletions(-) delete mode 100644 packages/compiler-ssr/src/transforms/ssrVSlot.ts diff --git a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts index 642289c29a6..4414ad3a891 100644 --- a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts +++ b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts @@ -70,7 +70,7 @@ describe('ssr: components', () => { test('explicit default slot', () => { expect(compile(`{{ msg + outer }}`).code) .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\\") return function ssrRender(_ctx, _push, _parent) { @@ -82,7 +82,7 @@ describe('ssr: components', () => { _push(\`\${_ssrInterpolate(msg + _ctx.outer)}\`) } else { return [ - _createTextVNode(_toDisplayString(msg + _ctx.outer)) + _createTextVNode(_toDisplayString(msg + _ctx.outer), 1 /* TEXT */) ] } }, @@ -168,7 +168,7 @@ describe('ssr: components', () => { `).code ).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\\") return function ssrRender(_ctx, _push, _parent) { @@ -183,7 +183,7 @@ describe('ssr: components', () => { _push(\`\${_ssrInterpolate(msg + key + _ctx.bar)}\`) } else { return [ - _createTextVNode(_toDisplayString(msg + _ctx.key + _ctx.bar)) + _createTextVNode(_toDisplayString(msg + _ctx.key + _ctx.bar), 1 /* TEXT */) ] } } diff --git a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts index 5a3ad978ffa..0e3f1457bf4 100644 --- a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts +++ b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts @@ -80,6 +80,9 @@ export const ssrTransformComponent: NodeTransform = (node, context) => { const clonedNode = clone(node) 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) => { vnodeBranches.push(createVNodeSlotBranch(props, children, context)) return createFunctionExpression(undefined) @@ -106,9 +109,7 @@ export const ssrTransformComponent: NodeTransform = (node, context) => { 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 :/ + // also collect the corresponding vnode branch built earlier vnodeBranch: vnodeBranches[wipEntries.length] }) return fn @@ -266,6 +267,9 @@ function subTransform( ) { const childRoot = createRoot([node]) 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 childContext.scopes = { ...parentContext.scopes } childContext.identifiers = { ...parentContext.identifiers } diff --git a/packages/compiler-ssr/src/transforms/ssrVSlot.ts b/packages/compiler-ssr/src/transforms/ssrVSlot.ts deleted file mode 100644 index 70b786d12ed..00000000000 --- a/packages/compiler-ssr/src/transforms/ssrVSlot.ts +++ /dev/null @@ -1 +0,0 @@ -// TODO diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 29ea5151eb0..ad8453e9c0f 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -300,7 +300,7 @@ export function setupComponent( // setup stateful logic let setupResult if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { - setupResult = setupStatefulComponent(instance, parentSuspense) + setupResult = setupStatefulComponent(instance, parentSuspense, isSSR) } isInSSRComponentSetup = false return setupResult @@ -308,7 +308,8 @@ export function setupComponent( function setupStatefulComponent( instance: ComponentInternalInstance, - parentSuspense: SuspenseBoundary | null + parentSuspense: SuspenseBoundary | null, + isSSR: boolean ) { const Component = instance.type as ComponentOptions @@ -362,7 +363,7 @@ function setupStatefulComponent( if (isInSSRComponentSetup) { // return the promise so server-renderer can wait on it return setupResult.then(resolvedResult => { - handleSetupResult(instance, resolvedResult, parentSuspense) + handleSetupResult(instance, resolvedResult, parentSuspense, isSSR) }) } else if (__FEATURE_SUSPENSE__) { // async setup returned Promise. @@ -375,17 +376,18 @@ function setupStatefulComponent( ) } } else { - handleSetupResult(instance, setupResult, parentSuspense) + handleSetupResult(instance, setupResult, parentSuspense, isSSR) } } else { - finishComponentSetup(instance, parentSuspense) + finishComponentSetup(instance, parentSuspense, isSSR) } } export function handleSetupResult( instance: ComponentInternalInstance, setupResult: unknown, - parentSuspense: SuspenseBoundary | null + parentSuspense: SuspenseBoundary | null, + isSSR: boolean ) { if (isFunction(setupResult)) { // setup returned an inline render function @@ -407,7 +409,7 @@ export function handleSetupResult( }` ) } - finishComponentSetup(instance, parentSuspense) + finishComponentSetup(instance, parentSuspense, isSSR) } type CompileFunction = ( @@ -424,10 +426,17 @@ export function registerRuntimeCompiler(_compile: any) { function finishComponentSetup( instance: ComponentInternalInstance, - parentSuspense: SuspenseBoundary | null + parentSuspense: SuspenseBoundary | null, + isSSR: boolean ) { 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) { // __RUNTIME_COMPILE__ ensures `compile` is provided Component.render = compile!(Component.template, { @@ -437,7 +446,7 @@ function finishComponentSetup( ;(Component.render as RenderFunction).isRuntimeCompiled = true } - if (__DEV__ && !Component.render && !Component.ssrRender) { + if (__DEV__ && !Component.render) { /* istanbul ignore if */ if (!__RUNTIME_COMPILE__ && Component.template) { warn( diff --git a/packages/runtime-core/src/components/Suspense.ts b/packages/runtime-core/src/components/Suspense.ts index d27c1f29f58..b3a6b31c6d4 100644 --- a/packages/runtime-core/src/components/Suspense.ts +++ b/packages/runtime-core/src/components/Suspense.ts @@ -410,7 +410,7 @@ function createSuspenseBoundary( if (__DEV__) { pushWarningContext(vnode) } - handleSetupResult(instance, asyncSetupResult, suspense) + handleSetupResult(instance, asyncSetupResult, suspense, false) // unset placeholder, otherwise this will be treated as a hydration mount vnode.el = null setupRenderEffect( diff --git a/packages/server-renderer/src/renderToString.ts b/packages/server-renderer/src/renderToString.ts index 5b5ecc4d57a..b181b5422cb 100644 --- a/packages/server-renderer/src/renderToString.ts +++ b/packages/server-renderer/src/renderToString.ts @@ -155,6 +155,37 @@ function renderComponentVNode( } } +function renderComponentSubTree( + instance: ComponentInternalInstance +): ResolvedSSRBuffer | Promise { + 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 = ( context: any, push: (item: any) => void, @@ -190,38 +221,7 @@ function ssrCompile( } } }) - return (compileCache[template] = Function(code)()) -} - -function renderComponentSubTree( - instance: ComponentInternalInstance -): ResolvedSSRBuffer | Promise { - 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() + return (compileCache[template] = Function('require', code)(require)) } function renderVNode(