Skip to content

Commit 0dcc98f

Browse files
authored
feat(suspense): support rendering of Vapor components (#14157)
1 parent 5db15cf commit 0dcc98f

File tree

10 files changed

+808
-124
lines changed

10 files changed

+808
-124
lines changed

packages/runtime-core/src/apiCreateApp.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { warn } from './warning'
2727
import type { VNode } from './vnode'
2828
import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
2929
import { NO, extend, hasOwn, isFunction, isObject } from '@vue/shared'
30-
import { type TransitionHooks, version } from '.'
30+
import { type SuspenseBoundary, type TransitionHooks, version } from '.'
3131
import { installAppCompatProperties } from './compat/global'
3232
import type { NormalizedPropsOptions } from './componentProps'
3333
import type { ObjectEmitsOptions } from './componentEmits'
@@ -187,6 +187,7 @@ export interface VaporInteropInterface {
187187
container: any,
188188
anchor: any,
189189
parentComponent: ComponentInternalInstance | null,
190+
parentSuspense: SuspenseBoundary | null,
190191
): GenericComponentInstance // VaporComponentInstance
191192
update(n1: VNode, n2: VNode, shouldUpdate: boolean): void
192193
unmount(vnode: VNode, doRemove?: boolean): void
@@ -198,6 +199,7 @@ export interface VaporInteropInterface {
198199
container: any,
199200
anchor: any,
200201
parentComponent: ComponentInternalInstance | null,
202+
parentSuspense: SuspenseBoundary | null,
201203
): Node
202204
hydrateSlot(vnode: VNode, node: any): Node
203205
activate(

packages/runtime-core/src/component.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,19 @@ export interface GenericComponentInstance {
461461
* @internal
462462
*/
463463
suspense: SuspenseBoundary | null
464+
/**
465+
* suspense pending batch id
466+
* @internal
467+
*/
468+
suspenseId: number
469+
/**
470+
* @internal
471+
*/
472+
asyncDep: Promise<any> | null
473+
/**
474+
* @internal
475+
*/
476+
asyncResolved: boolean
464477
/**
465478
* `updateTeleportCssVars`
466479
* For updating css vars on contained teleports

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

Lines changed: 12 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,22 @@ import {
1111
openBlock,
1212
} from '../vnode'
1313
import { ShapeFlags, isArray, isFunction, toNumber } from '@vue/shared'
14-
import { type ComponentInternalInstance, handleSetupResult } from '../component'
14+
import type {
15+
ComponentInternalInstance,
16+
GenericComponentInstance,
17+
} from '../component'
1518
import type { Slots } from '../componentSlots'
1619
import {
1720
type ElementNamespace,
1821
MoveType,
1922
type RendererElement,
2023
type RendererInternals,
2124
type RendererNode,
22-
type SetupRenderEffectFn,
2325
queuePostRenderEffect,
2426
} from '../renderer'
2527
import { queuePostFlushCb } from '../scheduler'
2628
import { filterSingleRoot, updateHOCHostEl } from '../componentRenderUtils'
27-
import {
28-
assertNumber,
29-
popWarningContext,
30-
pushWarningContext,
31-
warn,
32-
} from '../warning'
29+
import { assertNumber, warn } from '../warning'
3330
import { ErrorCodes, handleError } from '../errorHandling'
3431
import { NULL_DYNAMIC_COMPONENT } from '../helpers/resolveAssets'
3532

@@ -436,9 +433,8 @@ export interface SuspenseBoundary {
436433
): void
437434
next(): RendererNode | null
438435
registerDep(
439-
instance: ComponentInternalInstance,
440-
setupRenderEffect: SetupRenderEffectFn,
441-
optimized: boolean,
436+
instance: GenericComponentInstance,
437+
onResolve: (setupResult: unknown) => void,
442438
): void
443439
unmount(parentSuspense: SuspenseBoundary | null, doRemove?: boolean): void
444440
}
@@ -474,7 +470,7 @@ function createSuspenseBoundary(
474470
m: move,
475471
um: unmount,
476472
n: next,
477-
o: { parentNode, remove },
473+
o: { parentNode },
478474
} = rendererInternals
479475

480476
// if set `suspensible: true`, set the current suspense as a dep of parent suspense
@@ -701,12 +697,12 @@ function createSuspenseBoundary(
701697
return suspense.activeBranch && next(suspense.activeBranch)
702698
},
703699

704-
registerDep(instance, setupRenderEffect, optimized) {
700+
registerDep(instance, onResolve) {
705701
const isInPendingSuspense = !!suspense.pendingBranch
706702
if (isInPendingSuspense) {
707703
suspense.deps++
708704
}
709-
const hydratedEl = instance.vnode.el
705+
710706
instance
711707
.asyncDep!.catch(err => {
712708
handleError(err, instance, ErrorCodes.SETUP_FUNCTION)
@@ -723,40 +719,8 @@ function createSuspenseBoundary(
723719
}
724720
// retry from this component
725721
instance.asyncResolved = true
726-
const { vnode } = instance
727-
if (__DEV__) {
728-
pushWarningContext(vnode)
729-
}
730-
handleSetupResult(instance, asyncSetupResult, false)
731-
if (hydratedEl) {
732-
// vnode may have been replaced if an update happened before the
733-
// async dep is resolved.
734-
vnode.el = hydratedEl
735-
}
736-
const placeholder = !hydratedEl && instance.subTree.el
737-
setupRenderEffect(
738-
instance,
739-
vnode,
740-
// component may have been moved before resolve.
741-
// if this is not a hydration, instance.subTree will be the comment
742-
// placeholder.
743-
parentNode(hydratedEl || instance.subTree.el!)!,
744-
// anchor will not be used if this is hydration, so only need to
745-
// consider the comment placeholder case.
746-
hydratedEl ? null : next(instance.subTree),
747-
suspense,
748-
namespace,
749-
optimized,
750-
)
751-
if (placeholder) {
752-
// clean up placeholder reference
753-
vnode.placeholder = null
754-
remove(placeholder)
755-
}
756-
updateHOCHostEl(instance, vnode.el)
757-
if (__DEV__) {
758-
popWarningContext()
759-
}
722+
onResolve(asyncSetupResult)
723+
760724
// only decrease deps count if suspense is not already resolved
761725
if (isInPendingSuspense && --suspense.deps === 0) {
762726
suspense.resolve()

packages/runtime-core/src/hydration.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ export function createHydrationFunctions(
318318
container,
319319
null,
320320
parentComponent,
321+
parentSuspense,
321322
)
322323
} else {
323324
mountComponent(

packages/runtime-core/src/renderer.ts

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
type LifecycleHook,
2525
createComponentInstance,
2626
getComponentPublicInstance,
27+
handleSetupResult,
2728
setupComponent,
2829
} from './component'
2930
import {
@@ -1186,6 +1187,7 @@ function baseCreateRenderer(
11861187
container,
11871188
anchor,
11881189
parentComponent,
1190+
parentSuspense,
11891191
)
11901192
}
11911193
} else {
@@ -1272,9 +1274,45 @@ function baseCreateRenderer(
12721274
// setup() is async. This component relies on async logic to be resolved
12731275
// before proceeding
12741276
if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
1275-
parentSuspense &&
1276-
parentSuspense.registerDep(instance, setupRenderEffect, optimized)
1277-
1277+
if (parentSuspense) {
1278+
const hydratedEl = instance.vnode.el
1279+
parentSuspense.registerDep(instance, setupResult => {
1280+
const { vnode } = instance
1281+
if (__DEV__) {
1282+
pushWarningContext(vnode)
1283+
}
1284+
handleSetupResult(instance, setupResult, false)
1285+
if (hydratedEl) {
1286+
// vnode may have been replaced if an update happened before the
1287+
// async dep is resolved.
1288+
vnode.el = hydratedEl
1289+
}
1290+
const placeholder = !hydratedEl && instance.subTree.el
1291+
setupRenderEffect(
1292+
instance,
1293+
vnode,
1294+
// component may have been moved before resolve.
1295+
// if this is not a hydration, instance.subTree will be the comment
1296+
// placeholder.
1297+
hostParentNode(hydratedEl || instance.subTree.el!)!,
1298+
// anchor will not be used if this is hydration, so only need to
1299+
// consider the comment placeholder case.
1300+
hydratedEl ? null : getNextHostNode(instance.subTree),
1301+
parentSuspense,
1302+
namespace,
1303+
optimized,
1304+
)
1305+
if (placeholder) {
1306+
// clean up placeholder reference
1307+
vnode.placeholder = null
1308+
hostRemove(placeholder)
1309+
}
1310+
updateHOCHostEl(instance, vnode.el)
1311+
if (__DEV__) {
1312+
popWarningContext()
1313+
}
1314+
})
1315+
}
12781316
// Give it a placeholder if this is not hydration
12791317
// TODO handle self-defined fallback
12801318
if (!initialVNode.el) {

0 commit comments

Comments
 (0)