-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(
// TODO:
// - component
-// - lifecycle
+// - lifecycle / refs
+// - keep alive
// - app context
// - svg
-// - refs
// - hydration
// - warning context
// - parent chain
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
}
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
}
}
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
)
}
}
- if (oldProps !== emptyObj) {
+ if (oldProps !== EMPTY_OBJ) {
for (const key in oldProps) {
if (!(key in newProps)) {
hostPatchProp(
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
}
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
}
},
{
- scheduler: e => e() // TODO use proper scheduler
+ scheduler: queueJob
}
- ) as any
+ )
}
function updateComponent(
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)
// 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--) {
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) {
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
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 {
--- /dev/null
+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)
+ }
+}
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
return vnode
}
-export function normalizeVNode(child: any): VNode {
+export function normalizeVNode(child: VNodeChild): VNode {
if (child == null) {
// empty placeholder
return createVNode(Empty)
+++ /dev/null
-__tests__/
-__mocks__/
-dist/packages
\ No newline at end of file
+++ /dev/null
-# @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.
+++ /dev/null
-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'])
- })
-})
+++ /dev/null
-'use strict'
-
-if (process.env.NODE_ENV === 'production') {
- module.exports = require('./dist/scheduler.cjs.prod.js')
-} else {
- module.exports = require('./dist/scheduler.cjs.js')
-}
+++ /dev/null
-{
- "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"
-}
+++ /dev/null
-// 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])
- }
-}
export const EMPTY_OBJ: { readonly [key: string]: any } = Object.freeze({})
+export const EMPTY_ARR: [] = []
export const NOOP = () => {}
"@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"]
}