feat(server-renderer): render suspense in vnode mode (#727)

This commit is contained in:
Dmitry Sharshakov 2020-03-10 01:20:30 +03:00 committed by GitHub
parent e12ddd96ba
commit 589aeb402c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 133 additions and 4 deletions

View File

@ -449,7 +449,7 @@ function createSuspenseBoundary<HostNode, HostElement>(
return suspense
}
function normalizeSuspenseChildren(
export function normalizeSuspenseChildren(
vnode: VNode
): {
content: VNode

View File

@ -114,6 +114,7 @@ import {
setCurrentRenderingInstance
} from './componentRenderUtils'
import { isVNode, normalizeVNode } from './vnode'
import { normalizeSuspenseChildren } from './components/Suspense'
// SSR utils are only exposed in cjs builds.
const _ssrUtils = {
@ -122,7 +123,8 @@ const _ssrUtils = {
renderComponentRoot,
setCurrentRenderingInstance,
isVNode,
normalizeVNode
normalizeVNode,
normalizeSuspenseChildren
}
export const ssrUtils = (__NODE_JS__ ? _ssrUtils : null) as typeof _ssrUtils

View File

@ -0,0 +1,110 @@
import { createApp, h, Suspense } from 'vue'
import { renderToString } from '../src/renderToString'
describe('SSR Suspense', () => {
const ResolvingAsync = {
async setup() {
return () => h('div', 'async')
}
}
const RejectingAsync = {
setup() {
return new Promise((_, reject) => reject())
}
}
test('render', async () => {
const Comp = {
render() {
return h(Suspense, null, {
default: h(ResolvingAsync),
fallback: h('div', 'fallback')
})
}
}
expect(await renderToString(createApp(Comp))).toBe(`<div>async</div>`)
})
test('fallback', async () => {
const Comp = {
render() {
return h(Suspense, null, {
default: h(RejectingAsync),
fallback: h('div', 'fallback')
})
}
}
expect(await renderToString(createApp(Comp))).toBe(`<div>fallback</div>`)
})
test('2 components', async () => {
const Comp = {
render() {
return h(Suspense, null, {
default: h('div', [h(ResolvingAsync), h(ResolvingAsync)]),
fallback: h('div', 'fallback')
})
}
}
expect(await renderToString(createApp(Comp))).toBe(
`<div><div>async</div><div>async</div></div>`
)
})
test('resolving component + rejecting component', async () => {
const Comp = {
render() {
return h(Suspense, null, {
default: h('div', [h(ResolvingAsync), h(RejectingAsync)]),
fallback: h('div', 'fallback')
})
}
}
expect(await renderToString(createApp(Comp))).toBe(`<div>fallback</div>`)
})
test('failing suspense in passing suspense', async () => {
const Comp = {
render() {
return h(Suspense, null, {
default: h('div', [
h(ResolvingAsync),
h(Suspense, null, {
default: h('div', [h(RejectingAsync)]),
fallback: h('div', 'fallback 2')
})
]),
fallback: h('div', 'fallback 1')
})
}
}
expect(await renderToString(createApp(Comp))).toBe(
`<div><div>async</div><div>fallback 2</div></div>`
)
})
test('passing suspense in failing suspense', async () => {
const Comp = {
render() {
return h(Suspense, null, {
default: h('div', [
h(RejectingAsync),
h(Suspense, null, {
default: h('div', [h(ResolvingAsync)]),
fallback: h('div', 'fallback 2')
})
]),
fallback: h('div', 'fallback 1')
})
}
}
expect(await renderToString(createApp(Comp))).toBe(`<div>fallback 1</div>`)
})
})

View File

@ -36,7 +36,8 @@ const {
setCurrentRenderingInstance,
setupComponent,
renderComponentRoot,
normalizeVNode
normalizeVNode,
normalizeSuspenseChildren
} = ssrUtils
// Each component has a buffer array.
@ -248,7 +249,7 @@ function renderVNode(
} else if (shapeFlag & ShapeFlags.PORTAL) {
renderPortal(vnode, parentComponent)
} else if (shapeFlag & ShapeFlags.SUSPENSE) {
// TODO
push(renderSuspense(vnode, parentComponent))
} else {
console.warn(
'[@vue/server-renderer] Invalid VNode type:',
@ -365,3 +366,19 @@ async function resolvePortals(context: SSRContext) {
}
}
}
async function renderSuspense(
vnode: VNode,
parentComponent: ComponentInternalInstance
): Promise<ResolvedSSRBuffer> {
const { content, fallback } = normalizeSuspenseChildren(vnode)
try {
const { push, getBuffer } = createBuffer()
renderVNode(push, content, parentComponent)
return await getBuffer()
} catch {
const { push, getBuffer } = createBuffer()
renderVNode(push, fallback, parentComponent)
return getBuffer()
}
}