From a79a3078f5b7740e274bd5099f5c3f65351a6869 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Sat, 15 Feb 2020 09:02:27 +0300 Subject: [PATCH 1/2] feat(server-renderer): render suspense --- .../__tests__/ssrSuspense.spec.ts | 110 ++++++++++++++++++ .../server-renderer/src/renderToString.ts | 37 +++++- 2 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 packages/server-renderer/__tests__/ssrSuspense.spec.ts diff --git a/packages/server-renderer/__tests__/ssrSuspense.spec.ts b/packages/server-renderer/__tests__/ssrSuspense.spec.ts new file mode 100644 index 00000000000..b411e14a780 --- /dev/null +++ b/packages/server-renderer/__tests__/ssrSuspense.spec.ts @@ -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(`
async
`) + }) + + test('fallback', async () => { + const Comp = { + render() { + return h(Suspense, null, { + default: h(RejectingAsync), + fallback: h('div', 'fallback') + }) + } + } + + expect(await renderToString(createApp(Comp))).toBe(`
fallback
`) + }) + + 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( + `
async
async
` + ) + }) + + 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(`
fallback
`) + }) + + 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( + `
async
fallback 2
` + ) + }) + + 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(`
fallback 1
`) + }) +}) diff --git a/packages/server-renderer/src/renderToString.ts b/packages/server-renderer/src/renderToString.ts index b181b5422cb..fc8dd18f60a 100644 --- a/packages/server-renderer/src/renderToString.ts +++ b/packages/server-renderer/src/renderToString.ts @@ -224,6 +224,27 @@ function ssrCompile( return (compileCache[template] = Function('require', code)(require)) } +function normalizeSuspenseChildren( + vnode: VNode +): { + content: VNode + fallback: VNode +} { + const { shapeFlag, children } = vnode + if (shapeFlag & ShapeFlags.SLOTS_CHILDREN) { + const { default: d, fallback } = children as Slots + return { + content: normalizeVNode(isFunction(d) ? d() : d), + fallback: normalizeVNode(isFunction(fallback) ? fallback() : fallback) + } + } else { + return { + content: normalizeVNode(children as any), + fallback: normalizeVNode(null) + } + } +} + function renderVNode( push: PushFn, vnode: VNode, @@ -248,7 +269,21 @@ function renderVNode( } else if (shapeFlag & ShapeFlags.PORTAL) { renderPortal(vnode, parentComponent) } else if (shapeFlag & ShapeFlags.SUSPENSE) { - // TODO + const { content, fallback } = normalizeSuspenseChildren(vnode) + + push( + (async () => { + try { + const suspenseBuffer = createBuffer() + renderVNode(suspenseBuffer.push, content, parentComponent) + return await Promise.all(suspenseBuffer.buffer) + } catch { + const fallbackBuffer = createBuffer() + renderVNode(fallbackBuffer.push, fallback, parentComponent) + return await Promise.all(fallbackBuffer.buffer) + } + })() + ) } else { console.warn( '[@vue/server-renderer] Invalid VNode type:', From 4ab6ea663aa0b976d7a5087b6e84013fee306499 Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 9 Mar 2020 18:16:24 -0400 Subject: [PATCH 2/2] refactor: rebase on master + refactor --- .../runtime-core/src/components/Suspense.ts | 2 +- packages/runtime-core/src/index.ts | 4 +- .../server-renderer/src/renderToString.ts | 56 +++++++------------ 3 files changed, 23 insertions(+), 39 deletions(-) diff --git a/packages/runtime-core/src/components/Suspense.ts b/packages/runtime-core/src/components/Suspense.ts index b3a6b31c6d4..a783502fc77 100644 --- a/packages/runtime-core/src/components/Suspense.ts +++ b/packages/runtime-core/src/components/Suspense.ts @@ -449,7 +449,7 @@ function createSuspenseBoundary( return suspense } -function normalizeSuspenseChildren( +export function normalizeSuspenseChildren( vnode: VNode ): { content: VNode diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index d0b8444a8fd..9717174bbe3 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -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 diff --git a/packages/server-renderer/src/renderToString.ts b/packages/server-renderer/src/renderToString.ts index fc8dd18f60a..8051c204b4f 100644 --- a/packages/server-renderer/src/renderToString.ts +++ b/packages/server-renderer/src/renderToString.ts @@ -36,7 +36,8 @@ const { setCurrentRenderingInstance, setupComponent, renderComponentRoot, - normalizeVNode + normalizeVNode, + normalizeSuspenseChildren } = ssrUtils // Each component has a buffer array. @@ -224,27 +225,6 @@ function ssrCompile( return (compileCache[template] = Function('require', code)(require)) } -function normalizeSuspenseChildren( - vnode: VNode -): { - content: VNode - fallback: VNode -} { - const { shapeFlag, children } = vnode - if (shapeFlag & ShapeFlags.SLOTS_CHILDREN) { - const { default: d, fallback } = children as Slots - return { - content: normalizeVNode(isFunction(d) ? d() : d), - fallback: normalizeVNode(isFunction(fallback) ? fallback() : fallback) - } - } else { - return { - content: normalizeVNode(children as any), - fallback: normalizeVNode(null) - } - } -} - function renderVNode( push: PushFn, vnode: VNode, @@ -269,21 +249,7 @@ function renderVNode( } else if (shapeFlag & ShapeFlags.PORTAL) { renderPortal(vnode, parentComponent) } else if (shapeFlag & ShapeFlags.SUSPENSE) { - const { content, fallback } = normalizeSuspenseChildren(vnode) - - push( - (async () => { - try { - const suspenseBuffer = createBuffer() - renderVNode(suspenseBuffer.push, content, parentComponent) - return await Promise.all(suspenseBuffer.buffer) - } catch { - const fallbackBuffer = createBuffer() - renderVNode(fallbackBuffer.push, fallback, parentComponent) - return await Promise.all(fallbackBuffer.buffer) - } - })() - ) + push(renderSuspense(vnode, parentComponent)) } else { console.warn( '[@vue/server-renderer] Invalid VNode type:', @@ -400,3 +366,19 @@ async function resolvePortals(context: SSRContext) { } } } + +async function renderSuspense( + vnode: VNode, + parentComponent: ComponentInternalInstance +): Promise { + 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() + } +}