Portal,
normalizeVNode,
VNode,
- VNodeChildren
+ VNodeChildren,
+ Suspense
} from './vnode'
import {
ComponentInternalInstance,
createComponentInstance,
- setupStatefulComponent
+ setupStatefulComponent,
+ setCurrentInstance
} 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'
const prodEffectOptions = {
scheduler: queueJob
optimized
)
break
+ case Suspense:
+ processSuspense(
+ n1,
+ n2,
+ container,
+ anchor,
+ parentComponent,
+ isSVG,
+ optimized
+ )
+ break
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(
processEmptyNode(n1, n2, container, anchor)
}
+ function processSuspense(
+ n1: HostVNode | null,
+ n2: HostVNode,
+ container: HostElement,
+ anchor: HostNode | null,
+ parentComponent: ComponentInternalInstance | null,
+ isSVG: boolean,
+ optimized: boolean
+ ) {
+ 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)
+
+ // start mounting the subtree off-dom
+ // - tracking async deps and buffering postQueue jobs on current boundary
+
+ // 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.
+ } else {
+ const suspense = (n2.suspense = n1.suspense) as SuspenseBoundary
+ }
+ }
+
function processComponent(
n1: HostVNode | null,
n2: HostVNode,
--- /dev/null
+import { warn } from './warning'
+
+export const SuspenseSymbol = __DEV__ ? Symbol('Suspense key') : Symbol()
+
+export interface SuspenseBoundary {
+ deps: number
+ isResolved: boolean
+ parent: SuspenseBoundary | null
+ ping(): void
+ 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 = {
+ deps: 0,
+ 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.`)
+ }
+ },
+ resolve() {
+ boundary.isResolved = true
+ if (parent && !parent.isResolved) {
+ parent.ping()
+ } else {
+ onResolve && onResolve()
+ }
+ },
+ onResolve(cb: () => void) {
+ onResolve = cb
+ }
+ }
+ return boundary
+}
import { ShapeFlags } from './shapeFlags'
import { isReactive } from '@vue/reactivity'
import { AppContext } from './apiApp'
+import { SuspenseBoundary } from './suspense'
export const Fragment = __DEV__ ? Symbol('Fragment') : Symbol()
export const Text = __DEV__ ? Symbol('Text') : Symbol()
export const Empty = __DEV__ ? Symbol('Empty') : Symbol()
export const Portal = __DEV__ ? Symbol('Portal') : Symbol()
+export const Suspense = __DEV__ ? Symbol('Suspense') : Symbol()
export type VNodeTypes =
| string
| typeof Portal
| typeof Text
| typeof Empty
+ | typeof Suspense
type VNodeChildAtom<HostNode, HostElement> =
| VNode<HostNode, HostElement>
ref: string | Function | null
children: NormalizedChildren<HostNode, HostElement>
component: ComponentInternalInstance | null
+ suspense: SuspenseBoundary | null
// DOM
el: HostNode | null
ref: (props && props.ref) || null,
children: null,
component: null,
+ suspense: null,
el: null,
anchor: null,
target: null,
// mounted VNodes. If they are somehow not null, this means we have
// encountered an already-mounted vnode being used again.
component: null,
+ suspense: null,
el: null,
anchor: null
}