perf(ssr): avoid unnecessary async overhead

This commit is contained in:
Evan You 2020-01-26 16:13:12 -05:00
parent 8c892e0392
commit 297282a812
2 changed files with 43 additions and 22 deletions

View File

@ -337,7 +337,9 @@ function setupStatefulComponent(
if (isPromise(setupResult)) { if (isPromise(setupResult)) {
if (__SSR__) { if (__SSR__) {
// return the promise so server-renderer can wait on it // return the promise so server-renderer can wait on it
return setupResult return setupResult.then(resolvedResult => {
handleSetupResult(instance, resolvedResult, parentSuspense)
})
} else if (__FEATURE_SUSPENSE__) { } else if (__FEATURE_SUSPENSE__) {
// async setup returned Promise. // async setup returned Promise.
// bail here and wait for re-entry. // bail here and wait for re-entry.

View File

@ -7,17 +7,21 @@ import {
VNode, VNode,
createVNode createVNode
} from 'vue' } from 'vue'
import { isString } from '@vue/shared' import { isString, isPromise, isArray } from '@vue/shared'
type SSRBuffer = SSRBufferItem[] type SSRBuffer = SSRBufferItem[]
type SSRBufferItem = string | Promise<SSRBuffer> type SSRBufferItem = string | ResolvedSSRBuffer | Promise<SSRBuffer>
type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[] type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[]
function createSSRBuffer() { function createBuffer() {
let appendable = false let appendable = false
let hasAsync = false
const buffer: SSRBuffer = [] const buffer: SSRBuffer = []
return { return {
buffer, buffer,
hasAsync() {
return hasAsync
},
push(item: SSRBufferItem) { push(item: SSRBufferItem) {
const isStringItem = isString(item) const isStringItem = isString(item)
if (appendable && isStringItem) { if (appendable && isStringItem) {
@ -26,18 +30,14 @@ function createSSRBuffer() {
buffer.push(item) buffer.push(item)
} }
appendable = isStringItem appendable = isStringItem
if (!isStringItem && !isArray(item)) {
// promise
hasAsync = true
}
} }
} }
} }
export async function renderToString(app: App): Promise<string> {
const resolvedBuffer = (await renderComponent(
app._component,
app._props
)) as ResolvedSSRBuffer
return unrollBuffer(resolvedBuffer)
}
function unrollBuffer(buffer: ResolvedSSRBuffer): string { function unrollBuffer(buffer: ResolvedSSRBuffer): string {
let ret = '' let ret = ''
for (let i = 0; i < buffer.length; i++) { for (let i = 0; i < buffer.length; i++) {
@ -51,20 +51,35 @@ function unrollBuffer(buffer: ResolvedSSRBuffer): string {
return ret return ret
} }
export async function renderComponent( export async function renderToString(app: App): Promise<string> {
const resolvedBuffer = (await renderComponent(
app._component,
app._props
)) as ResolvedSSRBuffer
return unrollBuffer(resolvedBuffer)
}
export function renderComponent(
comp: Component, comp: Component,
props: Record<string, any> | null = null, props: Record<string, any> | null = null,
children: VNode['children'] = null, children: VNode['children'] = null,
parentComponent: ComponentInternalInstance | null = null parentComponent: ComponentInternalInstance | null = null
): Promise<SSRBuffer> { ): ResolvedSSRBuffer | Promise<SSRBuffer> {
// 1. create component buffer
const { buffer, push } = createSSRBuffer()
// 2. create actual instance
const vnode = createVNode(comp, props, children) const vnode = createVNode(comp, props, children)
const instance = createComponentInstance(vnode, parentComponent) const instance = createComponentInstance(vnode, parentComponent)
await setupComponent(instance, null) const res = setupComponent(instance, null)
if (isPromise(res)) {
return res.then(() => innerRenderComponent(comp, instance))
} else {
return innerRenderComponent(comp, instance)
}
}
function innerRenderComponent(
comp: Component,
instance: ComponentInternalInstance
): ResolvedSSRBuffer | Promise<SSRBuffer> {
const { buffer, push, hasAsync } = createBuffer()
if (typeof comp === 'function') { if (typeof comp === 'function') {
// TODO FunctionalComponent // TODO FunctionalComponent
} else { } else {
@ -77,7 +92,11 @@ export async function renderComponent(
// TODO warn component missing render function // TODO warn component missing render function
} }
} }
// TS can't figure this out due to recursive occurance of Promise in type // If the current component's buffer contains any Promise from async children,
// @ts-ignore // then it must return a Promise too. Otherwise this is a component that
return Promise.all(buffer) // contains only sync children so we can avoid the async book-keeping overhead.
return hasAsync()
? // TS can't figure out the typing due to recursive appearance of Promise
Promise.all(buffer as any)
: (buffer as ResolvedSSRBuffer)
} }