--- /dev/null
+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>`)
+ })
+})
setCurrentRenderingInstance,
setupComponent,
renderComponentRoot,
- normalizeVNode
+ normalizeVNode,
+ normalizeSuspenseChildren
} = ssrUtils
// Each component has a buffer array.
} 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:',
}
}
}
+
+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()
+ }
+}