Skip to content

Commit

Permalink
feat(server-renderer): render suspense in vnode mode (#727)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsseng authored Mar 9, 2020
1 parent e12ddd9 commit 589aeb4
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 4 deletions.
2 changes: 1 addition & 1 deletion packages/runtime-core/src/components/Suspense.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ function createSuspenseBoundary<HostNode, HostElement>(
return suspense
}

function normalizeSuspenseChildren(
export function normalizeSuspenseChildren(
vnode: VNode
): {
content: VNode
Expand Down
4 changes: 3 additions & 1 deletion packages/runtime-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -122,7 +123,8 @@ const _ssrUtils = {
renderComponentRoot,
setCurrentRenderingInstance,
isVNode,
normalizeVNode
normalizeVNode,
normalizeSuspenseChildren
}

export const ssrUtils = (__NODE_JS__ ? _ssrUtils : null) as typeof _ssrUtils
Expand Down
110 changes: 110 additions & 0 deletions packages/server-renderer/__tests__/ssrSuspense.spec.ts
Original file line number Diff line number Diff line change
@@ -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>`)
})
})
21 changes: 19 additions & 2 deletions packages/server-renderer/src/renderToString.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ const {
setCurrentRenderingInstance,
setupComponent,
renderComponentRoot,
normalizeVNode
normalizeVNode,
normalizeSuspenseChildren
} = ssrUtils

// Each component has a buffer array.
Expand Down Expand Up @@ -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:',
Expand Down Expand Up @@ -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()
}
}

0 comments on commit 589aeb4

Please sign in to comment.