Skip to content
4 changes: 3 additions & 1 deletion packages/runtime-core/src/apiCreateApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { warn } from './warning'
import type { VNode } from './vnode'
import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
import { NO, extend, hasOwn, isFunction, isObject } from '@vue/shared'
import { type TransitionHooks, version } from '.'
import { type SuspenseBoundary, type TransitionHooks, version } from '.'
import { installAppCompatProperties } from './compat/global'
import type { NormalizedPropsOptions } from './componentProps'
import type { ObjectEmitsOptions } from './componentEmits'
Expand Down Expand Up @@ -187,6 +187,7 @@ export interface VaporInteropInterface {
container: any,
anchor: any,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
): GenericComponentInstance // VaporComponentInstance
update(n1: VNode, n2: VNode, shouldUpdate: boolean): void
unmount(vnode: VNode, doRemove?: boolean): void
Expand All @@ -198,6 +199,7 @@ export interface VaporInteropInterface {
container: any,
anchor: any,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
): Node
hydrateSlot(vnode: VNode, node: any): Node
activate(
Expand Down
13 changes: 13 additions & 0 deletions packages/runtime-core/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,19 @@ export interface GenericComponentInstance {
* @internal
*/
suspense: SuspenseBoundary | null
/**
* suspense pending batch id
* @internal
*/
suspenseId: number
/**
* @internal
*/
asyncDep: Promise<any> | null
/**
* @internal
*/
asyncResolved: boolean
/**
* `updateTeleportCssVars`
* For updating css vars on contained teleports
Expand Down
55 changes: 8 additions & 47 deletions packages/runtime-core/src/components/Suspense.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,19 @@ import {
openBlock,
} from '../vnode'
import { ShapeFlags, isArray, isFunction, toNumber } from '@vue/shared'
import { type ComponentInternalInstance, handleSetupResult } from '../component'
import type { ComponentInternalInstance } from '../component'
import type { Slots } from '../componentSlots'
import {
type ElementNamespace,
MoveType,
type RendererElement,
type RendererInternals,
type RendererNode,
type SetupRenderEffectFn,
queuePostRenderEffect,
} from '../renderer'
import { queuePostFlushCb } from '../scheduler'
import { filterSingleRoot, updateHOCHostEl } from '../componentRenderUtils'
import {
assertNumber,
popWarningContext,
pushWarningContext,
warn,
} from '../warning'
import { assertNumber, warn } from '../warning'
import { ErrorCodes, handleError } from '../errorHandling'
import { NULL_DYNAMIC_COMPONENT } from '../helpers/resolveAssets'

Expand Down Expand Up @@ -437,8 +431,7 @@ export interface SuspenseBoundary {
next(): RendererNode | null
registerDep(
instance: ComponentInternalInstance,
setupRenderEffect: SetupRenderEffectFn,
optimized: boolean,
onResolve: (setupResult: unknown) => void,
): void
unmount(parentSuspense: SuspenseBoundary | null, doRemove?: boolean): void
}
Expand Down Expand Up @@ -474,7 +467,7 @@ function createSuspenseBoundary(
m: move,
um: unmount,
n: next,
o: { parentNode, remove },
o: { parentNode },
} = rendererInternals

// if set `suspensible: true`, set the current suspense as a dep of parent suspense
Expand Down Expand Up @@ -701,12 +694,12 @@ function createSuspenseBoundary(
return suspense.activeBranch && next(suspense.activeBranch)
},

registerDep(instance, setupRenderEffect, optimized) {
registerDep(instance, onResolve) {
const isInPendingSuspense = !!suspense.pendingBranch
if (isInPendingSuspense) {
suspense.deps++
}
const hydratedEl = instance.vnode.el

instance
.asyncDep!.catch(err => {
handleError(err, instance, ErrorCodes.SETUP_FUNCTION)
Expand All @@ -723,40 +716,8 @@ function createSuspenseBoundary(
}
// retry from this component
instance.asyncResolved = true
const { vnode } = instance
if (__DEV__) {
pushWarningContext(vnode)
}
handleSetupResult(instance, asyncSetupResult, false)
if (hydratedEl) {
// vnode may have been replaced if an update happened before the
// async dep is resolved.
vnode.el = hydratedEl
}
const placeholder = !hydratedEl && instance.subTree.el
setupRenderEffect(
instance,
vnode,
// component may have been moved before resolve.
// if this is not a hydration, instance.subTree will be the comment
// placeholder.
parentNode(hydratedEl || instance.subTree.el!)!,
// anchor will not be used if this is hydration, so only need to
// consider the comment placeholder case.
hydratedEl ? null : next(instance.subTree),
suspense,
namespace,
optimized,
)
if (placeholder) {
// clean up placeholder reference
vnode.placeholder = null
remove(placeholder)
}
updateHOCHostEl(instance, vnode.el)
if (__DEV__) {
popWarningContext()
}
onResolve(asyncSetupResult)

// only decrease deps count if suspense is not already resolved
if (isInPendingSuspense && --suspense.deps === 0) {
suspense.resolve()
Expand Down
1 change: 1 addition & 0 deletions packages/runtime-core/src/hydration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ export function createHydrationFunctions(
container,
null,
parentComponent,
parentSuspense,
)
} else {
mountComponent(
Expand Down
44 changes: 41 additions & 3 deletions packages/runtime-core/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
type LifecycleHook,
createComponentInstance,
getComponentPublicInstance,
handleSetupResult,
setupComponent,
} from './component'
import {
Expand Down Expand Up @@ -1186,6 +1187,7 @@ function baseCreateRenderer(
container,
anchor,
parentComponent,
parentSuspense,
)
}
} else {
Expand Down Expand Up @@ -1272,9 +1274,45 @@ function baseCreateRenderer(
// setup() is async. This component relies on async logic to be resolved
// before proceeding
if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
parentSuspense &&
parentSuspense.registerDep(instance, setupRenderEffect, optimized)

if (parentSuspense) {
const hydratedEl = instance.vnode.el
parentSuspense.registerDep(instance, setupResult => {
const { vnode } = instance
if (__DEV__) {
pushWarningContext(vnode)
}
handleSetupResult(instance, setupResult, false)
if (hydratedEl) {
// vnode may have been replaced if an update happened before the
// async dep is resolved.
vnode.el = hydratedEl
}
const placeholder = !hydratedEl && instance.subTree.el
setupRenderEffect(
instance,
vnode,
// component may have been moved before resolve.
// if this is not a hydration, instance.subTree will be the comment
// placeholder.
hostParentNode(hydratedEl || instance.subTree.el!)!,
// anchor will not be used if this is hydration, so only need to
// consider the comment placeholder case.
hydratedEl ? null : getNextHostNode(instance.subTree),
parentSuspense,
namespace,
optimized,
)
if (placeholder) {
// clean up placeholder reference
vnode.placeholder = null
hostRemove(placeholder)
}
updateHOCHostEl(instance, vnode.el)
if (__DEV__) {
popWarningContext()
}
})
}
// Give it a placeholder if this is not hydration
// TODO handle self-defined fallback
if (!initialVNode.el) {
Expand Down
Loading
Loading