perf(ssr): avoid unnecessary async overhead
This commit is contained in:
parent
8c892e0392
commit
297282a812
@ -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.
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user