]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: suspense
authorEvan You <yyx990803@gmail.com>
Mon, 9 Sep 2019 17:59:53 +0000 (13:59 -0400)
committerEvan You <yyx990803@gmail.com>
Wed, 11 Sep 2019 15:10:13 +0000 (11:10 -0400)
packages/runtime-core/src/createRenderer.ts
packages/runtime-core/src/suspense.ts
packages/runtime-core/src/vnode.ts

index 986667a1fbd8f8848be393cc277ce4674a55d582..dcd0fc5696fc0ec26443de12ee9c4e718ede65c2 100644 (file)
@@ -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<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,
index 8e8bdebfbc89e7d4b3eae59adfff82624d21d9f0..65e2184fc4a185d9629cd058a3d4c61d84befddc 100644 (file)
@@ -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<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
 }
index 03f4aef4572a341c3f0ec0ac6a70ea02e7d9d86c..8bcafdd7f98e57859d8e95e6fad9bc53c6dff87d 100644 (file)
@@ -61,7 +61,7 @@ export interface VNode<HostNode = any, HostElement = any> {
   ref: string | Function | null
   children: NormalizedChildren<HostNode, HostElement>
   component: ComponentInternalInstance | null
-  suspense: SuspenseBoundary | null
+  suspense: SuspenseBoundary<HostNode, HostElement> | null
 
   // DOM
   el: HostNode | null