]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: scheduler, more component
authorEvan You <yyx990803@gmail.com>
Tue, 28 May 2019 09:19:47 +0000 (17:19 +0800)
committerEvan You <yyx990803@gmail.com>
Tue, 28 May 2019 09:19:47 +0000 (17:19 +0800)
12 files changed:
packages/runtime-core/src/component.ts
packages/runtime-core/src/createRenderer.ts
packages/runtime-core/src/scheduler.ts [new file with mode: 0644]
packages/runtime-core/src/vnode.ts
packages/scheduler/.npmignore [deleted file]
packages/scheduler/README.md [deleted file]
packages/scheduler/__tests__/scheduler.spec.ts [deleted file]
packages/scheduler/index.js [deleted file]
packages/scheduler/package.json [deleted file]
packages/scheduler/src/index.ts [deleted file]
packages/shared/src/index.ts
tsconfig.json

index e6d9ecb87bcdcdfa4dbc53c89886c2bc4494aa8a..3ba82c316ddfe2ee49f30c5c5ec9aae78df292b6 100644 (file)
@@ -1,9 +1,82 @@
-import { VNode, normalizeVNode } from './vnode'
+import { VNode, normalizeVNode, VNodeChild } from './vnode'
+import { ReactiveEffect } from '@vue/observer'
+import { isFunction, EMPTY_OBJ } from '@vue/shared'
 
-export class Component {}
+interface Value<T> {
+  value: T
+}
+
+type UnwrapBindings<T> = {
+  [key in keyof T]: T[key] extends Value<infer V> ? V : T[key]
+}
+
+type Prop<T> = { (): T } | { new (...args: any[]): T & object }
+
+type ExtractPropTypes<PropOptions> = {
+  readonly [key in keyof PropOptions]: PropOptions[key] extends Prop<infer V>
+    ? V
+    : PropOptions[key] extends null | undefined ? any : PropOptions[key]
+}
+
+interface ComponentPublicProperties<P, S> {
+  $props: P
+  $state: S
+}
+
+export interface ComponentOptions<
+  RawProps = { [key: string]: Prop<any> },
+  RawBindings = { [key: string]: any } | void,
+  Props = ExtractPropTypes<RawProps>,
+  Bindings = UnwrapBindings<RawBindings>
+> {
+  props?: RawProps
+  setup?: (props: Props) => RawBindings
+  render?: <B extends Bindings>(
+    this: ComponentPublicProperties<Props, B>,
+    ctx: {
+      state: B
+      props: Props
+    }
+  ) => VNodeChild
+}
 
-export function renderComponentRoot(instance: any): VNode {
-  return normalizeVNode(instance.render(instance.vnode.props))
+// no-op, for type inference only
+export function createComponent<
+  RawProps,
+  RawBindings,
+  Props = ExtractPropTypes<RawProps>,
+  Bindings = UnwrapBindings<RawBindings>
+>(
+  options: ComponentOptions<RawProps, RawBindings, Props, Bindings>
+): {
+  // for TSX
+  new (): { $props: Props }
+} {
+  return options as any
+}
+
+export interface ComponentHandle {
+  type: Function | ComponentOptions
+  vnode: VNode | null
+  next: VNode | null
+  subTree: VNode | null
+  update: ReactiveEffect
+}
+
+export function renderComponentRoot(handle: ComponentHandle): VNode {
+  const { type, vnode } = handle
+  // TODO actually resolve props
+  const renderArg = {
+    props: (vnode as VNode).props || EMPTY_OBJ
+  }
+  if (isFunction(type)) {
+    return normalizeVNode(type(renderArg))
+  } else {
+    if (__DEV__ && !type.render) {
+      // TODO warn missing render
+    }
+    return normalizeVNode((type.render as Function)(renderArg))
+  }
 }
 
 export function shouldUpdateComponent(
index 7a7d8d58b577b428f4ade19303b2a71c98accc55..9a90785525edccf7a548428134005ecdec0ce44e 100644 (file)
@@ -1,9 +1,9 @@
 // TODO:
 // - component
-// - lifecycle
+// - lifecycle / refs
+// - keep alive
 // - app context
 // - svg
-// - refs
 // - hydration
 // - warning context
 // - parent chain
@@ -13,17 +13,20 @@ import {
   Text,
   Fragment,
   Empty,
+  Portal,
   normalizeVNode,
   VNode,
   VNodeChildren
-} from './vnode.js'
+} from './vnode'
+import { isString, isArray, EMPTY_OBJ, EMPTY_ARR } from '@vue/shared'
 import { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
-import { effect } from '@vue/observer'
-import { isString, isFunction, isArray } from '@vue/shared'
-import { renderComponentRoot, shouldUpdateComponent } from './component.js'
-
-const emptyArr: any[] = []
-const emptyObj: { [key: string]: any } = {}
+import { effect, stop } from '@vue/observer'
+import {
+  ComponentHandle,
+  renderComponentRoot,
+  shouldUpdateComponent
+} from './component'
+import { queueJob } from './scheduler'
 
 function isSameType(n1: VNode, n2: VNode): boolean {
   return n1.type === n2.type && n1.key === n2.key
@@ -81,16 +84,26 @@ export function createRenderer(options: RendererOptions) {
     }
 
     const { type } = n2
-    if (type === Text) {
-      processText(n1, n2, container, anchor)
-    } else if (type === Empty) {
-      processEmptyNode(n1, n2, container, anchor)
-    } else if (type === Fragment) {
-      processFragment(n1, n2, container, anchor, optimized)
-    } else if (isFunction(type)) {
-      processComponent(n1, n2, container, anchor)
-    } else {
-      processElement(n1, n2, container, anchor, optimized)
+    switch (type) {
+      case Text:
+        processText(n1, n2, container, anchor)
+        break
+      case Empty:
+        processEmptyNode(n1, n2, container, anchor)
+        break
+      case Fragment:
+        processFragment(n1, n2, container, anchor, optimized)
+        break
+      case Portal:
+        // TODO
+        break
+      default:
+        if (isString(type)) {
+          processElement(n1, n2, container, anchor, optimized)
+        } else {
+          processComponent(n1, n2, container, anchor)
+        }
+        break
     }
   }
 
@@ -172,8 +185,8 @@ export function createRenderer(options: RendererOptions) {
   function patchElement(n1: VNode, n2: VNode, optimized?: boolean) {
     const el = (n2.el = n1.el)
     const { patchFlag, dynamicChildren } = n2
-    const oldProps = (n1 && n1.props) || emptyObj
-    const newProps = n2.props || emptyObj
+    const oldProps = (n1 && n1.props) || EMPTY_OBJ
+    const newProps = n2.props || EMPTY_OBJ
 
     if (patchFlag != null) {
       // the presence of a patchFlag means this element's render code was
@@ -272,7 +285,7 @@ export function createRenderer(options: RendererOptions) {
           )
         }
       }
-      if (oldProps !== emptyObj) {
+      if (oldProps !== EMPTY_OBJ) {
         for (const key in oldProps) {
           if (!(key in newProps)) {
             hostPatchProp(
@@ -320,10 +333,10 @@ export function createRenderer(options: RendererOptions) {
     if (n1 == null) {
       mountComponent(n2, container, anchor)
     } else {
-      const instance = (n2.component = n1.component)
+      const instance = (n2.component = n1.component) as ComponentHandle
       if (shouldUpdateComponent(n1, n2)) {
         instance.next = n2
-        instance.forceUpdate()
+        instance.update()
       } else {
         n2.el = n1.el
       }
@@ -335,15 +348,17 @@ export function createRenderer(options: RendererOptions) {
     container: HostNode,
     anchor?: HostNode
   ) {
-    const instance = (vnode.component = {
+    const instance: ComponentHandle = (vnode.component = {
+      type: vnode.type as Function,
       vnode: null,
       next: null,
       subTree: null,
-      forceUpdate: null,
-      render: vnode.type
-    } as any)
+      update: null as any
+    })
+
+    // TODO call setup, handle bindings and render context
 
-    instance.forceUpdate = effect(
+    instance.update = effect(
       () => {
         if (!instance.vnode) {
           // initial mount
@@ -357,9 +372,9 @@ export function createRenderer(options: RendererOptions) {
         }
       },
       {
-        scheduler: e => e() // TODO use proper scheduler
+        scheduler: queueJob
       }
-    ) as any
+    )
   }
 
   function updateComponent(
@@ -454,8 +469,8 @@ export function createRenderer(options: RendererOptions) {
     anchor?: HostNode,
     optimized?: boolean
   ) {
-    c1 = c1 || emptyArr
-    c2 = c2 || emptyArr
+    c1 = c1 || EMPTY_ARR
+    c2 = c2 || EMPTY_ARR
     const oldLength = c1.length
     const newLength = c2.length
     const commonLength = Math.min(oldLength, newLength)
@@ -618,7 +633,7 @@ export function createRenderer(options: RendererOptions) {
       // generate longest stable subsequence only when nodes have moved
       const increasingNewIndexSequence = moved
         ? getSequence(newIndexToOldIndexMap)
-        : emptyArr
+        : EMPTY_ARR
       j = increasingNewIndexSequence.length - 1
       // looping backwards so that we can use last patched node as anchor
       for (i = toBePatched - 1; i >= 0; i--) {
@@ -645,7 +660,7 @@ export function createRenderer(options: RendererOptions) {
 
   function move(vnode: VNode, container: HostNode, anchor: HostNode) {
     if (vnode.component != null) {
-      move(vnode.component.subTree, container, anchor)
+      move(vnode.component.subTree as VNode, container, anchor)
       return
     }
     if (vnode.type === Fragment) {
@@ -663,7 +678,8 @@ export function createRenderer(options: RendererOptions) {
   function unmount(vnode: VNode, doRemove?: boolean) {
     if (vnode.component != null) {
       // TODO teardown component
-      unmount(vnode.component.subTree, doRemove)
+      stop(vnode.component.update)
+      unmount(vnode.component.subTree as VNode, doRemove)
       return
     }
     const shouldRemoveChildren = vnode.type === Fragment && doRemove
@@ -691,7 +707,7 @@ export function createRenderer(options: RendererOptions) {
   function getNextHostNode(vnode: VNode): HostNode {
     return vnode.component === null
       ? hostNextSibling(vnode.anchor || vnode.el)
-      : getNextHostNode(vnode.component.subTree)
+      : getNextHostNode(vnode.component.subTree as VNode)
   }
 
   return function render(vnode: VNode, dom: HostNode): VNode {
diff --git a/packages/runtime-core/src/scheduler.ts b/packages/runtime-core/src/scheduler.ts
new file mode 100644 (file)
index 0000000..1652fd5
--- /dev/null
@@ -0,0 +1,74 @@
+const queue: Array<() => void> = []
+const postFlushCbs: Array<() => void> = []
+const p = Promise.resolve()
+
+let isFlushing = false
+
+export function nextTick(fn?: () => void): Promise<void> {
+  return fn ? p.then(fn) : p
+}
+
+export function queueJob(job: () => void, onError?: (err: Error) => void) {
+  if (queue.indexOf(job) === -1) {
+    queue.push(job)
+    if (!isFlushing) {
+      const p = nextTick(flushJobs)
+      if (onError) p.catch(onError)
+    }
+  }
+}
+
+export function queuePostFlushCb(cb: () => void) {
+  if (postFlushCbs.indexOf(cb) === -1) {
+    postFlushCbs.push(cb)
+  }
+}
+
+export function flushPostFlushCbs() {
+  const cbs = postFlushCbs.slice()
+  let i = cbs.length
+  postFlushCbs.length = 0
+  // post flush cbs are flushed in reverse since they are queued top-down
+  // but should fire bottom-up
+  while (i--) {
+    cbs[i]()
+  }
+}
+
+const RECURSION_LIMIT = 100
+type JobCountMap = Map<Function, number>
+
+function flushJobs(seenJobs?: JobCountMap) {
+  isFlushing = true
+  let job
+  if (__DEV__) {
+    seenJobs = seenJobs || new Map()
+  }
+  while ((job = queue.shift())) {
+    if (__DEV__) {
+      const seen = seenJobs as JobCountMap
+      if (!seen.has(job)) {
+        seen.set(job, 1)
+      } else {
+        const count = seen.get(job) as number
+        if (count > RECURSION_LIMIT) {
+          throw new Error(
+            'Maximum recursive updates exceeded. ' +
+              "You may have code that is mutating state in your component's " +
+              'render function or updated hook.'
+          )
+        } else {
+          seen.set(job, count + 1)
+        }
+      }
+    }
+    job()
+  }
+  flushPostFlushCbs()
+  isFlushing = false
+  // some postFlushCb queued jobs!
+  // keep flushing until it drains.
+  if (queue.length) {
+    flushJobs(seenJobs)
+  }
+}
index e32fc00e3a8df7b85dd964b4a7bd40f175b93cdb..3fd8743595531541b53e4ef89aaeb951ce513ff3 100644 (file)
@@ -1,29 +1,34 @@
 import { isArray, isFunction } from '@vue/shared'
+import { ComponentHandle } from './component'
+import { HostNode } from './createRenderer'
 
 export const Fragment = Symbol('Fragment')
 export const Text = Symbol('Text')
 export const Empty = Symbol('Empty')
+export const Portal = Symbol('Portal')
 
 type VNodeTypes =
   | string
   | Function
+  | Object
   | typeof Fragment
   | typeof Text
   | typeof Empty
 
-export type VNodeChild = VNode | string | number | null
-export interface VNodeChildren extends Array<VNodeChildren | VNodeChild> {}
+type VNodeChildAtom = VNode | string | number | null | void
+export interface VNodeChildren extends Array<VNodeChildren | VNodeChildAtom> {}
+export type VNodeChild = VNodeChildAtom | VNodeChildren
 
 export interface VNode {
   type: VNodeTypes
   props: { [key: string]: any } | null
   key: string | number | null
   children: string | VNodeChildren | null
-  component: any
+  component: ComponentHandle | null
 
   // DOM
-  el: any
-  anchor: any // fragment anchor
+  el: HostNode | null
+  anchor: HostNode | null // fragment anchor
 
   // optimization only
   patchFlag: number | null
@@ -98,7 +103,7 @@ export function cloneVNode(vnode: VNode): VNode {
   return vnode
 }
 
-export function normalizeVNode(child: any): VNode {
+export function normalizeVNode(child: VNodeChild): VNode {
   if (child == null) {
     // empty placeholder
     return createVNode(Empty)
diff --git a/packages/scheduler/.npmignore b/packages/scheduler/.npmignore
deleted file mode 100644 (file)
index bb5c8a5..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-__tests__/
-__mocks__/
-dist/packages
\ No newline at end of file
diff --git a/packages/scheduler/README.md b/packages/scheduler/README.md
deleted file mode 100644 (file)
index 2af0546..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-# @vue/scheduler
-
-> This package is published only for typing and building custom renderers. It is NOT meant to be used in applications.
-
-The default scheduler that uses Promise / Microtask to defer and batch updates.
diff --git a/packages/scheduler/__tests__/scheduler.spec.ts b/packages/scheduler/__tests__/scheduler.spec.ts
deleted file mode 100644 (file)
index d196ce9..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-import { queueJob, queuePostEffect, nextTick } from '../src/index'
-
-describe('scheduler', () => {
-  it('queueJob', async () => {
-    const calls: any = []
-    const job1 = () => {
-      calls.push('job1')
-    }
-    const job2 = () => {
-      calls.push('job2')
-    }
-    queueJob(job1)
-    queueJob(job2)
-    expect(calls).toEqual([])
-    await nextTick()
-    expect(calls).toEqual(['job1', 'job2'])
-  })
-
-  it('queueJob while already flushing', async () => {
-    const calls: any = []
-    const job1 = () => {
-      calls.push('job1')
-      // job1 queues job2
-      queueJob(job2)
-    }
-    const job2 = () => {
-      calls.push('job2')
-    }
-    queueJob(job1)
-    expect(calls).toEqual([])
-    await nextTick()
-    expect(calls).toEqual(['job1', 'job2'])
-  })
-
-  it('queueJob w/ postCommitCb', async () => {
-    const calls: any = []
-    const job1 = () => {
-      calls.push('job1')
-      queuePostEffect(cb1)
-    }
-    const job2 = () => {
-      calls.push('job2')
-      queuePostEffect(cb2)
-    }
-    const cb1 = () => {
-      calls.push('cb1')
-    }
-    const cb2 = () => {
-      calls.push('cb2')
-    }
-    queueJob(job1)
-    queueJob(job2)
-    await nextTick()
-    // post commit cbs are called in reverse!
-    expect(calls).toEqual(['job1', 'job2', 'cb2', 'cb1'])
-  })
-
-  it('queueJob w/ postFlushCb while flushing', async () => {
-    const calls: any = []
-    const job1 = () => {
-      calls.push('job1')
-      queuePostEffect(cb1)
-      // job1 queues job2
-      queueJob(job2)
-    }
-    const job2 = () => {
-      calls.push('job2')
-      queuePostEffect(cb2)
-    }
-    const cb1 = () => {
-      calls.push('cb1')
-    }
-    const cb2 = () => {
-      calls.push('cb2')
-    }
-    queueJob(job1)
-    expect(calls).toEqual([])
-    await nextTick()
-    expect(calls).toEqual(['job1', 'job2', 'cb2', 'cb1'])
-  })
-
-  it('should dedupe queued tasks', async () => {
-    const calls: any = []
-    const job1 = () => {
-      calls.push('job1')
-    }
-    const job2 = () => {
-      calls.push('job2')
-    }
-    queueJob(job1)
-    queueJob(job2)
-    queueJob(job1)
-    queueJob(job2)
-    expect(calls).toEqual([])
-    await nextTick()
-    expect(calls).toEqual(['job1', 'job2'])
-  })
-
-  it('queueJob inside postEffect', async () => {
-    const calls: any = []
-    const job1 = () => {
-      calls.push('job1')
-      queuePostEffect(cb1)
-    }
-    const cb1 = () => {
-      // queue another job in postFlushCb
-      calls.push('cb1')
-      queueJob(job2)
-    }
-    const job2 = () => {
-      calls.push('job2')
-      queuePostEffect(cb2)
-    }
-    const cb2 = () => {
-      calls.push('cb2')
-    }
-
-    queueJob(job1)
-    queueJob(job2)
-    await nextTick()
-    expect(calls).toEqual(['job1', 'job2', 'cb2', 'cb1', 'job2', 'cb2'])
-  })
-})
diff --git a/packages/scheduler/index.js b/packages/scheduler/index.js
deleted file mode 100644 (file)
index 9a0883b..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-'use strict'
-
-if (process.env.NODE_ENV === 'production') {
-  module.exports = require('./dist/scheduler.cjs.prod.js')
-} else {
-  module.exports = require('./dist/scheduler.cjs.js')
-}
diff --git a/packages/scheduler/package.json b/packages/scheduler/package.json
deleted file mode 100644 (file)
index 397fdc2..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-{
-  "name": "@vue/scheduler",
-  "version": "3.0.0-alpha.1",
-  "description": "@vue/scheduler",
-  "main": "index.js",
-  "module": "dist/scheduler.esm-bundler.js",
-  "types": "dist/index.d.ts",
-  "sideEffects": false,
-  "repository": {
-    "type": "git",
-    "url": "git+https://github.com/vuejs/vue.git"
-  },
-  "keywords": [
-    "vue"
-  ],
-  "author": "Evan You",
-  "license": "MIT",
-  "bugs": {
-    "url": "https://github.com/vuejs/vue/issues"
-  },
-  "homepage": "https://github.com/vuejs/vue/tree/dev/packages/scheduler#readme"
-}
diff --git a/packages/scheduler/src/index.ts b/packages/scheduler/src/index.ts
deleted file mode 100644 (file)
index 89323ef..0000000
+++ /dev/null
@@ -1,385 +0,0 @@
-// TODO infinite updates detection
-
-// A data structure that stores a deferred DOM operation.
-// the first element is the function to call, and the rest of the array
-// stores up to 8 arguments.
-type Op = [Function, ...any[]]
-
-// A "job" stands for a unit of work that needs to be performed.
-// Typically, one job corresponds to the mounting or updating of one component
-// instance (including functional ones).
-interface Job<T extends Function = () => void> {
-  // A job is itself a function that performs work. It can contain work such as
-  // calling render functions, running the diff algorithm (patch), mounting new
-  // vnodes, and tearing down old vnodes. However, these work needs to be
-  // performed in several different phases, most importantly to separate
-  // workloads that do not produce side-effects ("stage") vs. those that do
-  // ("commit").
-  // During the stage call it should not perform any direct sife-effects.
-  // Instead, it buffers them. All side effects from multiple jobs queued in the
-  // same tick are flushed together during the "commit" phase. This allows us to
-  // perform side-effect-free work over multiple frames (yielding to the browser
-  // in-between to keep the app responsive), and only flush all the side effects
-  // together when all work is done (AKA time-slicing).
-  (): T
-  // A job's status changes over the different update phaes. See comments for
-  // phases below.
-  status: JobStatus
-  // Any operations performed by the job that directly mutates the DOM are
-  // buffered inside the job's ops queue, and only flushed in the commit phase.
-  // These ops are queued by calling `queueNodeOp` inside the job function.
-  ops: Op[]
-  // Any post DOM mutation side-effects (updated / mounted hooks, refs) are
-  // buffered inside the job's effects queue.
-  // Effects are queued by calling `queuePostEffect` inside the job function.
-  postEffects: Function[]
-  // A job may queue other jobs (e.g. a parent component update triggers the
-  // update of a child component). Jobs queued by another job is kept in the
-  // parent's children array, so that in case the parent job is invalidated,
-  // all its children can be invalidated as well (recursively).
-  children: Job[]
-  // Sometimes it's inevitable for a stage fn to produce some side effects
-  // (e.g. a component instance sets up an ReactiveEffect). In those cases the stage fn
-  // can return a cleanup function which will be called when the job is
-  // invalidated.
-  cleanup: T | null
-  // The expiration time is a timestamp past which the job needs to
-  // be force-committed regardless of frame budget.
-  // Why do we need an expiration time? Because a job may get invalidated before
-  // it is fully commited. If it keeps getting invalidated, we may "starve" the
-  // system and never apply any commits as jobs keep getting invalidated. The
-  // expiration time sets a limit on how long before a job can keep getting
-  // invalidated before it must be comitted.
-  expiration: number
-}
-
-const enum JobStatus {
-  IDLE = 0,
-  PENDING_STAGE,
-  PENDING_COMMIT
-}
-
-// Priorities for different types of jobs. This number is added to the
-// current time when a new job is queued to calculate the expiration time
-// for that job.
-//
-// Currently we have only one type which expires 500ms after it is initially
-// queued. There could be higher/lower priorities in the future.
-const enum JobPriorities {
-  NORMAL = 500
-}
-
-// There can be only one job being patched at one time. This allows us to
-// automatically "capture" and buffer the node ops and post effects queued
-// during a job.
-let currentJob: Job | null = null
-
-// Indicates we have a flush pending.
-let hasPendingFlush = false
-
-// A timestamp that indicates when a flush was started.
-let flushStartTimestamp: number = 0
-
-// The frame budget is the maximum amount of time passed while performing
-// "stage" work before we need to yield back to the browser.
-// Aiming for 60fps. Maybe we need to dynamically adjust this?
-const frameBudget = __JSDOM__ ? Infinity : 1000 / 60
-
-const getNow = () => performance.now()
-
-// An entire update consists of 4 phases:
-
-// 1. Stage phase. Render functions are called, diffs are performed, new
-//    component instances are created. However, no side-effects should be
-//    performed (i.e. no lifecycle hooks, no direct DOM operations).
-const stageQueue: Job[] = []
-
-// 2. Commit phase. This is only reached when the stageQueue has been depleted.
-//    Node ops are applied - in the browser, this means DOM is actually mutated
-//    during this phase. If a job is committed, it's post effects are then
-//    queued for the next phase.
-const commitQueue: Job[] = []
-
-// 3. Post-commit effects phase. Effect callbacks are only queued after a
-//    successful commit. These include callbacks that need to be invoked
-//    after DOM mutation - i.e. refs, mounted & updated hooks. This queue is
-//    flushed in reverse because child component effects are queued after but
-//    should be invoked before the parent's.
-const postEffectsQueue: Function[] = []
-
-// 4. NextTick phase. This is the user's catch-all mechanism for deferring
-//    work after a complete update cycle.
-const nextTickQueue: Function[] = []
-const pendingRejectors: ErrorHandler[] = []
-
-// Error handling --------------------------------------------------------------
-
-type ErrorHandler = (err: Error) => any
-
-let globalHandler: ErrorHandler
-
-export function handleSchedulerError(handler: ErrorHandler) {
-  globalHandler = handler
-}
-
-function handleError(err: Error) {
-  if (globalHandler) globalHandler(err)
-  pendingRejectors.forEach(handler => {
-    handler(err)
-  })
-}
-
-// Microtask defer -------------------------------------------------------------
-// For batching state mutations before we start an update. This does
-// NOT yield to the browser.
-
-const p = Promise.resolve()
-
-function flushAfterMicroTask() {
-  flushStartTimestamp = getNow()
-  return p.then(flush).catch(handleError)
-}
-
-// Macrotask defer -------------------------------------------------------------
-// For time slicing. This uses the window postMessage event to "yield"
-// to the browser so that other user events can trigger in between. This keeps
-// the app responsive even when performing large amount of JavaScript work.
-
-const key = `$vueTick`
-
-window.addEventListener(
-  'message',
-  event => {
-    if (event.source !== window || event.data !== key) {
-      return
-    }
-    flushStartTimestamp = getNow()
-    try {
-      flush()
-    } catch (e) {
-      handleError(e)
-    }
-  },
-  false
-)
-
-function flushAfterMacroTask() {
-  window.postMessage(key, `*`)
-}
-
-// API -------------------------------------------------------------------------
-
-// This is the main API of the scheduler. The raw job can actually be any
-// function, but since they are invalidated by identity, it is important that
-// a component's update job is a consistent function across its lifecycle -
-// in the renderer, it's actually instance._update which is in turn
-// an ReactiveEffect function.
-export function queueJob(rawJob: Function) {
-  const job = rawJob as Job
-  if (currentJob) {
-    currentJob.children.push(job)
-  }
-  // Let's see if this invalidates any work that
-  // has already been staged.
-  if (job.status === JobStatus.PENDING_COMMIT) {
-    // staged job invalidated
-    invalidateJob(job)
-    // re-insert it into the stage queue
-    requeueInvalidatedJob(job)
-  } else if (job.status !== JobStatus.PENDING_STAGE) {
-    // a new job
-    queueJobForStaging(job)
-  }
-  if (!hasPendingFlush) {
-    hasPendingFlush = true
-    flushAfterMicroTask()
-  }
-}
-
-export function queuePostEffect(fn: Function) {
-  if (currentJob) {
-    currentJob.postEffects.push(fn)
-  } else {
-    postEffectsQueue.push(fn)
-  }
-}
-
-export function flushEffects() {
-  // post commit hooks (updated, mounted)
-  // this queue is flushed in reverse becuase these hooks should be invoked
-  // child first
-  let i = postEffectsQueue.length
-  while (i--) {
-    postEffectsQueue[i]()
-  }
-  postEffectsQueue.length = 0
-}
-
-export function queueNodeOp(op: Op) {
-  if (currentJob) {
-    currentJob.ops.push(op)
-  } else {
-    applyOp(op)
-  }
-}
-
-// The original nextTick now needs to be reworked so that the callback only
-// triggers after the next commit, when all node ops and post effects have been
-// completed.
-export function nextTick<T>(fn?: () => T): Promise<T> {
-  return new Promise((resolve, reject) => {
-    p.then(() => {
-      if (hasPendingFlush) {
-        nextTickQueue.push(() => {
-          resolve(fn ? fn() : undefined)
-        })
-        pendingRejectors.push(err => {
-          if (fn) fn()
-          reject(err)
-        })
-      } else {
-        resolve(fn ? fn() : undefined)
-      }
-    }).catch(reject)
-  })
-}
-
-// Internals -------------------------------------------------------------------
-
-function flush(): void {
-  let job
-  while (true) {
-    job = stageQueue.shift()
-    if (job) {
-      stageJob(job)
-    } else {
-      break
-    }
-    if (!__COMPAT__) {
-      const now = getNow()
-      if (now - flushStartTimestamp > frameBudget && job.expiration > now) {
-        break
-      }
-    }
-  }
-
-  if (stageQueue.length === 0) {
-    // all done, time to commit!
-    for (let i = 0; i < commitQueue.length; i++) {
-      commitJob(commitQueue[i])
-    }
-    commitQueue.length = 0
-    flushEffects()
-    // some post commit hook triggered more updates...
-    if (stageQueue.length > 0) {
-      if (!__COMPAT__ && getNow() - flushStartTimestamp > frameBudget) {
-        return flushAfterMacroTask()
-      } else {
-        // not out of budget yet, flush sync
-        return flush()
-      }
-    }
-    // now we are really done
-    hasPendingFlush = false
-    pendingRejectors.length = 0
-    for (let i = 0; i < nextTickQueue.length; i++) {
-      nextTickQueue[i]()
-    }
-    nextTickQueue.length = 0
-  } else {
-    // got more job to do
-    // shouldn't reach here in compat mode, because the stageQueue is
-    // guarunteed to have been depleted
-    flushAfterMacroTask()
-  }
-}
-
-function resetJob(job: Job) {
-  job.ops.length = 0
-  job.postEffects.length = 0
-  job.children.length = 0
-}
-
-function queueJobForStaging(job: Job) {
-  job.ops = job.ops || []
-  job.postEffects = job.postEffects || []
-  job.children = job.children || []
-  resetJob(job)
-  // inherit parent job's expiration deadline
-  job.expiration = currentJob
-    ? currentJob.expiration
-    : getNow() + JobPriorities.NORMAL
-  stageQueue.push(job)
-  job.status = JobStatus.PENDING_STAGE
-}
-
-function invalidateJob(job: Job) {
-  // recursively invalidate all child jobs
-  const { children } = job
-  for (let i = 0; i < children.length; i++) {
-    const child = children[i]
-    if (child.status === JobStatus.PENDING_COMMIT) {
-      invalidateJob(child)
-    } else if (child.status === JobStatus.PENDING_STAGE) {
-      stageQueue.splice(stageQueue.indexOf(child), 1)
-      child.status = JobStatus.IDLE
-    }
-  }
-  if (job.cleanup) {
-    job.cleanup()
-    job.cleanup = null
-  }
-  resetJob(job)
-  // remove from commit queue
-  commitQueue.splice(commitQueue.indexOf(job), 1)
-  job.status = JobStatus.IDLE
-}
-
-function requeueInvalidatedJob(job: Job) {
-  // With varying priorities we should insert job at correct position
-  // based on expiration time.
-  for (let i = 0; i < stageQueue.length; i++) {
-    if (job.expiration < stageQueue[i].expiration) {
-      stageQueue.splice(i, 0, job)
-      job.status = JobStatus.PENDING_STAGE
-      return
-    }
-  }
-  stageQueue.push(job)
-  job.status = JobStatus.PENDING_STAGE
-}
-
-function stageJob(job: Job) {
-  // job with existing ops means it's already been patched in a low priority queue
-  if (job.ops.length === 0) {
-    currentJob = job
-    job.cleanup = job()
-    currentJob = null
-    commitQueue.push(job)
-    job.status = JobStatus.PENDING_COMMIT
-  }
-}
-
-function commitJob(job: Job) {
-  const { ops, postEffects } = job
-  for (let i = 0; i < ops.length; i++) {
-    applyOp(ops[i])
-  }
-  // queue post commit cbs
-  if (postEffects) {
-    postEffectsQueue.push(...postEffects)
-  }
-  resetJob(job)
-  job.status = JobStatus.IDLE
-}
-
-function applyOp(op: Op) {
-  const fn = op[0]
-  // optimize for more common cases
-  // only patchData needs 8 arguments
-  if (op.length <= 3) {
-    fn(op[1], op[2], op[3])
-  } else {
-    fn(op[1], op[2], op[3], op[4], op[5], op[6], op[7], op[8])
-  }
-}
index 1f23bf403ca922de943868c408043da940eb656d..002aab837d4ade7b1cc3980ea2fafbe49b1d05da 100644 (file)
@@ -1,4 +1,5 @@
 export const EMPTY_OBJ: { readonly [key: string]: any } = Object.freeze({})
+export const EMPTY_ARR: [] = []
 
 export const NOOP = () => {}
 
index 69423b402c405e2f007a045364d27555be4bbbfb..8c69e693ba670628dda9cfc9904bd9de878d1506 100644 (file)
@@ -24,7 +24,6 @@
       "@vue/runtime-dom": ["packages/runtime-dom/src"],
       "@vue/runtime-test": ["packages/runtime-test/src"],
       "@vue/observer": ["packages/observer/src"],
-      "@vue/scheduler": ["packages/scheduler/src"],
       "@vue/compiler-core": ["packages/compiler-core/src"],
       "@vue/server-renderer": ["packages/server-renderer/src"]
     }