normalizeVNode,
VNode,
VNodeChildren,
- Suspense,
createVNode
} from './vnode'
import {
ComponentInternalInstance,
createComponentInstance,
setupStatefulComponent,
- handleSetupResult,
Component,
Data
} from './component'
import {
renderComponentRoot,
- shouldUpdateComponent
+ shouldUpdateComponent,
+ updateHOCHostEl
} from './componentRenderUtils'
import {
isString,
import { invokeDirectiveHook } from './directives'
import { ComponentPublicInstance } from './componentProxy'
import { App, createAppAPI } from './apiApp'
-import {
- SuspenseBoundary,
- createSuspenseBoundary,
- normalizeSuspenseChildren
-} from './suspense'
-import { handleError, ErrorCodes, callWithErrorHandling } from './errorHandling'
-
-const prodEffectOptions = {
- scheduler: queueJob
-}
-
-function createDevEffectOptions(
- instance: ComponentInternalInstance
-): ReactiveEffectOptions {
- return {
- scheduler: queueJob,
- onTrack: instance.rtc ? e => invokeHooks(instance.rtc!, e) : void 0,
- onTrigger: instance.rtg ? e => invokeHooks(instance.rtg!, e) : void 0
- }
-}
-
-function isSameType(n1: VNode, n2: VNode): boolean {
- return n1.type === n2.type && n1.key === n2.key
-}
-
-function invokeHooks(hooks: Function[], arg?: DebuggerEvent) {
- for (let i = 0; i < hooks.length; i++) {
- hooks[i](arg)
- }
-}
-
-export function queuePostRenderEffect(
- fn: Function | Function[],
- suspense: SuspenseBoundary<any, any> | null
-) {
- if (suspense !== null && !suspense.isResolved) {
- if (isArray(fn)) {
- suspense.effects.push(...fn)
- } else {
- suspense.effects.push(fn)
- }
- } else {
- queuePostFlushCb(fn)
- }
-}
+import { SuspenseBoundary, SuspenseImpl } from './suspense'
+import { ErrorCodes, callWithErrorHandling } from './errorHandling'
export interface RendererOptions<HostNode = any, HostElement = any> {
patchProp(
dom: HostElement
) => void
+// An object exposing the internals of a renderer, passed to tree-shakeable
+// features so that they can be decoupled from this file.
+export interface RendererInternals<HostNode = any, HostElement = any> {
+ patch: (
+ n1: VNode<HostNode, HostElement> | null, // null means this is a mount
+ n2: VNode<HostNode, HostElement>,
+ container: HostElement,
+ anchor?: HostNode | null,
+ parentComponent?: ComponentInternalInstance | null,
+ parentSuspense?: SuspenseBoundary<HostNode, HostElement> | null,
+ isSVG?: boolean,
+ optimized?: boolean
+ ) => void
+ unmount: (
+ vnode: VNode<HostNode, HostElement>,
+ parentComponent: ComponentInternalInstance | null,
+ parentSuspense: SuspenseBoundary<HostNode, HostElement> | null,
+ doRemove?: boolean
+ ) => void
+ move: (
+ vnode: VNode<HostNode, HostElement>,
+ container: HostElement,
+ anchor: HostNode | null
+ ) => void
+ next: (vnode: VNode<HostNode, HostElement>) => HostNode | null
+ options: RendererOptions<HostNode, HostElement>
+}
+
+const prodEffectOptions = {
+ scheduler: queueJob
+}
+
+function createDevEffectOptions(
+ instance: ComponentInternalInstance
+): ReactiveEffectOptions {
+ return {
+ scheduler: queueJob,
+ onTrack: instance.rtc ? e => invokeHooks(instance.rtc!, e) : void 0,
+ onTrigger: instance.rtg ? e => invokeHooks(instance.rtg!, e) : void 0
+ }
+}
+
+function isSameType(n1: VNode, n2: VNode): boolean {
+ return n1.type === n2.type && n1.key === n2.key
+}
+
+function invokeHooks(hooks: Function[], arg?: DebuggerEvent) {
+ for (let i = 0; i < hooks.length; i++) {
+ hooks[i](arg)
+ }
+}
+
+export const queuePostRenderEffect = __FEATURE_SUSPENSE__
+ ? (
+ fn: Function | Function[],
+ suspense: SuspenseBoundary<any, any> | null
+ ) => {
+ if (suspense !== null && !suspense.isResolved) {
+ if (isArray(fn)) {
+ suspense.effects.push(...fn)
+ } else {
+ suspense.effects.push(fn)
+ }
+ } else {
+ queuePostFlushCb(fn)
+ }
+ }
+ : queuePostFlushCb
+
/**
* The createRenderer function accepts two generic arguments:
* HostNode and HostElement, corresponding to Node and Element types in the
querySelector: hostQuerySelector
} = options
+ const internals: RendererInternals<HostNode, HostElement> = {
+ patch,
+ unmount,
+ move,
+ next: getNextHostNode,
+ options
+ }
+
function patch(
n1: HostVNode | null, // null means this is a mount
n2: HostVNode,
optimized
)
break
- case Suspense:
- if (__FEATURE_SUSPENSE__) {
- processSuspense(
+ default:
+ if (shapeFlag & ShapeFlags.ELEMENT) {
+ processElement(
n1,
n2,
container,
isSVG,
optimized
)
- } else if (__DEV__) {
- warn(`Suspense is not enabled in the version of Vue you are using.`)
- }
- break
- default:
- if (shapeFlag & ShapeFlags.ELEMENT) {
- processElement(
+ } else if (shapeFlag & ShapeFlags.COMPONENT) {
+ processComponent(
n1,
n2,
container,
isSVG,
optimized
)
- } else if (shapeFlag & ShapeFlags.COMPONENT) {
- processComponent(
+ } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
+ ;(type as typeof SuspenseImpl).process(
n1,
n2,
container,
parentComponent,
parentSuspense,
isSVG,
- optimized
+ optimized,
+ internals
)
} else if (__DEV__) {
warn('Invalid HostVNode type:', n2.type, `(${typeof n2.type})`)
processCommentNode(n1, n2, container, anchor)
}
- function processSuspense(
- n1: HostVNode | null,
- n2: HostVNode,
- container: HostElement,
- anchor: HostNode | null,
- parentComponent: ComponentInternalInstance | null,
- parentSuspense: HostSuspenseBoundary | null,
- isSVG: boolean,
- optimized: boolean
- ) {
- if (n1 == null) {
- mountSuspense(
- n2,
- container,
- anchor,
- parentComponent,
- parentSuspense,
- isSVG,
- optimized
- )
- } else {
- patchSuspense(
- n1,
- n2,
- container,
- anchor,
- parentComponent,
- isSVG,
- optimized
- )
- }
- }
-
- function mountSuspense(
- n2: HostVNode,
- container: HostElement,
- anchor: HostNode | null,
- parentComponent: ComponentInternalInstance | null,
- parentSuspense: HostSuspenseBoundary | null,
- isSVG: boolean,
- optimized: boolean
- ) {
- const hiddenContainer = hostCreateElement('div')
- const suspense = (n2.suspense = createSuspenseBoundary(
- n2,
- parentSuspense,
- parentComponent,
- container,
- hiddenContainer,
- anchor,
- isSVG,
- optimized
- ))
-
- const { content, fallback } = normalizeSuspenseChildren(n2)
- suspense.subTree = content
- suspense.fallbackTree = fallback
-
- // start mounting the content subtree in an off-dom container
- patch(
- null,
- content,
- hiddenContainer,
- null,
- parentComponent,
- suspense,
- isSVG,
- optimized
- )
- // now check if we have encountered any async deps
- if (suspense.deps > 0) {
- // mount the fallback tree
- patch(
- null,
- fallback,
- container,
- anchor,
- parentComponent,
- null, // fallback tree will not have suspense context
- isSVG,
- optimized
- )
- n2.el = fallback.el
- } else {
- // Suspense has no async deps. Just resolve.
- resolveSuspense(suspense)
- }
- }
-
- function patchSuspense(
- n1: HostVNode,
- n2: HostVNode,
- container: HostElement,
- anchor: HostNode | null,
- parentComponent: ComponentInternalInstance | null,
- isSVG: boolean,
- optimized: boolean
- ) {
- const suspense = (n2.suspense = n1.suspense)!
- suspense.vnode = n2
- const { content, fallback } = normalizeSuspenseChildren(n2)
- const oldSubTree = suspense.subTree
- const oldFallbackTree = suspense.fallbackTree
- if (!suspense.isResolved) {
- patch(
- oldSubTree,
- content,
- suspense.hiddenContainer,
- null,
- parentComponent,
- suspense,
- isSVG,
- optimized
- )
- if (suspense.deps > 0) {
- // still pending. patch the fallback tree.
- patch(
- oldFallbackTree,
- fallback,
- container,
- anchor,
- parentComponent,
- null, // fallback tree will not have suspense context
- isSVG,
- optimized
- )
- n2.el = fallback.el
- }
- // If deps somehow becomes 0 after the patch it means the patch caused an
- // async dep component to unmount and removed its dep. It will cause the
- // suspense to resolve and we don't need to do anything here.
- } else {
- // just normal patch inner content as a fragment
- patch(
- oldSubTree,
- content,
- container,
- anchor,
- parentComponent,
- suspense,
- isSVG,
- optimized
- )
- n2.el = content.el
- }
- suspense.subTree = content
- suspense.fallbackTree = fallback
- }
-
- function resolveSuspense(suspense: HostSuspenseBoundary) {
- if (__DEV__) {
- if (suspense.isResolved) {
- throw new Error(
- `resolveSuspense() is called on an already resolved suspense boundary.`
- )
- }
- if (suspense.isUnmounted) {
- throw new Error(
- `resolveSuspense() is called on an already unmounted suspense boundary.`
- )
- }
- }
- const {
- vnode,
- subTree,
- fallbackTree,
- effects,
- parentComponent,
- container
- } = suspense
-
- // this is initial anchor on mount
- let { anchor } = suspense
- // unmount fallback tree
- if (fallbackTree.el) {
- // if the fallback tree was mounted, it may have been moved
- // as part of a parent suspense. get the latest anchor for insertion
- anchor = getNextHostNode(fallbackTree)
- unmount(fallbackTree as HostVNode, parentComponent, suspense, true)
- }
- // move content from off-dom container to actual container
- move(subTree as HostVNode, container, anchor)
- const el = (vnode.el = (subTree as HostVNode).el!)
- // suspense as the root node of a component...
- if (parentComponent && parentComponent.subTree === vnode) {
- parentComponent.vnode.el = el
- updateHOCHostEl(parentComponent, el)
- }
- // check if there is a pending parent suspense
- let parent = suspense.parent
- let hasUnresolvedAncestor = false
- while (parent) {
- if (!parent.isResolved) {
- // 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, flush all jobs
- if (!hasUnresolvedAncestor) {
- queuePostFlushCb(effects)
- }
- suspense.isResolved = true
- // invoke @resolve event
- const onResolve = vnode.props && vnode.props.onResolve
- if (isFunction(onResolve)) {
- onResolve()
- }
- }
-
- function restartSuspense(suspense: HostSuspenseBoundary) {
- suspense.isResolved = false
- const {
- vnode,
- subTree,
- fallbackTree,
- parentComponent,
- container,
- hiddenContainer,
- isSVG,
- optimized
- } = suspense
-
- // move content tree back to the off-dom container
- const anchor = getNextHostNode(subTree)
- move(subTree as HostVNode, hiddenContainer, null)
- // remount the fallback tree
- patch(
- null,
- fallbackTree,
- container,
- anchor,
- parentComponent,
- null, // fallback tree will not have suspense context
- isSVG,
- optimized
- )
- const el = (vnode.el = (fallbackTree as HostVNode).el!)
- // suspense as the root node of a component...
- if (parentComponent && parentComponent.subTree === vnode) {
- parentComponent.vnode.el = el
- updateHOCHostEl(parentComponent, el)
- }
-
- // invoke @suspense event
- const onSuspense = vnode.props && vnode.props.onSuspense
- if (isFunction(onSuspense)) {
- onSuspense()
- }
- }
-
function processComponent(
n1: HostVNode | null,
n2: HostVNode,
if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
if (!parentSuspense) {
// TODO handle this properly
- throw new Error('Async component without a suspense boundary!')
+ throw new Error('Async setup() is used without a suspense boundary!')
}
- // parent suspense already resolved, need to re-suspense
- // use queueJob so it's handled synchronously after patching the current
- // suspense tree
- if (parentSuspense.isResolved) {
- queueJob(() => {
- restartSuspense(parentSuspense)
- })
- }
-
- parentSuspense.deps++
- instance.asyncDep
- .catch(err => {
- handleError(err, instance, ErrorCodes.SETUP_FUNCTION)
- })
- .then(asyncSetupResult => {
- // component may be unmounted before resolve
- if (!instance.isUnmounted && !parentSuspense.isUnmounted) {
- retryAsyncComponent(
- instance,
- asyncSetupResult,
- parentSuspense,
- isSVG
- )
- }
- })
+ parentSuspense.registerDep(instance, setupRenderEffect)
// give it a placeholder
const placeholder = (instance.subTree = createVNode(Comment))
}
}
- function retryAsyncComponent(
- instance: ComponentInternalInstance,
- asyncSetupResult: unknown,
- parentSuspense: HostSuspenseBoundary,
- isSVG: boolean
- ) {
- parentSuspense.deps--
- // retry from this component
- instance.asyncResolved = true
- const { vnode } = instance
- if (__DEV__) {
- pushWarningContext(vnode)
- }
- handleSetupResult(instance, asyncSetupResult, parentSuspense)
- setupRenderEffect(
- instance,
- parentSuspense,
- vnode,
- // component may have been moved before resolve
- hostParentNode(instance.subTree.el) as HostElement,
- getNextHostNode(instance.subTree),
- isSVG
- )
- updateHOCHostEl(instance, vnode.el as HostNode)
- if (__DEV__) {
- popWarningContext()
- }
- if (parentSuspense.deps === 0) {
- resolveSuspense(parentSuspense)
- }
- }
-
function setupRenderEffect(
instance: ComponentInternalInstance,
parentSuspense: HostSuspenseBoundary | null,
resolveSlots(instance, nextVNode.children)
}
- function updateHOCHostEl(
- { vnode, parent }: ComponentInternalInstance,
- el: HostNode
- ) {
- while (parent && parent.subTree === vnode) {
- ;(vnode = parent.vnode).el = el
- parent = parent.parent
- }
- }
-
function patchChildren(
n1: HostVNode | null,
n2: HostVNode,
container: HostElement,
anchor: HostNode | null
) {
- if (vnode.component !== null) {
- move(vnode.component.subTree, container, anchor)
+ if (vnode.shapeFlag & ShapeFlags.COMPONENT) {
+ move(vnode.component!.subTree, container, anchor)
return
}
- if (__FEATURE_SUSPENSE__ && vnode.type === Suspense) {
+ if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) {
const suspense = vnode.suspense!
move(
suspense.isResolved ? suspense.subTree : suspense.fallbackTree,
props,
ref,
type,
- component,
- suspense,
children,
dynamicChildren,
shapeFlag,
setRef(ref, null, parentComponent, null)
}
- if (component != null) {
- unmountComponent(component, parentSuspense, doRemove)
+ if (shapeFlag & ShapeFlags.COMPONENT) {
+ unmountComponent(vnode.component!, parentSuspense, doRemove)
return
}
- if (__FEATURE_SUSPENSE__ && suspense != null) {
- unmountSuspense(suspense, parentComponent, parentSuspense, doRemove)
+ if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
+ vnode.suspense!.unmount(parentSuspense, doRemove)
return
}
) {
parentSuspense.deps--
if (parentSuspense.deps === 0) {
- resolveSuspense(parentSuspense)
+ parentSuspense.resolve()
}
}
}
- function unmountSuspense(
- suspense: HostSuspenseBoundary,
- parentComponent: ComponentInternalInstance | null,
- parentSuspense: HostSuspenseBoundary | null,
- doRemove?: boolean
- ) {
- suspense.isUnmounted = true
- unmount(suspense.subTree, parentComponent, parentSuspense, doRemove)
- if (!suspense.isResolved) {
- unmount(suspense.fallbackTree, parentComponent, parentSuspense, doRemove)
- }
- }
-
function unmountChildren(
children: HostVNode[],
parentComponent: ComponentInternalInstance | null,
}
}
- function getNextHostNode({
- component,
- suspense,
- anchor,
- el
- }: HostVNode): HostNode | null {
- if (component !== null) {
- return getNextHostNode(component.subTree)
+ function getNextHostNode(vnode: HostVNode): HostNode | null {
+ if (vnode.shapeFlag & ShapeFlags.COMPONENT) {
+ return getNextHostNode(vnode.component!.subTree)
}
- if (__FEATURE_SUSPENSE__ && suspense !== null) {
+ if (__FEATURE_SUSPENSE__ && vnode.shapeFlag & ShapeFlags.SUSPENSE) {
+ const suspense = vnode.suspense!
return getNextHostNode(
suspense.isResolved ? suspense.subTree : suspense.fallbackTree
)
}
- return hostNextSibling((anchor || el)!)
+ return hostNextSibling((vnode.anchor || vnode.el)!)
}
function setRef(
-import { VNode, normalizeVNode, VNodeChild } from './vnode'
-import { ShapeFlags } from '.'
+import { VNode, normalizeVNode, VNodeChild, VNodeTypes } from './vnode'
+import { ShapeFlags } from './shapeFlags'
import { isFunction } from '@vue/shared'
-import { ComponentInternalInstance } from './component'
+import { ComponentInternalInstance, handleSetupResult } from './component'
import { Slots } from './componentSlots'
+import { RendererInternals } from './createRenderer'
+import { queuePostFlushCb, queueJob } from './scheduler'
+import { updateHOCHostEl } from './componentRenderUtils'
+import { handleError, ErrorCodes } from './errorHandling'
+import { pushWarningContext, popWarningContext } from './warning'
-export const SuspenseSymbol = Symbol(__DEV__ ? 'Suspense key' : undefined)
+export function isSuspenseType(type: VNodeTypes): type is typeof SuspenseImpl {
+ return (type as any).__isSuspenseImpl === true
+}
+
+export const SuspenseImpl = {
+ __isSuspenseImpl: true,
+ process(
+ n1: VNode | null,
+ n2: VNode,
+ container: object,
+ anchor: object | null,
+ parentComponent: ComponentInternalInstance | null,
+ parentSuspense: SuspenseBoundary | null,
+ isSVG: boolean,
+ optimized: boolean,
+ // platform-specific impl passed from renderer
+ rendererInternals: RendererInternals
+ ) {
+ if (n1 == null) {
+ mountSuspense(
+ n2,
+ container,
+ anchor,
+ parentComponent,
+ parentSuspense,
+ isSVG,
+ optimized,
+ rendererInternals
+ )
+ } else {
+ patchSuspense(
+ n1,
+ n2,
+ container,
+ anchor,
+ parentComponent,
+ isSVG,
+ optimized,
+ rendererInternals
+ )
+ }
+ }
+}
+
+function mountSuspense(
+ n2: VNode,
+ container: object,
+ anchor: object | null,
+ parentComponent: ComponentInternalInstance | null,
+ parentSuspense: SuspenseBoundary | null,
+ isSVG: boolean,
+ optimized: boolean,
+ rendererInternals: RendererInternals
+) {
+ const {
+ patch,
+ options: { createElement }
+ } = rendererInternals
+ const hiddenContainer = createElement('div')
+ const suspense = (n2.suspense = createSuspenseBoundary(
+ n2,
+ parentSuspense,
+ parentComponent,
+ container,
+ hiddenContainer,
+ anchor,
+ isSVG,
+ optimized,
+ rendererInternals
+ ))
+
+ const { content, fallback } = normalizeSuspenseChildren(n2)
+ suspense.subTree = content
+ suspense.fallbackTree = fallback
+
+ // start mounting the content subtree in an off-dom container
+ patch(
+ null,
+ content,
+ hiddenContainer,
+ null,
+ parentComponent,
+ suspense,
+ isSVG,
+ optimized
+ )
+ // now check if we have encountered any async deps
+ if (suspense.deps > 0) {
+ // mount the fallback tree
+ patch(
+ null,
+ fallback,
+ container,
+ anchor,
+ parentComponent,
+ null, // fallback tree will not have suspense context
+ isSVG,
+ optimized
+ )
+ n2.el = fallback.el
+ } else {
+ // Suspense has no async deps. Just resolve.
+ suspense.resolve()
+ }
+}
+
+function patchSuspense(
+ n1: VNode,
+ n2: VNode,
+ container: object,
+ anchor: object | null,
+ parentComponent: ComponentInternalInstance | null,
+ isSVG: boolean,
+ optimized: boolean,
+ { patch }: RendererInternals
+) {
+ const suspense = (n2.suspense = n1.suspense)!
+ suspense.vnode = n2
+ const { content, fallback } = normalizeSuspenseChildren(n2)
+ const oldSubTree = suspense.subTree
+ const oldFallbackTree = suspense.fallbackTree
+ if (!suspense.isResolved) {
+ patch(
+ oldSubTree,
+ content,
+ suspense.hiddenContainer,
+ null,
+ parentComponent,
+ suspense,
+ isSVG,
+ optimized
+ )
+ if (suspense.deps > 0) {
+ // still pending. patch the fallback tree.
+ patch(
+ oldFallbackTree,
+ fallback,
+ container,
+ anchor,
+ parentComponent,
+ null, // fallback tree will not have suspense context
+ isSVG,
+ optimized
+ )
+ n2.el = fallback.el
+ }
+ // If deps somehow becomes 0 after the patch it means the patch caused an
+ // async dep component to unmount and removed its dep. It will cause the
+ // suspense to resolve and we don't need to do anything here.
+ } else {
+ // just normal patch inner content as a fragment
+ patch(
+ oldSubTree,
+ content,
+ container,
+ anchor,
+ parentComponent,
+ suspense,
+ isSVG,
+ optimized
+ )
+ n2.el = content.el
+ }
+ suspense.subTree = content
+ suspense.fallbackTree = fallback
+}
export interface SuspenseBoundary<
HostNode = any,
isResolved: boolean
isUnmounted: boolean
effects: Function[]
+ resolve(): void
+ restart(): void
+ registerDep(
+ instance: ComponentInternalInstance,
+ setupRenderEffect: (
+ instance: ComponentInternalInstance,
+ parentSuspense: SuspenseBoundary<HostNode, HostElement> | null,
+ initialVNode: VNode<HostNode, HostElement>,
+ container: HostElement,
+ anchor: HostNode | null,
+ isSVG: boolean
+ ) => void
+ ): void
+ unmount(
+ parentSuspense: SuspenseBoundary<HostNode, HostElement> | null,
+ doRemove?: boolean
+ ): void
}
-export function createSuspenseBoundary<HostNode, HostElement>(
+function createSuspenseBoundary<HostNode, HostElement>(
vnode: VNode<HostNode, HostElement>,
parent: SuspenseBoundary<HostNode, HostElement> | null,
parentComponent: ComponentInternalInstance | null,
hiddenContainer: HostElement,
anchor: HostNode | null,
isSVG: boolean,
- optimized: boolean
+ optimized: boolean,
+ rendererInternals: RendererInternals<HostNode, HostElement>
): SuspenseBoundary<HostNode, HostElement> {
- return {
+ const {
+ patch,
+ move,
+ unmount,
+ next,
+ options: { parentNode }
+ } = rendererInternals
+
+ const suspense: SuspenseBoundary<HostNode, HostElement> = {
vnode,
parent,
parentComponent,
fallbackTree: null as any, // will be set immediately after creation
isResolved: false,
isUnmounted: false,
- effects: []
+ effects: [],
+
+ resolve() {
+ if (__DEV__) {
+ if (suspense.isResolved) {
+ throw new Error(
+ `resolveSuspense() is called on an already resolved suspense boundary.`
+ )
+ }
+ if (suspense.isUnmounted) {
+ throw new Error(
+ `resolveSuspense() is called on an already unmounted suspense boundary.`
+ )
+ }
+ }
+ const {
+ vnode,
+ subTree,
+ fallbackTree,
+ effects,
+ parentComponent,
+ container
+ } = suspense
+
+ // this is initial anchor on mount
+ let { anchor } = suspense
+ // unmount fallback tree
+ if (fallbackTree.el) {
+ // if the fallback tree was mounted, it may have been moved
+ // as part of a parent suspense. get the latest anchor for insertion
+ anchor = next(fallbackTree)
+ unmount(fallbackTree as VNode, parentComponent, suspense, true)
+ }
+ // move content from off-dom container to actual container
+ move(subTree as VNode, container, anchor)
+ const el = (vnode.el = (subTree as VNode).el!)
+ // suspense as the root node of a component...
+ if (parentComponent && parentComponent.subTree === vnode) {
+ parentComponent.vnode.el = el
+ updateHOCHostEl(parentComponent, el)
+ }
+ // check if there is a pending parent suspense
+ let parent = suspense.parent
+ let hasUnresolvedAncestor = false
+ while (parent) {
+ if (!parent.isResolved) {
+ // 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, flush all jobs
+ if (!hasUnresolvedAncestor) {
+ queuePostFlushCb(effects)
+ }
+ suspense.isResolved = true
+ // invoke @resolve event
+ const onResolve = vnode.props && vnode.props.onResolve
+ if (isFunction(onResolve)) {
+ onResolve()
+ }
+ },
+
+ restart() {
+ suspense.isResolved = false
+ const {
+ vnode,
+ subTree,
+ fallbackTree,
+ parentComponent,
+ container,
+ hiddenContainer,
+ isSVG,
+ optimized
+ } = suspense
+
+ // move content tree back to the off-dom container
+ const anchor = next(subTree)
+ move(subTree as VNode, hiddenContainer, null)
+ // remount the fallback tree
+ patch(
+ null,
+ fallbackTree,
+ container,
+ anchor,
+ parentComponent,
+ null, // fallback tree will not have suspense context
+ isSVG,
+ optimized
+ )
+ const el = (vnode.el = (fallbackTree as VNode).el!)
+ // suspense as the root node of a component...
+ if (parentComponent && parentComponent.subTree === vnode) {
+ parentComponent.vnode.el = el
+ updateHOCHostEl(parentComponent, el)
+ }
+
+ // invoke @suspense event
+ const onSuspense = vnode.props && vnode.props.onSuspense
+ if (isFunction(onSuspense)) {
+ onSuspense()
+ }
+ },
+
+ registerDep(instance, setupRenderEffect) {
+ // suspense is already resolved, need to recede.
+ // use queueJob so it's handled synchronously after patching the current
+ // suspense tree
+ if (suspense.isResolved) {
+ queueJob(() => {
+ suspense.restart()
+ })
+ }
+
+ suspense.deps++
+ 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) {
+ return
+ }
+ suspense.deps--
+ // retry from this component
+ instance.asyncResolved = true
+ const { vnode } = instance
+ if (__DEV__) {
+ pushWarningContext(vnode)
+ }
+ handleSetupResult(instance, asyncSetupResult, suspense)
+ setupRenderEffect(
+ instance,
+ suspense,
+ vnode,
+ // component may have been moved before resolve
+ parentNode(instance.subTree.el)!,
+ next(instance.subTree),
+ isSVG
+ )
+ updateHOCHostEl(instance, vnode.el)
+ if (__DEV__) {
+ popWarningContext()
+ }
+ if (suspense.deps === 0) {
+ suspense.resolve()
+ }
+ })
+ },
+
+ unmount(parentSuspense, doRemove) {
+ suspense.isUnmounted = true
+ unmount(suspense.subTree, parentComponent, parentSuspense, doRemove)
+ if (!suspense.isResolved) {
+ unmount(
+ suspense.fallbackTree,
+ parentComponent,
+ parentSuspense,
+ doRemove
+ )
+ }
+ }
}
+
+ return suspense
}
-export function normalizeSuspenseChildren(
+function normalizeSuspenseChildren(
vnode: VNode
): {
content: VNode