Skip to content

Commit 519d324

Browse files
committed
feat(ssr-render): suspense supports server-side triggering of onResolve hooks
1 parent 46ee8fd commit 519d324

File tree

6 files changed

+40
-37
lines changed

6 files changed

+40
-37
lines changed

packages/runtime-core/src/components/Suspense.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,7 @@ export type ssrSuspenseBoundary = {
838838
deps: number
839839
resolve: (node: VNode) => void
840840
vnode: VNode
841+
parentSuspense: null | ssrSuspenseBoundary
841842
} & SuspenseBoundary
842843
export function createSSRSuspenseBoundary(vnode: VNode) {
843844
return {
@@ -846,6 +847,7 @@ export function createSSRSuspenseBoundary(vnode: VNode) {
846847
// invoke @resolve event
847848
triggerEvent(vnode, 'onResolve')
848849
},
849-
vnode: vnode
850+
vnode: vnode,
851+
parentSuspense: null
850852
}
851853
}

packages/server-renderer/__tests__/render.spec.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -927,7 +927,8 @@ function testRender(type: string, render: typeof renderToString) {
927927
ssrRenderVNode(
928928
_push,
929929
createVNode(resolveDynamicComponent('B'), null, null),
930-
_parent
930+
_parent,
931+
null
931932
)
932933
}
933934
})

packages/server-renderer/__tests__/ssrSuspense.spec.ts

+3-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createApp, createSSRApp, h, Suspense } from 'vue'
1+
import { createApp, h, Suspense } from 'vue'
22
import { renderToString } from '../src/renderToString'
33
import { expect } from 'vitest'
44

@@ -164,15 +164,13 @@ describe('SSR Suspense', () => {
164164
return h(
165165
Suspense,
166166
{
167-
id: 'ss1',
168167
onResolve
169168
},
170169
{
171170
default: h(
172171
Suspense,
173172
{
174-
id: 'ss2',
175-
onNestedResolve
173+
onResolve: onNestedResolve
176174
},
177175
{
178176
default: h(ResolvingAsync)
@@ -182,9 +180,7 @@ describe('SSR Suspense', () => {
182180
)
183181
}
184182
}
185-
expect(await renderToString(createSSRApp(Comp))).toBe(
186-
`<div><div>async</div><div>async</div></div>`
187-
)
183+
expect(await renderToString(createApp(Comp))).toBe(`<div>async</div>`)
188184
expect(onResolve).toHaveBeenCalledTimes(1)
189185
expect(onNestedResolve).toHaveBeenCalledTimes(1)
190186
})

packages/server-renderer/src/helpers/ssrRenderComponent.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export function ssrRenderComponent(
1212
return renderComponentVNode(
1313
createVNode(comp, props, children),
1414
parentComponent,
15+
null,
1516
slotScopeId
1617
)
1718
}

packages/server-renderer/src/helpers/ssrRenderSlot.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export function ssrRenderSlotInner(
5757
)
5858
if (isArray(ret)) {
5959
// normal slot
60-
renderVNodeChildren(push, ret, parentComponent, slotScopeId)
60+
renderVNodeChildren(push, ret, parentComponent, null, slotScopeId)
6161
} else {
6262
// ssr slot.
6363
// check if the slot renders all comments, in which case use the fallback

packages/server-renderer/src/render.ts

+30-27
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,15 @@ export function createBuffer() {
9191
export function renderComponentVNode(
9292
vnode: VNode,
9393
parentComponent: ComponentInternalInstance | null = null,
94-
parentSuspense: any,
94+
suspense: ssrSuspenseBoundary | null = null,
9595
slotScopeId?: string
9696
): SSRBuffer | Promise<SSRBuffer> {
9797
const instance = createComponentInstance(vnode, parentComponent, null)
9898
const res = setupComponent(instance, true /* isSSR */)
9999
const hasAsyncSetup = isPromise(res)
100100
const prefetches = instance.sp /* LifecycleHooks.SERVER_PREFETCH */
101101
if (hasAsyncSetup || prefetches) {
102-
parentSuspense && parentSuspense.deps++
102+
suspense && suspense.deps++
103103
let p: Promise<unknown> = hasAsyncSetup
104104
? (res as Promise<void>)
105105
: Promise.resolve()
@@ -113,27 +113,32 @@ export function renderComponentVNode(
113113
}
114114

115115
return p.then(() => {
116-
const subtree = renderComponentSubTree(
117-
instance,
118-
parentSuspense,
119-
slotScopeId
120-
)
121-
if (__FEATURE_SUSPENSE__ && parentSuspense) {
122-
parentSuspense.deps--
123-
if (parentSuspense.deps === 0) {
124-
parentSuspense.resolve(parentSuspense.vnode)
116+
const subtree = renderComponentSubTree(instance, suspense, slotScopeId)
117+
if (__FEATURE_SUSPENSE__ && suspense) {
118+
// resolve suspense
119+
suspense.deps--
120+
if (suspense.deps <= 0) {
121+
suspense.resolve(suspense.vnode)
122+
}
123+
124+
// resolve parent suspense
125+
if (suspense.parentSuspense) {
126+
suspense.parentSuspense.deps--
127+
if (suspense.deps <= 0) {
128+
suspense.parentSuspense.resolve(suspense.parentSuspense.vnode)
129+
}
125130
}
126131
}
127132
return subtree
128133
})
129134
} else {
130-
return renderComponentSubTree(instance, parentSuspense, slotScopeId)
135+
return renderComponentSubTree(instance, suspense, slotScopeId)
131136
}
132137
}
133138

134139
function renderComponentSubTree(
135140
instance: ComponentInternalInstance,
136-
parentSuspense: any,
141+
parentSuspense: ssrSuspenseBoundary | null,
137142
slotScopeId?: string
138143
): SSRBuffer | Promise<SSRBuffer> {
139144
const comp = instance.type as Component
@@ -226,6 +231,7 @@ function renderComponentSubTree(
226231
push,
227232
(instance.subTree = renderComponentRoot(instance)),
228233
instance,
234+
parentSuspense,
229235
slotScopeId
230236
)
231237
} else {
@@ -241,7 +247,7 @@ export function renderVNode(
241247
push: PushFn,
242248
vnode: VNode,
243249
parentComponent: ComponentInternalInstance,
244-
parentSuspense: any,
250+
parentSuspense: ssrSuspenseBoundary | null = null,
245251
slotScopeId?: string
246252
) {
247253
const { type, shapeFlag, children } = vnode
@@ -305,27 +311,24 @@ export function renderVNode(
305311
) as ssrSuspenseBoundary
306312
}
307313

308-
// nested ???
309-
// if (parentSuspense) {
310-
// parentSuspense.deps++
311-
// }
314+
// nested suspense
315+
if (parentSuspense) {
316+
parentSuspense.deps++
317+
;(vnode.suspense as ssrSuspenseBoundary).parentSuspense =
318+
parentSuspense
319+
}
312320
renderVNode(
313321
push,
314322
vnode.ssContent!,
315323
parentComponent,
316-
vnode.suspense,
324+
vnode.suspense as ssrSuspenseBoundary,
317325
slotScopeId
318326
)
319327

320328
// sync
321329
if (vnode.suspense && vnode.suspense.deps! <= 0) {
322330
;(vnode.suspense as ssrSuspenseBoundary).resolve(vnode)
323331
}
324-
325-
// nested ???
326-
// if (parentSuspense) {
327-
// parentSuspense.deps--
328-
// }
329332
} else {
330333
warn(
331334
'[@vue/server-renderer] Invalid VNode type:',
@@ -340,7 +343,7 @@ export function renderVNodeChildren(
340343
push: PushFn,
341344
children: VNodeArrayChildren,
342345
parentComponent: ComponentInternalInstance,
343-
parentSuspense: any,
346+
parentSuspense: ssrSuspenseBoundary | null,
344347
slotScopeId: string | undefined
345348
) {
346349
for (let i = 0; i < children.length; i++) {
@@ -358,7 +361,7 @@ function renderElementVNode(
358361
push: PushFn,
359362
vnode: VNode,
360363
parentComponent: ComponentInternalInstance,
361-
parentSuspense: any,
364+
parentSuspense: ssrSuspenseBoundary | null,
362365
slotScopeId: string | undefined
363366
) {
364367
const tag = vnode.type as string
@@ -445,7 +448,7 @@ function renderTeleportVNode(
445448
push: PushFn,
446449
vnode: VNode,
447450
parentComponent: ComponentInternalInstance,
448-
parentSuspense: any,
451+
parentSuspense: ssrSuspenseBoundary | null,
449452
slotScopeId: string | undefined
450453
) {
451454
const target = vnode.props && vnode.props.to

0 commit comments

Comments
 (0)