From 30584bcc61515eb9200071b8a4780e05c2ab786e Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 26 Jun 2020 11:10:30 -0400 Subject: [PATCH] perf(ssr): avoid unnecessary await ticks when unrolling sync buffers --- packages/server-renderer/src/render.ts | 10 +++++-- .../server-renderer/src/renderToStream.ts | 26 ++++++++++++++--- .../server-renderer/src/renderToString.ts | 28 ++++++++++++++++--- 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/packages/server-renderer/src/render.ts b/packages/server-renderer/src/render.ts index 15f052235b8..9f166aa6edf 100644 --- a/packages/server-renderer/src/render.ts +++ b/packages/server-renderer/src/render.ts @@ -20,7 +20,8 @@ import { isPromise, isString, isVoidTag, - ShapeFlags + ShapeFlags, + isArray } from '@vue/shared' import { ssrRenderAttrs } from './helpers/ssrRenderAttrs' import { ssrCompile } from './helpers/ssrCompile' @@ -35,7 +36,7 @@ const { normalizeSuspenseChildren } = ssrUtils -export type SSRBuffer = SSRBufferItem[] +export type SSRBuffer = SSRBufferItem[] & { hasAsync?: boolean } export type SSRBufferItem = string | SSRBuffer | Promise export type PushFn = (item: SSRBufferItem) => void export type Props = Record @@ -68,6 +69,11 @@ export function createBuffer() { buffer.push(item) } appendable = isStringItem + if (isPromise(item) || (isArray(item) && item.hasAsync)) { + // promise, or child buffer with async, mark as async. + // this allows skipping unnecessary await ticks during unroll stage + buffer.hasAsync = true + } } } } diff --git a/packages/server-renderer/src/renderToStream.ts b/packages/server-renderer/src/renderToStream.ts index 63b38952254..4952b51c267 100644 --- a/packages/server-renderer/src/renderToStream.ts +++ b/packages/server-renderer/src/renderToStream.ts @@ -16,15 +16,33 @@ async function unrollBuffer( buffer: SSRBuffer, stream: Readable ): Promise { + if (buffer.hasAsync) { + for (let i = 0; i < buffer.length; i++) { + let item = buffer[i] + if (isPromise(item)) { + item = await item + } + if (isString(item)) { + stream.push(item) + } else { + await unrollBuffer(item, stream) + } + } + } else { + // sync buffer can be more efficiently unrolled without unnecessary await + // ticks + unrollBufferSync(buffer, stream) + } +} + +function unrollBufferSync(buffer: SSRBuffer, stream: Readable) { for (let i = 0; i < buffer.length; i++) { let item = buffer[i] - if (isPromise(item)) { - item = await item - } if (isString(item)) { stream.push(item) } else { - await unrollBuffer(item, stream) + // since this is a sync buffer, child buffers are never promises + unrollBufferSync(item as SSRBuffer, stream) } } } diff --git a/packages/server-renderer/src/renderToString.ts b/packages/server-renderer/src/renderToString.ts index 68051da82e8..7e696a6e7f8 100644 --- a/packages/server-renderer/src/renderToString.ts +++ b/packages/server-renderer/src/renderToString.ts @@ -12,16 +12,36 @@ import { SSRContext, renderComponentVNode, SSRBuffer } from './render' const { isVNode } = ssrUtils async function unrollBuffer(buffer: SSRBuffer): Promise { + if (buffer.hasAsync) { + let ret = '' + for (let i = 0; i < buffer.length; i++) { + let item = buffer[i] + if (isPromise(item)) { + item = await item + } + if (isString(item)) { + ret += item + } else { + ret += await unrollBuffer(item) + } + } + return ret + } else { + // sync buffer can be more efficiently unrolled without unnecessary await + // ticks + return unrollBufferSync(buffer) + } +} + +function unrollBufferSync(buffer: SSRBuffer): string { let ret = '' for (let i = 0; i < buffer.length; i++) { let item = buffer[i] - if (isPromise(item)) { - item = await item - } if (isString(item)) { ret += item } else { - ret += await unrollBuffer(item as SSRBuffer) + // since this is a sync buffer, child buffers are never promises + ret += unrollBufferSync(item as SSRBuffer) } } return ret