normalizeVNode,
VNode,
VNodeChildren,
- Suspense
+ Suspense,
+ createVNode
} from './vnode'
import {
ComponentInternalInstance,
createComponentInstance,
- setupStatefulComponent,
- setCurrentInstance
+ setupStatefulComponent
} from './component'
import {
renderComponentRoot,
import { invokeDirectiveHook } from './directives'
import { ComponentPublicInstance } from './componentPublicInstanceProxy'
import { App, createAppAPI } from './apiApp'
-import {
- SuspenseSymbol,
- createSuspenseBoundary,
- SuspenseBoundary
-} from './suspense'
-import { provide } from './apiInject'
+import { SuspenseBoundary, createSuspenseBoundary } from './suspense'
const prodEffectOptions = {
scheduler: queueJob
anchor: HostNode | null,
parentComponent: ComponentInternalInstance | null,
isSVG: boolean,
- optimized: boolean
+ optimized: boolean,
+ parentSuspense: SuspenseBoundary<HostNode, HostElement> | null = null
) {
if (n1 == null) {
- const parentSuspense =
- parentComponent &&
- (parentComponent.provides[SuspenseSymbol as any] as SuspenseBoundary)
- const suspense = (n2.suspense = createSuspenseBoundary(parentSuspense))
-
- // provide this as the parent suspense for descendents
- setCurrentInstance(parentComponent)
- provide(SuspenseSymbol, suspense)
- setCurrentInstance(null)
+ const contentContainer = hostCreateElement('div')
+ const suspense = (n2.suspense = createSuspenseBoundary(
+ parentSuspense,
+ contentContainer
+ ))
// start mounting the subtree off-dom
- // - tracking async deps and buffering postQueue jobs on current boundary
-
+ // - TODO tracking async deps and buffering postQueue jobs on current boundary
+ const contentTree = (suspense.contentTree = childrenToFragment(n2))
+ processFragment(
+ null,
+ contentTree as VNode<HostNode, HostElement>,
+ contentContainer,
+ null,
+ parentComponent,
+ isSVG,
+ optimized
+ )
// now check if we have encountered any async deps
- // yes: mount the fallback tree.
- // Each time an async dep resolves, it pings the boundary
- // and causes a re-entry.
-
- // no: just mount the tree
- // - if have parent boundary that is still not resolved:
- // merge the buffered jobs into parent
- // - else: flush buffered jobs.
- // - mark resolved.
+ if (suspense.deps > 0) {
+ // yes: mount the fallback tree.
+ // Each time an async dep resolves, it pings the boundary
+ // and causes a re-entry.
+ } else {
+ suspense.resolve()
+ }
} else {
- const suspense = (n2.suspense = n1.suspense) as SuspenseBoundary
+ const suspense = (n2.suspense = n1.suspense) as SuspenseBoundary<
+ HostNode,
+ HostElement
+ >
+ const oldContentTree = suspense.contentTree
+ const newContentTree = (suspense.contentTree = childrenToFragment(n2))
+ // patch suspense subTree as fragment
+ processFragment(
+ oldContentTree,
+ newContentTree,
+ container,
+ anchor,
+ parentComponent,
+ isSVG,
+ optimized
+ )
+ if (suspense.deps > 0) {
+ // still pending.
+ // patch the fallback tree.
+ } else {
+ suspense.resolve()
+ }
}
}
+ function childrenToFragment(vnode: HostVNode): HostVNode {
+ return vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN
+ ? createVNode(Fragment, null, vnode.children)
+ : vnode.shapeFlag & ShapeFlags.TEXT_CHILDREN
+ ? createVNode(Fragment, null, [vnode.children])
+ : createVNode(Fragment, null, [])
+ }
+
function processComponent(
n1: HostVNode | null,
n2: HostVNode,
-import { warn } from './warning'
+import { VNode } from './vnode'
+import { queuePostFlushCb } from './scheduler'
export const SuspenseSymbol = __DEV__ ? Symbol('Suspense key') : Symbol()
-export interface SuspenseBoundary {
+export interface SuspenseBoundary<HostNode, HostElement> {
+ parent: SuspenseBoundary<HostNode, HostElement> | null
+ contentTree: VNode<HostNode, HostElement> | null
+ fallbackTree: VNode<HostNode, HostElement> | null
deps: number
isResolved: boolean
- parent: SuspenseBoundary | null
- ping(): void
+ bufferedJobs: Function[]
+ container: HostElement
resolve(): void
- onResolve(cb: () => void): void
}
-export function createSuspenseBoundary(
- parent: SuspenseBoundary | null
-): SuspenseBoundary {
- let onResolve: () => void
-
- if (parent && !parent.isResolved) {
- parent.deps++
- }
-
- const boundary: SuspenseBoundary = {
+export function createSuspenseBoundary<HostNode, HostElement>(
+ parent: SuspenseBoundary<HostNode, HostElement> | null,
+ container: HostElement
+): SuspenseBoundary<HostNode, HostElement> {
+ const suspense: SuspenseBoundary<HostNode, HostElement> = {
+ parent,
+ container,
deps: 0,
+ contentTree: null,
+ fallbackTree: null,
isResolved: false,
- parent: parent && parent.isResolved ? parent : null,
- ping() {
- // one of the deps resolved - re-entry from root suspense
- if (boundary.parent) {
- }
- if (__DEV__ && boundary.deps < 0) {
- warn(`Suspense boundary pinged when deps === 0. This is a bug.`)
- }
- },
+ bufferedJobs: [],
resolve() {
- boundary.isResolved = true
- if (parent && !parent.isResolved) {
- parent.ping()
- } else {
- onResolve && onResolve()
+ suspense.isResolved = true
+ let parent = suspense.parent
+ let hasUnresolvedAncestor = false
+ while (parent) {
+ if (!parent.isResolved) {
+ parent.bufferedJobs.push(...suspense.bufferedJobs)
+ hasUnresolvedAncestor = true
+ break
+ }
+ }
+ if (!hasUnresolvedAncestor) {
+ queuePostFlushCb(suspense.bufferedJobs)
}
- },
- onResolve(cb: () => void) {
- onResolve = cb
+ suspense.isResolved = true
}
}
- return boundary
+
+ return suspense
}