diff --git a/packages/server-renderer/src/renderToString.ts b/packages/server-renderer/src/renderToString.ts index 2753c3455..0aac04cd1 100644 --- a/packages/server-renderer/src/renderToString.ts +++ b/packages/server-renderer/src/renderToString.ts @@ -67,9 +67,11 @@ function createBuffer() { let hasAsync = false const buffer: SSRBuffer = [] return { - buffer, - hasAsync() { - return hasAsync + getBuffer(): ResolvedSSRBuffer | Promise { + // If the current component's buffer contains any Promise from async children, + // then it must return a Promise too. Otherwise this is a component that + // contains only sync children so we can avoid the async book-keeping overhead. + return hasAsync ? Promise.all(buffer) : (buffer as ResolvedSSRBuffer) }, push(item: SSRBufferItem) { const isStringItem = isString(item) @@ -104,28 +106,19 @@ export async function renderToString( input: App | VNode, context: SSRContext = {} ): Promise { - let buffer: ResolvedSSRBuffer if (isVNode(input)) { // raw vnode, wrap with app (for context) return renderToString(createApp({ render: () => input }), context) - } else { - // rendering an app - const vnode = createVNode(input._component, input._props) - vnode.appContext = input._context - // provide the ssr context to the tree - input.provide(ssrContextKey, context) - buffer = await renderComponentVNode(vnode) } - // resolve portals - if (context.__portalBuffers) { - context.portals = context.portals || {} - for (const key in context.__portalBuffers) { - // note: it's OK to await sequentially here because the Promises were - // created eagerly in parallel. - context.portals[key] = unrollBuffer(await context.__portalBuffers[key]) - } - } + // rendering an app + const vnode = createVNode(input._component, input._props) + vnode.appContext = input._context + // provide the ssr context to the tree + input.provide(ssrContextKey, context) + const buffer = await renderComponentVNode(vnode) + + await resolvePortals(context) return unrollBuffer(buffer) } @@ -201,7 +194,7 @@ function renderComponentSubTree( instance: ComponentInternalInstance ): ResolvedSSRBuffer | Promise { const comp = instance.type as Component - const { buffer, push, hasAsync } = createBuffer() + const { getBuffer, push } = createBuffer() if (isFunction(comp)) { renderVNode(push, renderComponentRoot(instance), instance) } else { @@ -225,10 +218,7 @@ function renderComponentSubTree( ) } } - // If the current component's buffer contains any Promise from async children, - // then it must return a Promise too. Otherwise this is a component that - // contains only sync children so we can avoid the async book-keeping overhead. - return hasAsync() ? Promise.all(buffer) : (buffer as ResolvedSSRBuffer) + return getBuffer() } function renderVNode( @@ -349,7 +339,7 @@ function renderPortal( return [] } - const { buffer, push, hasAsync } = createBuffer() + const { getBuffer, push } = createBuffer() renderVNodeChildren( push, vnode.children as VNodeArrayChildren, @@ -360,7 +350,17 @@ function renderPortal( ] as SSRContext const portalBuffers = context.__portalBuffers || (context.__portalBuffers = {}) - portalBuffers[target] = hasAsync() - ? Promise.all(buffer) - : (buffer as ResolvedSSRBuffer) + + portalBuffers[target] = getBuffer() +} + +async function resolvePortals(context: SSRContext) { + if (context.__portalBuffers) { + context.portals = context.portals || {} + for (const key in context.__portalBuffers) { + // note: it's OK to await sequentially here because the Promises were + // created eagerly in parallel. + context.portals[key] = unrollBuffer(await context.__portalBuffers[key]) + } + } }