Skip to content

Commit

Permalink
perf(ssr): avoid unnecessary async overhead
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jan 27, 2020
1 parent 8c892e0 commit 297282a
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 22 deletions.
4 changes: 3 additions & 1 deletion packages/runtime-core/src/component.ts
Expand Up @@ -337,7 +337,9 @@ function setupStatefulComponent(
if (isPromise(setupResult)) {
if (__SSR__) {
// return the promise so server-renderer can wait on it
return setupResult
return setupResult.then(resolvedResult => {
handleSetupResult(instance, resolvedResult, parentSuspense)
})
} else if (__FEATURE_SUSPENSE__) {
// async setup returned Promise.
// bail here and wait for re-entry.
Expand Down
61 changes: 40 additions & 21 deletions packages/server-renderer/src/index.ts
Expand Up @@ -7,17 +7,21 @@ import {
VNode,
createVNode
} from 'vue'
import { isString } from '@vue/shared'
import { isString, isPromise, isArray } from '@vue/shared'

type SSRBuffer = SSRBufferItem[]
type SSRBufferItem = string | Promise<SSRBuffer>
type SSRBufferItem = string | ResolvedSSRBuffer | Promise<SSRBuffer>
type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[]

function createSSRBuffer() {
function createBuffer() {
let appendable = false
let hasAsync = false
const buffer: SSRBuffer = []
return {
buffer,
hasAsync() {
return hasAsync
},
push(item: SSRBufferItem) {
const isStringItem = isString(item)
if (appendable && isStringItem) {
Expand All @@ -26,18 +30,14 @@ function createSSRBuffer() {
buffer.push(item)
}
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 {
let ret = ''
for (let i = 0; i < buffer.length; i++) {
Expand All @@ -51,20 +51,35 @@ function unrollBuffer(buffer: ResolvedSSRBuffer): string {
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,
props: Record<string, any> | null = null,
children: VNode['children'] = null,
parentComponent: ComponentInternalInstance | null = null
): Promise<SSRBuffer> {
// 1. create component buffer
const { buffer, push } = createSSRBuffer()

// 2. create actual instance
): ResolvedSSRBuffer | Promise<SSRBuffer> {
const vnode = createVNode(comp, props, children)
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') {
// TODO FunctionalComponent
} else {
Expand All @@ -77,7 +92,11 @@ export async function renderComponent(
// TODO warn component missing render function
}
}
// TS can't figure this out due to recursive occurance of Promise in type
// @ts-ignore
return Promise.all(buffer)
// 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()
? // TS can't figure out the typing due to recursive appearance of Promise
Promise.all(buffer as any)
: (buffer as ResolvedSSRBuffer)
}

0 comments on commit 297282a

Please sign in to comment.