immutable,
AutorunOptions
} from '@vue/observer'
-import { queueJob, handleSchedulerError, nextTick } from '@vue/scheduler'
+import {
+ queueJob,
+ handleSchedulerError,
+ nextTick,
+ queuePostCommitCb,
+ flushPostCommitCbs
+} from '@vue/scheduler'
import { VNodeFlags, ChildrenFlags } from './flags'
import { EMPTY_OBJ, reservedPropRE, isString } from '@vue/shared'
import {
}
}
- // Lifecycle Hooks -----------------------------------------------------------
-
- const lifecycleHooks: Function[] = []
- const vnodeUpdatedHooks: Function[] = []
-
- function queuePostCommitHook(fn: Function) {
- lifecycleHooks.push(fn)
- }
-
- function flushHooks() {
- let fn
- while ((fn = lifecycleHooks.pop())) {
- fn()
- }
- }
-
// mounting ------------------------------------------------------------------
function mount(
insertOrAppend(container, el, endNode)
}
if (ref) {
- queuePostCommitHook(() => {
+ queuePostCommitCb(() => {
ref(el)
})
}
if (data != null && data.vnodeMounted) {
- queuePostCommitHook(() => {
+ queuePostCommitCb(() => {
data.vnodeMounted(vnode)
})
}
mountComponentInstance(vnode, container, isSVG, endNode)
} else {
queueJob(() => {
- mountComponentInstance(vnode, container, isSVG, endNode)
- }, flushHooks)
+ const instance = mountComponentInstance(
+ vnode,
+ container,
+ isSVG,
+ endNode
+ )
+ // cleanup if mount is invalidated before committed
+ return () => {
+ teardownComponentInstance(instance)
+ }
+ })
}
}
}
const subTree = (handle.prevTree = vnode.children = renderFunctionalRoot(
vnode
))
- queuePostCommitHook(() => {
+ queuePostCommitCb(() => {
vnode.el = subTree.el as RenderNode
})
mount(subTree, container, vnode as MountedVNode, isSVG, endNode)
if (__COMPAT__) {
doMount()
} else {
- queueJob(doMount)
+ queueJob(() => {
+ doMount()
+ // cleanup if mount is invalidated before committed
+ return () => {
+ stop(handle.runner)
+ }
+ })
}
}
const nextTree = (handle.prevTree = current.children = renderFunctionalRoot(
current
))
- queuePostCommitHook(() => {
+ queuePostCommitCb(() => {
current.el = nextTree.el
})
patch(
const { children, childFlags } = vnode
switch (childFlags) {
case ChildrenFlags.SINGLE_VNODE:
- queuePostCommitHook(() => {
+ queuePostCommitCb(() => {
vnode.el = (children as MountedVNode).el
})
mount(children as VNode, container, contextVNode, isSVG, endNode)
vnode.el = placeholder.el
break
default:
- queuePostCommitHook(() => {
+ queuePostCommitCb(() => {
vnode.el = (children as MountedVNode[])[0].el
})
mountArrayChildren(
)
}
if (ref) {
- queuePostCommitHook(() => {
+ queuePostCommitCb(() => {
ref(target)
})
}
)
if (nextData != null && nextData.vnodeUpdated) {
- vnodeUpdatedHooks.push(() => {
- nextData.vnodeUpdated(nextVNode, prevVNode)
- })
+ // TODO fix me
+ // vnodeUpdatedHooks.push(() => {
+ // nextData.vnodeUpdated(nextVNode, prevVNode)
+ // })
}
}
// then retrieve its next sibling to use as the end node for patchChildren.
const endNode = platformNextSibling(getVNodeLastEl(prevVNode))
const { childFlags, children } = nextVNode
- queuePostCommitHook(() => {
+ queuePostCommitCb(() => {
switch (childFlags) {
case ChildrenFlags.SINGLE_VNODE:
nextVNode.el = (children as MountedVNode).el
container: RenderNode | null,
isSVG: boolean,
endNode: RenderNode | null
- ) {
+ ): ComponentInstance {
if (__DEV__) {
pushWarningContext(vnode)
}
$options: { beforeMount, renderTracked, renderTriggered }
} = instance
- if (beforeMount) {
- callLifecycleHookWithHandler(beforeMount, $proxy, ErrorTypes.BEFORE_MOUNT)
- }
-
const queueUpdate = (instance.$forceUpdate = () => {
- queueJob(instance._updateHandle, flushHooks)
+ queueJob(instance._updateHandle)
})
const autorunOptions: AutorunOptions = {
if (instance._mounted) {
updateComponentInstance(instance, isSVG)
} else {
+ if (beforeMount) {
+ callLifecycleHookWithHandler(
+ beforeMount,
+ $proxy,
+ ErrorTypes.BEFORE_MOUNT
+ )
+ }
+
// this will be executed synchronously right here
instance.$vnode = renderInstanceRoot(instance) as MountedVNode
- queuePostCommitHook(() => {
+ queuePostCommitCb(() => {
vnode.el = instance.$vnode.el
if (__COMPAT__) {
// expose __vue__ for devtools
if (__DEV__) {
popWarningContext()
}
+
+ return instance
}
function updateComponentInstance(
instance
) as MountedVNode)
- queuePostCommitHook(() => {
+ queuePostCommitCb(() => {
const el = nextVNode.el as RenderNode
if (__COMPAT__) {
// expose __vue__ for devtools
nextVNode
)
}
- if (vnodeUpdatedHooks.length > 0) {
- const vnodeUpdatedHooksForCurrentInstance = vnodeUpdatedHooks.slice()
- vnodeUpdatedHooks.length = 0
- queuePostCommitHook(() => {
- for (let i = 0; i < vnodeUpdatedHooksForCurrentInstance.length; i++) {
- vnodeUpdatedHooksForCurrentInstance[i]()
- }
- })
- }
+
+ // TODO fix me
+ // if (vnodeUpdatedHooks.length > 0) {
+ // const vnodeUpdatedHooksForCurrentInstance = vnodeUpdatedHooks.slice()
+ // vnodeUpdatedHooks.length = 0
+ // for (let i = 0; i < vnodeUpdatedHooksForCurrentInstance.length; i++) {
+ // vnodeUpdatedHooksForCurrentInstance[i]()
+ // }
+ // }
})
const container = platformParentNode(prevVNode.el) as RenderNode
const {
$vnode,
$proxy,
- _updateHandle,
$options: { beforeUnmount, unmounted }
} = instance
if (beforeUnmount) {
if ($vnode) {
unmount($vnode)
}
- stop(_updateHandle)
teardownComponentInstance(instance)
instance._unmounted = true
if (unmounted) {
if (__DEV__) {
popWarningContext()
}
- queuePostCommitHook(() => {
+ queuePostCommitCb(() => {
callActivatedHook(instance, true)
})
}
}
}
if (__COMPAT__) {
- flushHooks()
+ flushPostCommitCbs()
return vnode && vnode.flags & VNodeFlags.COMPONENT_STATEFUL
? (vnode.children as ComponentInstance).$proxy
: null
-import { queueJob, nextTick } from '../src/index'
+import { queueJob, queuePostCommitCb, nextTick } from '../src/index'
describe('scheduler', () => {
it('queueJob', async () => {
expect(calls).toEqual(['job1', 'job2'])
})
- it('queueJob w/ postFlushCb', async () => {
+ it('queueJob w/ postCommitCb', async () => {
const calls: any = []
const job1 = () => {
calls.push('job1')
+ queuePostCommitCb(cb1)
}
const job2 = () => {
calls.push('job2')
+ queuePostCommitCb(cb2)
}
const cb1 = () => {
calls.push('cb1')
const cb2 = () => {
calls.push('cb2')
}
- queueJob(job1, cb1)
- queueJob(job2, cb2)
+ queueJob(job1)
+ queueJob(job2)
await nextTick()
- expect(calls).toEqual(['job1', 'job2', 'cb1', 'cb2'])
+ // 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')
+ queuePostCommitCb(cb1)
// job1 queues job2
- queueJob(job2, cb2)
+ queueJob(job2)
}
const job2 = () => {
calls.push('job2')
+ queuePostCommitCb(cb2)
}
const cb1 = () => {
calls.push('cb1')
const cb2 = () => {
calls.push('cb2')
}
- queueJob(job1, cb1)
+ queueJob(job1)
expect(calls).toEqual([])
await nextTick()
- expect(calls).toEqual(['job1', 'job2', 'cb1', 'cb2'])
+ expect(calls).toEqual(['job1', 'job2', 'cb2', 'cb1'])
})
it('should dedupe queued tasks', async () => {
expect(calls).toEqual(['job1', 'job2'])
})
- it('queueJob inside postFlushCb', async () => {
+ it('queueJob inside postCommitCb', async () => {
const calls: any = []
const job1 = () => {
calls.push('job1')
+ queuePostCommitCb(cb1)
}
const cb1 = () => {
// queue another job in postFlushCb
calls.push('cb1')
- queueJob(job2, cb2)
+ queueJob(job2)
}
const job2 = () => {
calls.push('job2')
+ queuePostCommitCb(cb2)
}
const cb2 = () => {
calls.push('cb2')
}
- queueJob(job1, cb1)
- queueJob(job2, cb2)
+ queueJob(job1)
+ queueJob(job2)
await nextTick()
- expect(calls).toEqual(['job1', 'job2', 'cb1', 'cb2', 'job2', 'cb2'])
+ expect(calls).toEqual(['job1', 'job2', 'cb2', 'cb1', 'job2', 'cb2'])
})
})
+// TODO infinite updates detection
+
import { Op, setCurrentOps } from './patchNodeOps'
interface Job extends Function {
ops: Op[]
- post: Function | null
+ post: Function[]
+ cleanup: Function | null
expiration: number
}
type ErrorHandler = (err: Error) => any
+let currentJob: Job | null = null
+
let start: number = 0
const getNow = () => window.performance.now()
const frameBudget = __JSDOM__ ? Infinity : 1000 / 60
let hasPendingFlush = false
-export function queueJob(rawJob: Function, postJob?: Function | null) {
+export function queueJob(rawJob: Function) {
const job = rawJob as Job
- job.post = postJob || null
job.ops = job.ops || []
+ job.post = job.post || []
// 1. let's see if this invalidates any work that
// has already been done.
const commitIndex = commitQueue.indexOf(job)
}
}
+export function queuePostCommitCb(fn: Function) {
+ if (currentJob) {
+ currentJob.post.push(fn)
+ } else {
+ postCommitQueue.push(fn)
+ }
+}
+
+export function flushPostCommitCbs() {
+ // post commit hooks (updated, mounted)
+ // this queue is flushed in reverse becuase these hooks should be invoked
+ // child first
+ let job
+ while ((job = postCommitQueue.pop())) {
+ job()
+ }
+}
+
function flush(): void {
let job
while (true) {
// all done, time to commit!
while ((job = commitQueue.shift())) {
commitJob(job)
- if (job.post && postCommitQueue.indexOf(job.post) < 0) {
- postCommitQueue.push(job.post)
+ if (job.post) {
+ postCommitQueue.push(...job.post)
+ job.post.length = 0
}
}
- // post commit hooks (updated, mounted)
- while ((job = postCommitQueue.shift())) {
- job()
- }
+ flushPostCommitCbs()
// some post commit hook triggered more updates...
if (patchQueue.length > 0) {
if (!__COMPAT__ && getNow() - start > frameBudget) {
// job with existing ops means it's already been patched in a low priority queue
if (job.ops.length === 0) {
setCurrentOps(job.ops)
- job()
+ currentJob = job
+ job.cleanup = job()
+ currentJob = null
setCurrentOps(null)
commitQueue.push(job)
}
function invalidateJob(job: Job) {
job.ops.length = 0
+ job.post.length = 0
+ if (job.cleanup) {
+ job.cleanup()
+ job.cleanup = null
+ }
}