Browse Source

perf(ssr): avoid unnecessary await ticks when unrolling sync buffers

Evan You 5 years ago
parent
commit
30584bcc61

+ 8 - 2
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<SSRBuffer>
 export type PushFn = (item: SSRBufferItem) => void
 export type Props = Record<string, unknown>
@@ -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
+      }
     }
   }
 }

+ 22 - 4
packages/server-renderer/src/renderToStream.ts

@@ -16,15 +16,33 @@ async function unrollBuffer(
   buffer: SSRBuffer,
   stream: Readable
 ): Promise<void> {
+  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)
     }
   }
 }

+ 24 - 4
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<string> {
+  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