| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899 |
- import {
- Comment,
- type VNode,
- type VNodeProps,
- closeBlock,
- createVNode,
- currentBlock,
- isBlockTreeEnabled,
- isSameVNodeType,
- normalizeVNode,
- openBlock,
- } from '../vnode'
- import { ShapeFlags, isArray, isFunction, toNumber } from '@vue/shared'
- import { type ComponentInternalInstance, handleSetupResult } from '../component'
- import type { Slots } from '../componentSlots'
- import {
- type ElementNamespace,
- MoveType,
- type RendererElement,
- type RendererInternals,
- type RendererNode,
- type SetupRenderEffectFn,
- } from '../renderer'
- import { queuePostFlushCb } from '../scheduler'
- import { filterSingleRoot, updateHOCHostEl } from '../componentRenderUtils'
- import {
- assertNumber,
- popWarningContext,
- pushWarningContext,
- warn,
- } from '../warning'
- import { ErrorCodes, handleError } from '../errorHandling'
- import { NULL_DYNAMIC_COMPONENT } from '../helpers/resolveAssets'
- export interface SuspenseProps {
- onResolve?: () => void
- onPending?: () => void
- onFallback?: () => void
- timeout?: string | number
- /**
- * Allow suspense to be captured by parent suspense
- *
- * @default false
- */
- suspensible?: boolean
- }
- export const isSuspense = (type: any): boolean => type.__isSuspense
- // incrementing unique id for every pending branch
- let suspenseId = 0
- /**
- * For testing only
- */
- export const resetSuspenseId = () => (suspenseId = 0)
- // Suspense exposes a component-like API, and is treated like a component
- // in the compiler, but internally it's a special built-in type that hooks
- // directly into the renderer.
- export const SuspenseImpl = {
- name: 'Suspense',
- // In order to make Suspense tree-shakable, we need to avoid importing it
- // directly in the renderer. The renderer checks for the __isSuspense flag
- // on a vnode's type and calls the `process` method, passing in renderer
- // internals.
- __isSuspense: true,
- process(
- n1: VNode | null,
- n2: VNode,
- container: RendererElement,
- anchor: RendererNode | null,
- parentComponent: ComponentInternalInstance | null,
- parentSuspense: SuspenseBoundary | null,
- namespace: ElementNamespace,
- slotScopeIds: string[] | null,
- optimized: boolean,
- // platform-specific impl passed from renderer
- rendererInternals: RendererInternals,
- ) {
- if (n1 == null) {
- mountSuspense(
- n2,
- container,
- anchor,
- parentComponent,
- parentSuspense,
- namespace,
- slotScopeIds,
- optimized,
- rendererInternals,
- )
- } else {
- // #8678 if the current suspense needs to be patched and parentSuspense has
- // not been resolved. this means that both the current suspense and parentSuspense
- // need to be patched. because parentSuspense's pendingBranch includes the
- // current suspense, it will be processed twice:
- // 1. current patch
- // 2. mounting along with the pendingBranch of parentSuspense
- // it is necessary to skip the current patch to avoid multiple mounts
- // of inner components.
- if (parentSuspense && parentSuspense.deps > 0) {
- n2.suspense = n1.suspense!
- n2.suspense.vnode = n2
- n2.el = n1.el
- return
- }
- patchSuspense(
- n1,
- n2,
- container,
- anchor,
- parentComponent,
- namespace,
- slotScopeIds,
- optimized,
- rendererInternals,
- )
- }
- },
- hydrate: hydrateSuspense,
- create: createSuspenseBoundary,
- normalize: normalizeSuspenseChildren,
- }
- // Force-casted public typing for h and TSX props inference
- export const Suspense = (__FEATURE_SUSPENSE__
- ? SuspenseImpl
- : null) as unknown as {
- __isSuspense: true
- new (): {
- $props: VNodeProps & SuspenseProps
- $slots: {
- default(): VNode[]
- fallback(): VNode[]
- }
- }
- }
- function triggerEvent(
- vnode: VNode,
- name: 'onResolve' | 'onPending' | 'onFallback',
- ) {
- const eventListener = vnode.props && vnode.props[name]
- if (isFunction(eventListener)) {
- eventListener()
- }
- }
- function mountSuspense(
- vnode: VNode,
- container: RendererElement,
- anchor: RendererNode | null,
- parentComponent: ComponentInternalInstance | null,
- parentSuspense: SuspenseBoundary | null,
- namespace: ElementNamespace,
- slotScopeIds: string[] | null,
- optimized: boolean,
- rendererInternals: RendererInternals,
- ) {
- const {
- p: patch,
- o: { createElement },
- } = rendererInternals
- const hiddenContainer = createElement('div')
- const suspense = (vnode.suspense = createSuspenseBoundary(
- vnode,
- parentSuspense,
- parentComponent,
- container,
- hiddenContainer,
- anchor,
- namespace,
- slotScopeIds,
- optimized,
- rendererInternals,
- ))
- // start mounting the content subtree in an off-dom container
- patch(
- null,
- (suspense.pendingBranch = vnode.ssContent!),
- hiddenContainer,
- null,
- parentComponent,
- suspense,
- namespace,
- slotScopeIds,
- )
- // now check if we have encountered any async deps
- if (suspense.deps > 0) {
- // has async
- // invoke @fallback event
- triggerEvent(vnode, 'onPending')
- triggerEvent(vnode, 'onFallback')
- // mount the fallback tree
- patch(
- null,
- vnode.ssFallback!,
- container,
- anchor,
- parentComponent,
- null, // fallback tree will not have suspense context
- namespace,
- slotScopeIds,
- )
- setActiveBranch(suspense, vnode.ssFallback!)
- } else {
- // Suspense has no async deps. Just resolve.
- suspense.resolve(false, true)
- }
- }
- function patchSuspense(
- n1: VNode,
- n2: VNode,
- container: RendererElement,
- anchor: RendererNode | null,
- parentComponent: ComponentInternalInstance | null,
- namespace: ElementNamespace,
- slotScopeIds: string[] | null,
- optimized: boolean,
- { p: patch, um: unmount, o: { createElement } }: RendererInternals,
- ) {
- const suspense = (n2.suspense = n1.suspense)!
- suspense.vnode = n2
- n2.el = n1.el
- const newBranch = n2.ssContent!
- const newFallback = n2.ssFallback!
- const { activeBranch, pendingBranch, isInFallback, isHydrating } = suspense
- if (pendingBranch) {
- suspense.pendingBranch = newBranch
- if (isSameVNodeType(newBranch, pendingBranch)) {
- // same root type but content may have changed.
- patch(
- pendingBranch,
- newBranch,
- suspense.hiddenContainer,
- null,
- parentComponent,
- suspense,
- namespace,
- slotScopeIds,
- optimized,
- )
- if (suspense.deps <= 0) {
- suspense.resolve()
- } else if (isInFallback) {
- // It's possible that the app is in hydrating state when patching the
- // suspense instance. If someone updates the dependency during component
- // setup in children of suspense boundary, that would be problemtic
- // because we aren't actually showing a fallback content when
- // patchSuspense is called. In such case, patch of fallback content
- // should be no op
- if (!isHydrating) {
- patch(
- activeBranch,
- newFallback,
- container,
- anchor,
- parentComponent,
- null, // fallback tree will not have suspense context
- namespace,
- slotScopeIds,
- optimized,
- )
- setActiveBranch(suspense, newFallback)
- }
- }
- } else {
- // toggled before pending tree is resolved
- // increment pending ID. this is used to invalidate async callbacks
- suspense.pendingId = suspenseId++
- if (isHydrating) {
- // if toggled before hydration is finished, the current DOM tree is
- // no longer valid. set it as the active branch so it will be unmounted
- // when resolved
- suspense.isHydrating = false
- suspense.activeBranch = pendingBranch
- } else {
- unmount(pendingBranch, parentComponent, suspense)
- }
- // reset suspense state
- suspense.deps = 0
- // discard effects from pending branch
- suspense.effects.length = 0
- // discard previous container
- suspense.hiddenContainer = createElement('div')
- if (isInFallback) {
- // already in fallback state
- patch(
- null,
- newBranch,
- suspense.hiddenContainer,
- null,
- parentComponent,
- suspense,
- namespace,
- slotScopeIds,
- optimized,
- )
- if (suspense.deps <= 0) {
- suspense.resolve()
- } else {
- patch(
- activeBranch,
- newFallback,
- container,
- anchor,
- parentComponent,
- null, // fallback tree will not have suspense context
- namespace,
- slotScopeIds,
- optimized,
- )
- setActiveBranch(suspense, newFallback)
- }
- } else if (activeBranch && isSameVNodeType(newBranch, activeBranch)) {
- // toggled "back" to current active branch
- patch(
- activeBranch,
- newBranch,
- container,
- anchor,
- parentComponent,
- suspense,
- namespace,
- slotScopeIds,
- optimized,
- )
- // force resolve
- suspense.resolve(true)
- } else {
- // switched to a 3rd branch
- patch(
- null,
- newBranch,
- suspense.hiddenContainer,
- null,
- parentComponent,
- suspense,
- namespace,
- slotScopeIds,
- optimized,
- )
- if (suspense.deps <= 0) {
- suspense.resolve()
- }
- }
- }
- } else {
- if (activeBranch && isSameVNodeType(newBranch, activeBranch)) {
- // root did not change, just normal patch
- patch(
- activeBranch,
- newBranch,
- container,
- anchor,
- parentComponent,
- suspense,
- namespace,
- slotScopeIds,
- optimized,
- )
- setActiveBranch(suspense, newBranch)
- } else {
- // root node toggled
- // invoke @pending event
- triggerEvent(n2, 'onPending')
- // mount pending branch in off-dom container
- suspense.pendingBranch = newBranch
- if (newBranch.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
- suspense.pendingId = newBranch.component!.suspenseId!
- } else {
- suspense.pendingId = suspenseId++
- }
- patch(
- null,
- newBranch,
- suspense.hiddenContainer,
- null,
- parentComponent,
- suspense,
- namespace,
- slotScopeIds,
- optimized,
- )
- if (suspense.deps <= 0) {
- // incoming branch has no async deps, resolve now.
- suspense.resolve()
- } else {
- const { timeout, pendingId } = suspense
- if (timeout > 0) {
- setTimeout(() => {
- if (suspense.pendingId === pendingId) {
- suspense.fallback(newFallback)
- }
- }, timeout)
- } else if (timeout === 0) {
- suspense.fallback(newFallback)
- }
- }
- }
- }
- }
- export interface SuspenseBoundary {
- vnode: VNode<RendererNode, RendererElement, SuspenseProps>
- parent: SuspenseBoundary | null
- parentComponent: ComponentInternalInstance | null
- namespace: ElementNamespace
- container: RendererElement
- hiddenContainer: RendererElement
- activeBranch: VNode | null
- pendingBranch: VNode | null
- deps: number
- pendingId: number
- timeout: number
- isInFallback: boolean
- isHydrating: boolean
- isUnmounted: boolean
- effects: Function[]
- resolve(force?: boolean, sync?: boolean): void
- fallback(fallbackVNode: VNode): void
- move(
- container: RendererElement,
- anchor: RendererNode | null,
- type: MoveType,
- ): void
- next(): RendererNode | null
- registerDep(
- instance: ComponentInternalInstance,
- setupRenderEffect: SetupRenderEffectFn,
- ): void
- unmount(parentSuspense: SuspenseBoundary | null, doRemove?: boolean): void
- }
- let hasWarned = false
- function createSuspenseBoundary(
- vnode: VNode,
- parentSuspense: SuspenseBoundary | null,
- parentComponent: ComponentInternalInstance | null,
- container: RendererElement,
- hiddenContainer: RendererElement,
- anchor: RendererNode | null,
- namespace: ElementNamespace,
- slotScopeIds: string[] | null,
- optimized: boolean,
- rendererInternals: RendererInternals,
- isHydrating = false,
- ): SuspenseBoundary {
- /* istanbul ignore if */
- if (__DEV__ && !__TEST__ && !hasWarned) {
- hasWarned = true
- // @ts-expect-error `console.info` cannot be null error
- // eslint-disable-next-line no-console
- console[console.info ? 'info' : 'log'](
- `<Suspense> is an experimental feature and its API will likely change.`,
- )
- }
- const {
- p: patch,
- m: move,
- um: unmount,
- n: next,
- o: { parentNode, remove },
- } = rendererInternals
- // if set `suspensible: true`, set the current suspense as a dep of parent suspense
- let parentSuspenseId: number | undefined
- const isSuspensible = isVNodeSuspensible(vnode)
- if (isSuspensible) {
- if (parentSuspense?.pendingBranch) {
- parentSuspenseId = parentSuspense.pendingId
- parentSuspense.deps++
- }
- }
- const timeout = vnode.props ? toNumber(vnode.props.timeout) : undefined
- if (__DEV__) {
- assertNumber(timeout, `Suspense timeout`)
- }
- const initialAnchor = anchor
- const suspense: SuspenseBoundary = {
- vnode,
- parent: parentSuspense,
- parentComponent,
- namespace,
- container,
- hiddenContainer,
- deps: 0,
- pendingId: suspenseId++,
- timeout: typeof timeout === 'number' ? timeout : -1,
- activeBranch: null,
- pendingBranch: null,
- isInFallback: !isHydrating,
- isHydrating,
- isUnmounted: false,
- effects: [],
- resolve(resume = false, sync = false) {
- if (__DEV__) {
- if (!resume && !suspense.pendingBranch) {
- throw new Error(
- `suspense.resolve() is called without a pending branch.`,
- )
- }
- if (suspense.isUnmounted) {
- throw new Error(
- `suspense.resolve() is called on an already unmounted suspense boundary.`,
- )
- }
- }
- const {
- vnode,
- activeBranch,
- pendingBranch,
- pendingId,
- effects,
- parentComponent,
- container,
- } = suspense
- // if there's a transition happening we need to wait it to finish.
- let delayEnter: boolean | null = false
- if (suspense.isHydrating) {
- suspense.isHydrating = false
- } else if (!resume) {
- delayEnter =
- activeBranch &&
- pendingBranch!.transition &&
- pendingBranch!.transition.mode === 'out-in'
- if (delayEnter) {
- activeBranch!.transition!.afterLeave = () => {
- if (pendingId === suspense.pendingId) {
- move(
- pendingBranch!,
- container,
- anchor === initialAnchor ? next(activeBranch!) : anchor,
- MoveType.ENTER,
- )
- queuePostFlushCb(effects)
- }
- }
- }
- // unmount current active tree
- if (activeBranch) {
- // if the fallback tree was mounted, it may have been moved
- // as part of a parent suspense. get the latest anchor for insertion
- // #8105 if `delayEnter` is true, it means that the mounting of
- // `activeBranch` will be delayed. if the branch switches before
- // transition completes, both `activeBranch` and `pendingBranch` may
- // coexist in the `hiddenContainer`. This could result in
- // `next(activeBranch!)` obtaining an incorrect anchor
- // (got `pendingBranch.el`).
- // Therefore, after the mounting of activeBranch is completed,
- // it is necessary to get the latest anchor.
- if (parentNode(activeBranch.el!) !== suspense.hiddenContainer) {
- anchor = next(activeBranch)
- }
- unmount(activeBranch, parentComponent, suspense, true)
- }
- if (!delayEnter) {
- // move content from off-dom container to actual container
- move(pendingBranch!, container, anchor, MoveType.ENTER)
- }
- }
- setActiveBranch(suspense, pendingBranch!)
- suspense.pendingBranch = null
- suspense.isInFallback = false
- // flush buffered effects
- // check if there is a pending parent suspense
- let parent = suspense.parent
- let hasUnresolvedAncestor = false
- while (parent) {
- if (parent.pendingBranch) {
- // found a pending parent suspense, merge buffered post jobs
- // into that parent
- parent.effects.push(...effects)
- hasUnresolvedAncestor = true
- break
- }
- parent = parent.parent
- }
- // no pending parent suspense nor transition, flush all jobs
- if (!hasUnresolvedAncestor && !delayEnter) {
- queuePostFlushCb(effects)
- }
- suspense.effects = []
- // resolve parent suspense if all async deps are resolved
- if (isSuspensible) {
- if (
- parentSuspense &&
- parentSuspense.pendingBranch &&
- parentSuspenseId === parentSuspense.pendingId
- ) {
- parentSuspense.deps--
- if (parentSuspense.deps === 0 && !sync) {
- parentSuspense.resolve()
- }
- }
- }
- // invoke @resolve event
- triggerEvent(vnode, 'onResolve')
- },
- fallback(fallbackVNode) {
- if (!suspense.pendingBranch) {
- return
- }
- const { vnode, activeBranch, parentComponent, container, namespace } =
- suspense
- // invoke @fallback event
- triggerEvent(vnode, 'onFallback')
- const anchor = next(activeBranch!)
- const mountFallback = () => {
- if (!suspense.isInFallback) {
- return
- }
- // mount the fallback tree
- patch(
- null,
- fallbackVNode,
- container,
- anchor,
- parentComponent,
- null, // fallback tree will not have suspense context
- namespace,
- slotScopeIds,
- optimized,
- )
- setActiveBranch(suspense, fallbackVNode)
- }
- const delayEnter =
- fallbackVNode.transition && fallbackVNode.transition.mode === 'out-in'
- if (delayEnter) {
- activeBranch!.transition!.afterLeave = mountFallback
- }
- suspense.isInFallback = true
- // unmount current active branch
- unmount(
- activeBranch!,
- parentComponent,
- null, // no suspense so unmount hooks fire now
- true, // shouldRemove
- )
- if (!delayEnter) {
- mountFallback()
- }
- },
- move(container, anchor, type) {
- suspense.activeBranch &&
- move(suspense.activeBranch, container, anchor, type)
- suspense.container = container
- },
- next() {
- return suspense.activeBranch && next(suspense.activeBranch)
- },
- registerDep(instance, setupRenderEffect) {
- const isInPendingSuspense = !!suspense.pendingBranch
- if (isInPendingSuspense) {
- suspense.deps++
- }
- const hydratedEl = instance.vnode.el
- instance
- .asyncDep!.catch(err => {
- handleError(err, instance, ErrorCodes.SETUP_FUNCTION)
- })
- .then(asyncSetupResult => {
- // retry when the setup() promise resolves.
- // component may have been unmounted before resolve.
- if (
- instance.isUnmounted ||
- suspense.isUnmounted ||
- suspense.pendingId !== instance.suspenseId
- ) {
- return
- }
- // 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) {
- remove(placeholder)
- }
- updateHOCHostEl(instance, vnode.el)
- if (__DEV__) {
- popWarningContext()
- }
- // only decrease deps count if suspense is not already resolved
- if (isInPendingSuspense && --suspense.deps === 0) {
- suspense.resolve()
- }
- })
- },
- unmount(parentSuspense, doRemove) {
- suspense.isUnmounted = true
- if (suspense.activeBranch) {
- unmount(
- suspense.activeBranch,
- parentComponent,
- parentSuspense,
- doRemove,
- )
- }
- if (suspense.pendingBranch) {
- unmount(
- suspense.pendingBranch,
- parentComponent,
- parentSuspense,
- doRemove,
- )
- }
- },
- }
- return suspense
- }
- function hydrateSuspense(
- node: Node,
- vnode: VNode,
- parentComponent: ComponentInternalInstance | null,
- parentSuspense: SuspenseBoundary | null,
- namespace: ElementNamespace,
- slotScopeIds: string[] | null,
- optimized: boolean,
- rendererInternals: RendererInternals,
- hydrateNode: (
- node: Node,
- vnode: VNode,
- parentComponent: ComponentInternalInstance | null,
- parentSuspense: SuspenseBoundary | null,
- slotScopeIds: string[] | null,
- optimized: boolean,
- ) => Node | null,
- ): Node | null {
- const suspense = (vnode.suspense = createSuspenseBoundary(
- vnode,
- parentSuspense,
- parentComponent,
- node.parentNode!,
- // eslint-disable-next-line no-restricted-globals
- document.createElement('div'),
- null,
- namespace,
- slotScopeIds,
- optimized,
- rendererInternals,
- true /* hydrating */,
- ))
- // there are two possible scenarios for server-rendered suspense:
- // - success: ssr content should be fully resolved
- // - failure: ssr content should be the fallback branch.
- // however, on the client we don't really know if it has failed or not
- // attempt to hydrate the DOM assuming it has succeeded, but we still
- // need to construct a suspense boundary first
- const result = hydrateNode(
- node,
- (suspense.pendingBranch = vnode.ssContent!),
- parentComponent,
- suspense,
- slotScopeIds,
- optimized,
- )
- if (suspense.deps === 0) {
- suspense.resolve(false, true)
- }
- return result
- /* eslint-enable no-restricted-globals */
- }
- function normalizeSuspenseChildren(vnode: VNode) {
- const { shapeFlag, children } = vnode
- const isSlotChildren = shapeFlag & ShapeFlags.SLOTS_CHILDREN
- vnode.ssContent = normalizeSuspenseSlot(
- isSlotChildren ? (children as Slots).default : children,
- )
- vnode.ssFallback = isSlotChildren
- ? normalizeSuspenseSlot((children as Slots).fallback)
- : createVNode(Comment)
- }
- function normalizeSuspenseSlot(s: any) {
- let block: VNode[] | null | undefined
- if (isFunction(s)) {
- const trackBlock = isBlockTreeEnabled && s._c
- if (trackBlock) {
- // disableTracking: false
- // allow block tracking for compiled slots
- // (see ./componentRenderContext.ts)
- s._d = false
- openBlock()
- }
- s = s()
- if (trackBlock) {
- s._d = true
- block = currentBlock
- closeBlock()
- }
- }
- if (isArray(s)) {
- const singleChild = filterSingleRoot(s)
- if (
- __DEV__ &&
- !singleChild &&
- s.filter(child => child !== NULL_DYNAMIC_COMPONENT).length > 0
- ) {
- warn(`<Suspense> slots expect a single root node.`)
- }
- s = singleChild
- }
- s = normalizeVNode(s)
- if (block && !s.dynamicChildren) {
- s.dynamicChildren = block.filter(c => c !== s)
- }
- return s
- }
- export function queueEffectWithSuspense(
- fn: Function | Function[],
- suspense: SuspenseBoundary | null,
- ): void {
- if (suspense && suspense.pendingBranch) {
- if (isArray(fn)) {
- suspense.effects.push(...fn)
- } else {
- suspense.effects.push(fn)
- }
- } else {
- queuePostFlushCb(fn)
- }
- }
- function setActiveBranch(suspense: SuspenseBoundary, branch: VNode) {
- suspense.activeBranch = branch
- const { vnode, parentComponent } = suspense
- let el = branch.el
- // if branch has no el after patch, it's a HOC wrapping async components
- // drill and locate the placeholder comment node
- while (!el && branch.component) {
- branch = branch.component.subTree
- el = branch.el
- }
- vnode.el = el
- // in case suspense is the root node of a component,
- // recursively update the HOC el
- if (parentComponent && parentComponent.subTree === vnode) {
- parentComponent.vnode.el = el
- updateHOCHostEl(parentComponent, el)
- }
- }
- function isVNodeSuspensible(vnode: VNode) {
- return vnode.props?.suspensible != null && vnode.props.suspensible !== false
- }
|