diff --git a/packages/runtime-core/src/createRenderer.ts b/packages/runtime-core/src/createRenderer.ts index 986667a1..dcd0fc56 100644 --- a/packages/runtime-core/src/createRenderer.ts +++ b/packages/runtime-core/src/createRenderer.ts @@ -6,13 +6,13 @@ import { normalizeVNode, VNode, VNodeChildren, - Suspense + Suspense, + createVNode } from './vnode' import { ComponentInternalInstance, createComponentInstance, - setupStatefulComponent, - setCurrentInstance + setupStatefulComponent } from './component' import { renderComponentRoot, @@ -42,12 +42,7 @@ import { pushWarningContext, popWarningContext, warn } from './warning' 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 @@ -603,37 +598,70 @@ export function createRenderer< anchor: HostNode | null, parentComponent: ComponentInternalInstance | null, isSVG: boolean, - optimized: boolean + optimized: boolean, + parentSuspense: SuspenseBoundary | 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, + 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, diff --git a/packages/runtime-core/src/suspense.ts b/packages/runtime-core/src/suspense.ts index 8e8bdebf..65e2184f 100644 --- a/packages/runtime-core/src/suspense.ts +++ b/packages/runtime-core/src/suspense.ts @@ -1,48 +1,48 @@ -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 { + parent: SuspenseBoundary | null + contentTree: VNode | null + fallbackTree: VNode | 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( + parent: SuspenseBoundary | null, + container: HostElement +): SuspenseBoundary { + const suspense: SuspenseBoundary = { + 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 + } } - }, - onResolve(cb: () => void) { - onResolve = cb + if (!hasUnresolvedAncestor) { + queuePostFlushCb(suspense.bufferedJobs) + } + suspense.isResolved = true } } - return boundary + + return suspense } diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index 03f4aef4..8bcafdd7 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -61,7 +61,7 @@ export interface VNode { ref: string | Function | null children: NormalizedChildren component: ComponentInternalInstance | null - suspense: SuspenseBoundary | null + suspense: SuspenseBoundary | null // DOM el: HostNode | null