diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts index ae7e80a40c2..5af8c410dd6 100644 --- a/packages/runtime-core/src/componentOptions.ts +++ b/packages/runtime-core/src/componentOptions.ts @@ -103,6 +103,7 @@ export interface ComponentOptionsBase< directives?: Record inheritAttrs?: boolean emits?: (E | EE[]) & ThisType + serverPrefetch?(): Promise // Internal ------------------------------------------------------------------ diff --git a/packages/server-renderer/__tests__/renderToStream.spec.ts b/packages/server-renderer/__tests__/renderToStream.spec.ts index 4c9466b6725..410be382e5f 100644 --- a/packages/server-renderer/__tests__/renderToStream.spec.ts +++ b/packages/server-renderer/__tests__/renderToStream.spec.ts @@ -15,6 +15,7 @@ import { renderToStream as _renderToStream } from '../src/renderToStream' import { Readable } from 'stream' import { ssrRenderSlot } from '../src/helpers/ssrRenderSlot' import { ssrRenderComponent } from '../src/helpers/ssrRenderComponent' + const promisifyStream = (stream: Readable) => { return new Promise((resolve, reject) => { let result = '' @@ -599,4 +600,23 @@ describe('ssr: renderToStream', () => { ) }) }) + + test('serverPrefetch', async () => { + const msg = Promise.resolve('hello') + const app = createApp({ + data() { + return { + msg: '' + } + }, + async serverPrefetch() { + this.msg = await msg + }, + render() { + return h('div', this.msg) + } + }) + const html = await renderToStream(app) + expect(html).toBe(`
hello
`) + }) }) diff --git a/packages/server-renderer/__tests__/renderToString.spec.ts b/packages/server-renderer/__tests__/renderToString.spec.ts index dfaab64c2c6..25704eeee46 100644 --- a/packages/server-renderer/__tests__/renderToString.spec.ts +++ b/packages/server-renderer/__tests__/renderToString.spec.ts @@ -14,6 +14,7 @@ import { escapeHtml } from '@vue/shared' import { renderToString } from '../src/renderToString' import { ssrRenderSlot, SSRSlot } from '../src/helpers/ssrRenderSlot' import { ssrRenderComponent } from '../src/helpers/ssrRenderComponent' + describe('ssr: renderToString', () => { test('should apply app context', async () => { const app = createApp({ @@ -580,4 +581,23 @@ describe('ssr: renderToString', () => { ).toHaveBeenWarned() }) }) + + test('serverPrefetch', async () => { + const msg = Promise.resolve('hello') + const app = createApp({ + data() { + return { + msg: '' + } + }, + async serverPrefetch() { + this.msg = await msg + }, + render() { + return h('div', this.msg) + } + }) + const html = await renderToString(app) + expect(html).toBe(`
hello
`) + }) }) diff --git a/packages/server-renderer/src/render.ts b/packages/server-renderer/src/render.ts index 0cb20625b09..44ec2be14fe 100644 --- a/packages/server-renderer/src/render.ts +++ b/packages/server-renderer/src/render.ts @@ -2,6 +2,7 @@ import { Comment, Component, ComponentInternalInstance, + ComponentOptions, DirectiveBinding, Fragment, mergeProps, @@ -84,12 +85,20 @@ export function renderComponentVNode( ): SSRBuffer | Promise { const instance = createComponentInstance(vnode, parentComponent, null) const res = setupComponent(instance, true /* isSSR */) - if (isPromise(res)) { - return res - .catch(err => { - warn(`[@vue/server-renderer]: Uncaught error in async setup:\n`, err) + const hasAsyncSetup = isPromise(res) + const prefetch = (vnode.type as ComponentOptions).serverPrefetch + if (hasAsyncSetup || prefetch) { + let p = hasAsyncSetup + ? (res as Promise).catch(err => { + warn(`[@vue/server-renderer]: Uncaught error in async setup:\n`, err) + }) + : Promise.resolve() + if (prefetch) { + p = p.then(() => prefetch.call(instance.proxy)).catch(err => { + warn(`[@vue/server-renderer]: Uncaught error in serverPrefetch:\n`, err) }) - .then(() => renderComponentSubTree(instance)) + } + return p.then(() => renderComponentSubTree(instance)) } else { return renderComponentSubTree(instance) }