]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: suspense ideas
authorEvan You <yyx990803@gmail.com>
Sat, 7 Sep 2019 15:28:40 +0000 (11:28 -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 [new file with mode: 0644]
packages/runtime-core/src/vnode.ts

index d20466f11b6eb784921ec62b7b309c2e5a26e1ed..33cae409520c37d402ae6f43626a40c9fa2ba260 100644 (file)
@@ -5,12 +5,14 @@ import {
   Portal,
   normalizeVNode,
   VNode,
-  VNodeChildren
+  VNodeChildren,
+  Suspense
 } from './vnode'
 import {
   ComponentInternalInstance,
   createComponentInstance,
-  setupStatefulComponent
+  setupStatefulComponent,
+  setCurrentInstance
 } from './component'
 import {
   renderComponentRoot,
@@ -40,6 +42,12 @@ 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'
 
 const prodEffectOptions = {
   scheduler: queueJob
@@ -187,6 +195,17 @@ export function createRenderer<
           optimized
         )
         break
+      case Suspense:
+        processSuspense(
+          n1,
+          n2,
+          container,
+          anchor,
+          parentComponent,
+          isSVG,
+          optimized
+        )
+        break
       default:
         if (shapeFlag & ShapeFlags.ELEMENT) {
           processElement(
@@ -575,6 +594,44 @@ export function createRenderer<
     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,
diff --git a/packages/runtime-core/src/suspense.ts b/packages/runtime-core/src/suspense.ts
new file mode 100644 (file)
index 0000000..8e8bdeb
--- /dev/null
@@ -0,0 +1,48 @@
+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
+}
index 1df7641aa69bb91892d43380d8a16994024c6e00..03f4aef4572a341c3f0ec0ac6a70ea02e7d9d86c 100644 (file)
@@ -12,11 +12,13 @@ import { PatchFlags } from './patchFlags'
 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
@@ -26,6 +28,7 @@ export type VNodeTypes =
   | typeof Portal
   | typeof Text
   | typeof Empty
+  | typeof Suspense
 
 type VNodeChildAtom<HostNode, HostElement> =
   | VNode<HostNode, HostElement>
@@ -58,6 +61,7 @@ export interface VNode<HostNode = any, HostElement = any> {
   ref: string | Function | null
   children: NormalizedChildren<HostNode, HostElement>
   component: ComponentInternalInstance | null
+  suspense: SuspenseBoundary | null
 
   // DOM
   el: HostNode | null
@@ -168,6 +172,7 @@ export function createVNode(
     ref: (props && props.ref) || null,
     children: null,
     component: null,
+    suspense: null,
     el: null,
     anchor: null,
     target: null,
@@ -221,6 +226,7 @@ export function cloneVNode(vnode: VNode): VNode {
     // 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
   }