Skip to content

fix(ssr): call serverPrefetch hooks in mixins(fix: vuejs#2889) #2902

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions packages/runtime-core/src/apiLifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,17 @@ export function injectHook(
export const createHook = <T extends Function = () => any>(
lifecycle: LifecycleHooks
) => (hook: T, target: ComponentInternalInstance | null = currentInstance) =>
// post-create lifecycle registrations are noops during SSR
!isInSSRComponentSetup && injectHook(lifecycle, hook, target)
// post-create lifecycle registrations are noops during SSR (except for serverPrefetch)
(!isInSSRComponentSetup || lifecycle === LifecycleHooks.SERVER_PREFETCH) &&
injectHook(lifecycle, hook, target)

export const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT)
export const onMounted = createHook(LifecycleHooks.MOUNTED)
export const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE)
export const onUpdated = createHook(LifecycleHooks.UPDATED)
export const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT)
export const onUnmounted = createHook(LifecycleHooks.UNMOUNTED)
export const onServerPrefetch = createHook(LifecycleHooks.SERVER_PREFETCH)

export type DebuggerHook = (e: DebuggerEvent) => void
export const onRenderTriggered = createHook<DebuggerHook>(
Expand Down
10 changes: 8 additions & 2 deletions packages/runtime-core/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ export const enum LifecycleHooks {
ACTIVATED = 'a',
RENDER_TRIGGERED = 'rtg',
RENDER_TRACKED = 'rtc',
ERROR_CAPTURED = 'ec'
ERROR_CAPTURED = 'ec',
SERVER_PREFETCH = 'sp'
}

export interface SetupContext<E = EmitsOptions> {
Expand Down Expand Up @@ -396,6 +397,10 @@ export interface ComponentInternalInstance {
* @internal
*/
[LifecycleHooks.ERROR_CAPTURED]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.SERVER_PREFETCH]: LifecycleHook
}

const emptyAppContext = createAppContext()
Expand Down Expand Up @@ -476,7 +481,8 @@ export function createComponentInstance(
a: null,
rtg: null,
rtc: null,
ec: null
ec: null,
sp: null
}
if (__DEV__) {
instance.ctx = createRenderContext(instance)
Expand Down
5 changes: 5 additions & 0 deletions packages/runtime-core/src/componentOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
onActivated,
onDeactivated,
onRenderTriggered,
onServerPrefetch,
DebuggerHook,
ErrorCapturedHook
} from './apiLifecycle'
Expand Down Expand Up @@ -504,6 +505,7 @@ export function applyOptions(
renderTracked,
renderTriggered,
errorCaptured,
serverPrefetch,
// public API
expose
} = options
Expand Down Expand Up @@ -781,6 +783,9 @@ export function applyOptions(
if (unmounted) {
onUnmounted(unmounted.bind(publicThis))
}
if (serverPrefetch) {
onServerPrefetch(serverPrefetch.bind(publicThis))
}

if (isArray(expose)) {
if (!asMixin) {
Expand Down
58 changes: 58 additions & 0 deletions packages/server-renderer/__tests__/render.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -715,5 +715,63 @@ function testRender(type: string, render: typeof renderToString) {
const html = await render(app)
expect(html).toBe(`<div>hello</div>`)
})

test('mixed in serverPrefetch', async () => {
const msg = Promise.resolve('hello')
const app = createApp({
data() {
return {
msg: ''
}
},
mixins: [
{
async serverPrefetch() {
this.msg = await msg
}
}
],
render() {
return h('div', this.msg)
}
})
const html = await render(app)
expect(html).toBe(`<div>hello</div>`)
})

test('many serverPrefetch', async () => {
const foo = Promise.resolve('foo')
const bar = Promise.resolve('bar')
const baz = Promise.resolve('baz')
const app = createApp({
data() {
return {
foo: '',
bar: '',
baz: ''
}
},
mixins: [
{
async serverPrefetch() {
this.foo = await foo
}
},
{
async serverPrefetch() {
this.bar = await bar
}
}
],
async serverPrefetch() {
this.baz = await baz
},
render() {
return h('div', `${this.foo}${this.bar}${this.baz}`)
}
})
const html = await render(app)
expect(html).toBe(`<div>foobarbaz</div>`)
})
})
}
21 changes: 14 additions & 7 deletions packages/server-renderer/src/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
Comment,
Component,
ComponentInternalInstance,
ComponentOptions,
DirectiveBinding,
Fragment,
mergeProps,
Expand Down Expand Up @@ -85,17 +84,25 @@ export function renderComponentVNode(
const instance = createComponentInstance(vnode, parentComponent, null)
const res = setupComponent(instance, true /* isSSR */)
const hasAsyncSetup = isPromise(res)
const prefetch = (vnode.type as ComponentOptions).serverPrefetch
if (hasAsyncSetup || prefetch) {
const prefetches = instance.sp
if (hasAsyncSetup || prefetches) {
let p = hasAsyncSetup
? (res as Promise<void>).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)
})
if (prefetches) {
p = p
.then(() =>
Promise.all(prefetches.map(prefetch => prefetch.call(instance.proxy)))
)
.then(() => undefined)
.catch(err => {
warn(
`[@vue/server-renderer]: Uncaught error in serverPrefetch:\n`,
err
)
})
}
return p.then(() => renderComponentSubTree(instance))
} else {
Expand Down