diff options
| author | ruki <waruqi@gmail.com> | 2018-11-08 00:38:48 +0800 |
|---|---|---|
| committer | ruki <waruqi@gmail.com> | 2018-11-07 21:53:09 +0800 |
| commit | 26105034da4fcce7ac883c899d781f016559310d (patch) | |
| tree | c459a5dc4e3aa0972d9919033ece511ce76dd129 /node_modules/vue/src/core | |
| parent | 2c77f00f1a7ecb6c8192f9c16d3b2001b254a107 (diff) | |
| download | xmake-docs-26105034da4fcce7ac883c899d781f016559310d.tar.gz xmake-docs-26105034da4fcce7ac883c899d781f016559310d.zip | |
switch to vuepress
Diffstat (limited to 'node_modules/vue/src/core')
57 files changed, 6104 insertions, 0 deletions
diff --git a/node_modules/vue/src/core/components/index.js b/node_modules/vue/src/core/components/index.js new file mode 100644 index 00000000..5461c763 --- /dev/null +++ b/node_modules/vue/src/core/components/index.js @@ -0,0 +1,5 @@ +import KeepAlive from './keep-alive' + +export default { + KeepAlive +} diff --git a/node_modules/vue/src/core/components/keep-alive.js b/node_modules/vue/src/core/components/keep-alive.js new file mode 100644 index 00000000..fb4cf1e8 --- /dev/null +++ b/node_modules/vue/src/core/components/keep-alive.js @@ -0,0 +1,124 @@ +/* @flow */ + +import { isRegExp, remove } from 'shared/util' +import { getFirstComponentChild } from 'core/vdom/helpers/index' + +type VNodeCache = { [key: string]: ?VNode }; + +function getComponentName (opts: ?VNodeComponentOptions): ?string { + return opts && (opts.Ctor.options.name || opts.tag) +} + +function matches (pattern: string | RegExp | Array<string>, name: string): boolean { + if (Array.isArray(pattern)) { + return pattern.indexOf(name) > -1 + } else if (typeof pattern === 'string') { + return pattern.split(',').indexOf(name) > -1 + } else if (isRegExp(pattern)) { + return pattern.test(name) + } + /* istanbul ignore next */ + return false +} + +function pruneCache (keepAliveInstance: any, filter: Function) { + const { cache, keys, _vnode } = keepAliveInstance + for (const key in cache) { + const cachedNode: ?VNode = cache[key] + if (cachedNode) { + const name: ?string = getComponentName(cachedNode.componentOptions) + if (name && !filter(name)) { + pruneCacheEntry(cache, key, keys, _vnode) + } + } + } +} + +function pruneCacheEntry ( + cache: VNodeCache, + key: string, + keys: Array<string>, + current?: VNode +) { + const cached = cache[key] + if (cached && (!current || cached.tag !== current.tag)) { + cached.componentInstance.$destroy() + } + cache[key] = null + remove(keys, key) +} + +const patternTypes: Array<Function> = [String, RegExp, Array] + +export default { + name: 'keep-alive', + abstract: true, + + props: { + include: patternTypes, + exclude: patternTypes, + max: [String, Number] + }, + + created () { + this.cache = Object.create(null) + this.keys = [] + }, + + destroyed () { + for (const key in this.cache) { + pruneCacheEntry(this.cache, key, this.keys) + } + }, + + mounted () { + this.$watch('include', val => { + pruneCache(this, name => matches(val, name)) + }) + this.$watch('exclude', val => { + pruneCache(this, name => !matches(val, name)) + }) + }, + + render () { + const slot = this.$slots.default + const vnode: VNode = getFirstComponentChild(slot) + const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions + if (componentOptions) { + // check pattern + const name: ?string = getComponentName(componentOptions) + const { include, exclude } = this + if ( + // not included + (include && (!name || !matches(include, name))) || + // excluded + (exclude && name && matches(exclude, name)) + ) { + return vnode + } + + const { cache, keys } = this + const key: ?string = vnode.key == null + // same constructor may get registered as different local components + // so cid alone is not enough (#3269) + ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') + : vnode.key + if (cache[key]) { + vnode.componentInstance = cache[key].componentInstance + // make current key freshest + remove(keys, key) + keys.push(key) + } else { + cache[key] = vnode + keys.push(key) + // prune oldest entry + if (this.max && keys.length > parseInt(this.max)) { + pruneCacheEntry(cache, keys[0], keys, this._vnode) + } + } + + vnode.data.keepAlive = true + } + return vnode || (slot && slot[0]) + } +} diff --git a/node_modules/vue/src/core/config.js b/node_modules/vue/src/core/config.js new file mode 100644 index 00000000..f25f09d1 --- /dev/null +++ b/node_modules/vue/src/core/config.js @@ -0,0 +1,121 @@ +/* @flow */ + +import { + no, + noop, + identity +} from 'shared/util' + +import { LIFECYCLE_HOOKS } from 'shared/constants' + +export type Config = { + // user + optionMergeStrategies: { [key: string]: Function }; + silent: boolean; + productionTip: boolean; + performance: boolean; + devtools: boolean; + errorHandler: ?(err: Error, vm: Component, info: string) => void; + warnHandler: ?(msg: string, vm: Component, trace: string) => void; + ignoredElements: Array<string | RegExp>; + keyCodes: { [key: string]: number | Array<number> }; + + // platform + isReservedTag: (x?: string) => boolean; + isReservedAttr: (x?: string) => boolean; + parsePlatformTagName: (x: string) => string; + isUnknownElement: (x?: string) => boolean; + getTagNamespace: (x?: string) => string | void; + mustUseProp: (tag: string, type: ?string, name: string) => boolean; + + // legacy + _lifecycleHooks: Array<string>; +}; + +export default ({ + /** + * Option merge strategies (used in core/util/options) + */ + // $flow-disable-line + optionMergeStrategies: Object.create(null), + + /** + * Whether to suppress warnings. + */ + silent: false, + + /** + * Show production mode tip message on boot? + */ + productionTip: process.env.NODE_ENV !== 'production', + + /** + * Whether to enable devtools + */ + devtools: process.env.NODE_ENV !== 'production', + + /** + * Whether to record perf + */ + performance: false, + + /** + * Error handler for watcher errors + */ + errorHandler: null, + + /** + * Warn handler for watcher warns + */ + warnHandler: null, + + /** + * Ignore certain custom elements + */ + ignoredElements: [], + + /** + * Custom user key aliases for v-on + */ + // $flow-disable-line + keyCodes: Object.create(null), + + /** + * Check if a tag is reserved so that it cannot be registered as a + * component. This is platform-dependent and may be overwritten. + */ + isReservedTag: no, + + /** + * Check if an attribute is reserved so that it cannot be used as a component + * prop. This is platform-dependent and may be overwritten. + */ + isReservedAttr: no, + + /** + * Check if a tag is an unknown element. + * Platform-dependent. + */ + isUnknownElement: no, + + /** + * Get the namespace of an element + */ + getTagNamespace: noop, + + /** + * Parse the real tag name for the specific platform. + */ + parsePlatformTagName: identity, + + /** + * Check if an attribute must be bound using property, e.g. value + * Platform-dependent. + */ + mustUseProp: no, + + /** + * Exposed for legacy reasons + */ + _lifecycleHooks: LIFECYCLE_HOOKS +}: Config) diff --git a/node_modules/vue/src/core/global-api/assets.js b/node_modules/vue/src/core/global-api/assets.js new file mode 100644 index 00000000..d975a6a5 --- /dev/null +++ b/node_modules/vue/src/core/global-api/assets.js @@ -0,0 +1,34 @@ +/* @flow */ + +import { ASSET_TYPES } from 'shared/constants' +import { isPlainObject, validateComponentName } from '../util/index' + +export function initAssetRegisters (Vue: GlobalAPI) { + /** + * Create asset registration methods. + */ + ASSET_TYPES.forEach(type => { + Vue[type] = function ( + id: string, + definition: Function | Object + ): Function | Object | void { + if (!definition) { + return this.options[type + 's'][id] + } else { + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production' && type === 'component') { + validateComponentName(id) + } + if (type === 'component' && isPlainObject(definition)) { + definition.name = definition.name || id + definition = this.options._base.extend(definition) + } + if (type === 'directive' && typeof definition === 'function') { + definition = { bind: definition, update: definition } + } + this.options[type + 's'][id] = definition + return definition + } + } + }) +} diff --git a/node_modules/vue/src/core/global-api/extend.js b/node_modules/vue/src/core/global-api/extend.js new file mode 100644 index 00000000..c1bbda3f --- /dev/null +++ b/node_modules/vue/src/core/global-api/extend.js @@ -0,0 +1,95 @@ +/* @flow */ + +import { ASSET_TYPES } from 'shared/constants' +import { defineComputed, proxy } from '../instance/state' +import { extend, mergeOptions, validateComponentName } from '../util/index' + +export function initExtend (Vue: GlobalAPI) { + /** + * Each instance constructor, including Vue, has a unique + * cid. This enables us to create wrapped "child + * constructors" for prototypal inheritance and cache them. + */ + Vue.cid = 0 + let cid = 1 + + /** + * Class inheritance + */ + Vue.extend = function (extendOptions: Object): Function { + extendOptions = extendOptions || {} + const Super = this + const SuperId = Super.cid + const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}) + if (cachedCtors[SuperId]) { + return cachedCtors[SuperId] + } + + const name = extendOptions.name || Super.options.name + if (process.env.NODE_ENV !== 'production' && name) { + validateComponentName(name) + } + + const Sub = function VueComponent (options) { + this._init(options) + } + Sub.prototype = Object.create(Super.prototype) + Sub.prototype.constructor = Sub + Sub.cid = cid++ + Sub.options = mergeOptions( + Super.options, + extendOptions + ) + Sub['super'] = Super + + // For props and computed properties, we define the proxy getters on + // the Vue instances at extension time, on the extended prototype. This + // avoids Object.defineProperty calls for each instance created. + if (Sub.options.props) { + initProps(Sub) + } + if (Sub.options.computed) { + initComputed(Sub) + } + + // allow further extension/mixin/plugin usage + Sub.extend = Super.extend + Sub.mixin = Super.mixin + Sub.use = Super.use + + // create asset registers, so extended classes + // can have their private assets too. + ASSET_TYPES.forEach(function (type) { + Sub[type] = Super[type] + }) + // enable recursive self-lookup + if (name) { + Sub.options.components[name] = Sub + } + + // keep a reference to the super options at extension time. + // later at instantiation we can check if Super's options have + // been updated. + Sub.superOptions = Super.options + Sub.extendOptions = extendOptions + Sub.sealedOptions = extend({}, Sub.options) + + // cache constructor + cachedCtors[SuperId] = Sub + return Sub + } +} + +function initProps (Comp) { + const props = Comp.options.props + for (const key in props) { + proxy(Comp.prototype, `_props`, key) + } +} + +function initComputed (Comp) { + const computed = Comp.options.computed + for (const key in computed) { + defineComputed(Comp.prototype, key, computed[key]) + } +} diff --git a/node_modules/vue/src/core/global-api/index.js b/node_modules/vue/src/core/global-api/index.js new file mode 100644 index 00000000..03ffa0fb --- /dev/null +++ b/node_modules/vue/src/core/global-api/index.js @@ -0,0 +1,62 @@ +/* @flow */ + +import config from '../config' +import { initUse } from './use' +import { initMixin } from './mixin' +import { initExtend } from './extend' +import { initAssetRegisters } from './assets' +import { set, del } from '../observer/index' +import { ASSET_TYPES } from 'shared/constants' +import builtInComponents from '../components/index' + +import { + warn, + extend, + nextTick, + mergeOptions, + defineReactive +} from '../util/index' + +export function initGlobalAPI (Vue: GlobalAPI) { + // config + const configDef = {} + configDef.get = () => config + if (process.env.NODE_ENV !== 'production') { + configDef.set = () => { + warn( + 'Do not replace the Vue.config object, set individual fields instead.' + ) + } + } + Object.defineProperty(Vue, 'config', configDef) + + // exposed util methods. + // NOTE: these are not considered part of the public API - avoid relying on + // them unless you are aware of the risk. + Vue.util = { + warn, + extend, + mergeOptions, + defineReactive + } + + Vue.set = set + Vue.delete = del + Vue.nextTick = nextTick + + Vue.options = Object.create(null) + ASSET_TYPES.forEach(type => { + Vue.options[type + 's'] = Object.create(null) + }) + + // this is used to identify the "base" constructor to extend all plain-object + // components with in Weex's multi-instance scenarios. + Vue.options._base = Vue + + extend(Vue.options.components, builtInComponents) + + initUse(Vue) + initMixin(Vue) + initExtend(Vue) + initAssetRegisters(Vue) +} diff --git a/node_modules/vue/src/core/global-api/mixin.js b/node_modules/vue/src/core/global-api/mixin.js new file mode 100644 index 00000000..36017a32 --- /dev/null +++ b/node_modules/vue/src/core/global-api/mixin.js @@ -0,0 +1,10 @@ +/* @flow */ + +import { mergeOptions } from '../util/index' + +export function initMixin (Vue: GlobalAPI) { + Vue.mixin = function (mixin: Object) { + this.options = mergeOptions(this.options, mixin) + return this + } +} diff --git a/node_modules/vue/src/core/global-api/use.js b/node_modules/vue/src/core/global-api/use.js new file mode 100644 index 00000000..a5b69988 --- /dev/null +++ b/node_modules/vue/src/core/global-api/use.js @@ -0,0 +1,23 @@ +/* @flow */ + +import { toArray } from '../util/index' + +export function initUse (Vue: GlobalAPI) { + Vue.use = function (plugin: Function | Object) { + const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) + if (installedPlugins.indexOf(plugin) > -1) { + return this + } + + // additional parameters + const args = toArray(arguments, 1) + args.unshift(this) + if (typeof plugin.install === 'function') { + plugin.install.apply(plugin, args) + } else if (typeof plugin === 'function') { + plugin.apply(null, args) + } + installedPlugins.push(plugin) + return this + } +} diff --git a/node_modules/vue/src/core/index.js b/node_modules/vue/src/core/index.js new file mode 100644 index 00000000..daf6203b --- /dev/null +++ b/node_modules/vue/src/core/index.js @@ -0,0 +1,26 @@ +import Vue from './instance/index' +import { initGlobalAPI } from './global-api/index' +import { isServerRendering } from 'core/util/env' +import { FunctionalRenderContext } from 'core/vdom/create-functional-component' + +initGlobalAPI(Vue) + +Object.defineProperty(Vue.prototype, '$isServer', { + get: isServerRendering +}) + +Object.defineProperty(Vue.prototype, '$ssrContext', { + get () { + /* istanbul ignore next */ + return this.$vnode && this.$vnode.ssrContext + } +}) + +// expose FunctionalRenderContext for ssr runtime helper installation +Object.defineProperty(Vue, 'FunctionalRenderContext', { + value: FunctionalRenderContext +}) + +Vue.version = '__VERSION__' + +export default Vue diff --git a/node_modules/vue/src/core/instance/events.js b/node_modules/vue/src/core/instance/events.js new file mode 100644 index 00000000..58779996 --- /dev/null +++ b/node_modules/vue/src/core/instance/events.js @@ -0,0 +1,142 @@ +/* @flow */ + +import { + tip, + toArray, + hyphenate, + handleError, + formatComponentName +} from '../util/index' +import { updateListeners } from '../vdom/helpers/index' + +export function initEvents (vm: Component) { + vm._events = Object.create(null) + vm._hasHookEvent = false + // init parent attached events + const listeners = vm.$options._parentListeners + if (listeners) { + updateComponentListeners(vm, listeners) + } +} + +let target: any + +function add (event, fn, once) { + if (once) { + target.$once(event, fn) + } else { + target.$on(event, fn) + } +} + +function remove (event, fn) { + target.$off(event, fn) +} + +export function updateComponentListeners ( + vm: Component, + listeners: Object, + oldListeners: ?Object +) { + target = vm + updateListeners(listeners, oldListeners || {}, add, remove, vm) + target = undefined +} + +export function eventsMixin (Vue: Class<Component>) { + const hookRE = /^hook:/ + Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component { + const vm: Component = this + if (Array.isArray(event)) { + for (let i = 0, l = event.length; i < l; i++) { + this.$on(event[i], fn) + } + } else { + (vm._events[event] || (vm._events[event] = [])).push(fn) + // optimize hook:event cost by using a boolean flag marked at registration + // instead of a hash lookup + if (hookRE.test(event)) { + vm._hasHookEvent = true + } + } + return vm + } + + Vue.prototype.$once = function (event: string, fn: Function): Component { + const vm: Component = this + function on () { + vm.$off(event, on) + fn.apply(vm, arguments) + } + on.fn = fn + vm.$on(event, on) + return vm + } + + Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component { + const vm: Component = this + // all + if (!arguments.length) { + vm._events = Object.create(null) + return vm + } + // array of events + if (Array.isArray(event)) { + for (let i = 0, l = event.length; i < l; i++) { + this.$off(event[i], fn) + } + return vm + } + // specific event + const cbs = vm._events[event] + if (!cbs) { + return vm + } + if (!fn) { + vm._events[event] = null + return vm + } + if (fn) { + // specific handler + let cb + let i = cbs.length + while (i--) { + cb = cbs[i] + if (cb === fn || cb.fn === fn) { + cbs.splice(i, 1) + break + } + } + } + return vm + } + + Vue.prototype.$emit = function (event: string): Component { + const vm: Component = this + if (process.env.NODE_ENV !== 'production') { + const lowerCaseEvent = event.toLowerCase() + if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) { + tip( + `Event "${lowerCaseEvent}" is emitted in component ` + + `${formatComponentName(vm)} but the handler is registered for "${event}". ` + + `Note that HTML attributes are case-insensitive and you cannot use ` + + `v-on to listen to camelCase events when using in-DOM templates. ` + + `You should probably use "${hyphenate(event)}" instead of "${event}".` + ) + } + } + let cbs = vm._events[event] + if (cbs) { + cbs = cbs.length > 1 ? toArray(cbs) : cbs + const args = toArray(arguments, 1) + for (let i = 0, l = cbs.length; i < l; i++) { + try { + cbs[i].apply(vm, args) + } catch (e) { + handleError(e, vm, `event handler for "${event}"`) + } + } + } + return vm + } +} diff --git a/node_modules/vue/src/core/instance/index.js b/node_modules/vue/src/core/instance/index.js new file mode 100644 index 00000000..8a440231 --- /dev/null +++ b/node_modules/vue/src/core/instance/index.js @@ -0,0 +1,23 @@ +import { initMixin } from './init' +import { stateMixin } from './state' +import { renderMixin } from './render' +import { eventsMixin } from './events' +import { lifecycleMixin } from './lifecycle' +import { warn } from '../util/index' + +function Vue (options) { + if (process.env.NODE_ENV !== 'production' && + !(this instanceof Vue) + ) { + warn('Vue is a constructor and should be called with the `new` keyword') + } + this._init(options) +} + +initMixin(Vue) +stateMixin(Vue) +eventsMixin(Vue) +lifecycleMixin(Vue) +renderMixin(Vue) + +export default Vue diff --git a/node_modules/vue/src/core/instance/init.js b/node_modules/vue/src/core/instance/init.js new file mode 100644 index 00000000..ed2550d0 --- /dev/null +++ b/node_modules/vue/src/core/instance/init.js @@ -0,0 +1,150 @@ +/* @flow */ + +import config from '../config' +import { initProxy } from './proxy' +import { initState } from './state' +import { initRender } from './render' +import { initEvents } from './events' +import { mark, measure } from '../util/perf' +import { initLifecycle, callHook } from './lifecycle' +import { initProvide, initInjections } from './inject' +import { extend, mergeOptions, formatComponentName } from '../util/index' + +let uid = 0 + +export function initMixin (Vue: Class<Component>) { + Vue.prototype._init = function (options?: Object) { + const vm: Component = this + // a uid + vm._uid = uid++ + + let startTag, endTag + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production' && config.performance && mark) { + startTag = `vue-perf-start:${vm._uid}` + endTag = `vue-perf-end:${vm._uid}` + mark(startTag) + } + + // a flag to avoid this being observed + vm._isVue = true + // merge options + if (options && options._isComponent) { + // optimize internal component instantiation + // since dynamic options merging is pretty slow, and none of the + // internal component options needs special treatment. + initInternalComponent(vm, options) + } else { + vm.$options = mergeOptions( + resolveConstructorOptions(vm.constructor), + options || {}, + vm + ) + } + /* istanbul ignore else */ + if (process.env.NODE_ENV !== 'production') { + initProxy(vm) + } else { + vm._renderProxy = vm + } + // expose real self + vm._self = vm + initLifecycle(vm) + initEvents(vm) + initRender(vm) + callHook(vm, 'beforeCreate') + initInjections(vm) // resolve injections before data/props + initState(vm) + initProvide(vm) // resolve provide after data/props + callHook(vm, 'created') + + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production' && config.performance && mark) { + vm._name = formatComponentName(vm, false) + mark(endTag) + measure(`vue ${vm._name} init`, startTag, endTag) + } + + if (vm.$options.el) { + vm.$mount(vm.$options.el) + } + } +} + +export function initInternalComponent (vm: Component, options: InternalComponentOptions) { + const opts = vm.$options = Object.create(vm.constructor.options) + // doing this because it's faster than dynamic enumeration. + const parentVnode = options._parentVnode + opts.parent = options.parent + opts._parentVnode = parentVnode + opts._parentElm = options._parentElm + opts._refElm = options._refElm + + const vnodeComponentOptions = parentVnode.componentOptions + opts.propsData = vnodeComponentOptions.propsData + opts._parentListeners = vnodeComponentOptions.listeners + opts._renderChildren = vnodeComponentOptions.children + opts._componentTag = vnodeComponentOptions.tag + + if (options.render) { + opts.render = options.render + opts.staticRenderFns = options.staticRenderFns + } +} + +export function resolveConstructorOptions (Ctor: Class<Component>) { + let options = Ctor.options + if (Ctor.super) { + const superOptions = resolveConstructorOptions(Ctor.super) + const cachedSuperOptions = Ctor.superOptions + if (superOptions !== cachedSuperOptions) { + // super option changed, + // need to resolve new options. + Ctor.superOptions = superOptions + // check if there are any late-modified/attached options (#4976) + const modifiedOptions = resolveModifiedOptions(Ctor) + // update base extend options + if (modifiedOptions) { + extend(Ctor.extendOptions, modifiedOptions) + } + options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions) + if (options.name) { + options.components[options.name] = Ctor + } + } + } + return options +} + +function resolveModifiedOptions (Ctor: Class<Component>): ?Object { + let modified + const latest = Ctor.options + const extended = Ctor.extendOptions + const sealed = Ctor.sealedOptions + for (const key in latest) { + if (latest[key] !== sealed[key]) { + if (!modified) modified = {} + modified[key] = dedupe(latest[key], extended[key], sealed[key]) + } + } + return modified +} + +function dedupe (latest, extended, sealed) { + // compare latest and sealed to ensure lifecycle hooks won't be duplicated + // between merges + if (Array.isArray(latest)) { + const res = [] + sealed = Array.isArray(sealed) ? sealed : [sealed] + extended = Array.isArray(extended) ? extended : [extended] + for (let i = 0; i < latest.length; i++) { + // push original options and not sealed options to exclude duplicated options + if (extended.indexOf(latest[i]) >= 0 || sealed.indexOf(latest[i]) < 0) { + res.push(latest[i]) + } + } + return res + } else { + return latest + } +} diff --git a/node_modules/vue/src/core/instance/inject.js b/node_modules/vue/src/core/instance/inject.js new file mode 100644 index 00000000..e464e12a --- /dev/null +++ b/node_modules/vue/src/core/instance/inject.js @@ -0,0 +1,74 @@ +/* @flow */ + +import { hasOwn } from 'shared/util' +import { warn, hasSymbol } from '../util/index' +import { defineReactive, toggleObserving } from '../observer/index' + +export function initProvide (vm: Component) { + const provide = vm.$options.provide + if (provide) { + vm._provided = typeof provide === 'function' + ? provide.call(vm) + : provide + } +} + +export function initInjections (vm: Component) { + const result = resolveInject(vm.$options.inject, vm) + if (result) { + toggleObserving(false) + Object.keys(result).forEach(key => { + /* istanbul ignore else */ + if (process.env.NODE_ENV !== 'production') { + defineReactive(vm, key, result[key], () => { + warn( + `Avoid mutating an injected value directly since the changes will be ` + + `overwritten whenever the provided component re-renders. ` + + `injection being mutated: "${key}"`, + vm + ) + }) + } else { + defineReactive(vm, key, result[key]) + } + }) + toggleObserving(true) + } +} + +export function resolveInject (inject: any, vm: Component): ?Object { + if (inject) { + // inject is :any because flow is not smart enough to figure out cached + const result = Object.create(null) + const keys = hasSymbol + ? Reflect.ownKeys(inject).filter(key => { + /* istanbul ignore next */ + return Object.getOwnPropertyDescriptor(inject, key).enumerable + }) + : Object.keys(inject) + + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + const provideKey = inject[key].from + let source = vm + while (source) { + if (source._provided && hasOwn(source._provided, provideKey)) { + result[key] = source._provided[provideKey] + break + } + source = source.$parent + } + if (!source) { + if ('default' in inject[key]) { + const provideDefault = inject[key].default + result[key] = typeof provideDefault === 'function' + ? provideDefault.call(vm) + : provideDefault + } else if (process.env.NODE_ENV !== 'production') { + warn(`Injection "${key}" not found`, vm) + } + } + } + return result + } +} diff --git a/node_modules/vue/src/core/instance/lifecycle.js b/node_modules/vue/src/core/instance/lifecycle.js new file mode 100644 index 00000000..169a54a6 --- /dev/null +++ b/node_modules/vue/src/core/instance/lifecycle.js @@ -0,0 +1,336 @@ +/* @flow */ + +import config from '../config' +import Watcher from '../observer/watcher' +import { mark, measure } from '../util/perf' +import { createEmptyVNode } from '../vdom/vnode' +import { updateComponentListeners } from './events' +import { resolveSlots } from './render-helpers/resolve-slots' +import { toggleObserving } from '../observer/index' +import { pushTarget, popTarget } from '../observer/dep' + +import { + warn, + noop, + remove, + handleError, + emptyObject, + validateProp +} from '../util/index' + +export let activeInstance: any = null +export let isUpdatingChildComponent: boolean = false + +export function initLifecycle (vm: Component) { + const options = vm.$options + + // locate first non-abstract parent + let parent = options.parent + if (parent && !options.abstract) { + while (parent.$options.abstract && parent.$parent) { + parent = parent.$parent + } + parent.$children.push(vm) + } + + vm.$parent = parent + vm.$root = parent ? parent.$root : vm + + vm.$children = [] + vm.$refs = {} + + vm._watcher = null + vm._inactive = null + vm._directInactive = false + vm._isMounted = false + vm._isDestroyed = false + vm._isBeingDestroyed = false +} + +export function lifecycleMixin (Vue: Class<Component>) { + Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { + const vm: Component = this + if (vm._isMounted) { + callHook(vm, 'beforeUpdate') + } + const prevEl = vm.$el + const prevVnode = vm._vnode + const prevActiveInstance = activeInstance + activeInstance = vm + vm._vnode = vnode + // Vue.prototype.__patch__ is injected in entry points + // based on the rendering backend used. + if (!prevVnode) { + // initial render + vm.$el = vm.__patch__( + vm.$el, vnode, hydrating, false /* removeOnly */, + vm.$options._parentElm, + vm.$options._refElm + ) + // no need for the ref nodes after initial patch + // this prevents keeping a detached DOM tree in memory (#5851) + vm.$options._parentElm = vm.$options._refElm = null + } else { + // updates + vm.$el = vm.__patch__(prevVnode, vnode) + } + activeInstance = prevActiveInstance + // update __vue__ reference + if (prevEl) { + prevEl.__vue__ = null + } + if (vm.$el) { + vm.$el.__vue__ = vm + } + // if parent is an HOC, update its $el as well + if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) { + vm.$parent.$el = vm.$el + } + // updated hook is called by the scheduler to ensure that children are + // updated in a parent's updated hook. + } + + Vue.prototype.$forceUpdate = function () { + const vm: Component = this + if (vm._watcher) { + vm._watcher.update() + } + } + + Vue.prototype.$destroy = function () { + const vm: Component = this + if (vm._isBeingDestroyed) { + return + } + callHook(vm, 'beforeDestroy') + vm._isBeingDestroyed = true + // remove self from parent + const parent = vm.$parent + if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { + remove(parent.$children, vm) + } + // teardown watchers + if (vm._watcher) { + vm._watcher.teardown() + } + let i = vm._watchers.length + while (i--) { + vm._watchers[i].teardown() + } + // remove reference from data ob + // frozen object may not have observer. + if (vm._data.__ob__) { + vm._data.__ob__.vmCount-- + } + // call the last hook... + vm._isDestroyed = true + // invoke destroy hooks on current rendered tree + vm.__patch__(vm._vnode, null) + // fire destroyed hook + callHook(vm, 'destroyed') + // turn off all instance listeners. + vm.$off() + // remove __vue__ reference + if (vm.$el) { + vm.$el.__vue__ = null + } + // release circular reference (#6759) + if (vm.$vnode) { + vm.$vnode.parent = null + } + } +} + +export function mountComponent ( + vm: Component, + el: ?Element, + hydrating?: boolean +): Component { + vm.$el = el + if (!vm.$options.render) { + vm.$options.render = createEmptyVNode + if (process.env.NODE_ENV !== 'production') { + /* istanbul ignore if */ + if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') || + vm.$options.el || el) { + warn( + 'You are using the runtime-only build of Vue where the template ' + + 'compiler is not available. Either pre-compile the templates into ' + + 'render functions, or use the compiler-included build.', + vm + ) + } else { + warn( + 'Failed to mount component: template or render function not defined.', + vm + ) + } + } + } + callHook(vm, 'beforeMount') + + let updateComponent + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production' && config.performance && mark) { + updateComponent = () => { + const name = vm._name + const id = vm._uid + const startTag = `vue-perf-start:${id}` + const endTag = `vue-perf-end:${id}` + + mark(startTag) + const vnode = vm._render() + mark(endTag) + measure(`vue ${name} render`, startTag, endTag) + + mark(startTag) + vm._update(vnode, hydrating) + mark(endTag) + measure(`vue ${name} patch`, startTag, endTag) + } + } else { + updateComponent = () => { + vm._update(vm._render(), hydrating) + } + } + + // we set this to vm._watcher inside the watcher's constructor + // since the watcher's initial patch may call $forceUpdate (e.g. inside child + // component's mounted hook), which relies on vm._watcher being already defined + new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */) + hydrating = false + + // manually mounted instance, call mounted on self + // mounted is called for render-created child components in its inserted hook + if (vm.$vnode == null) { + vm._isMounted = true + callHook(vm, 'mounted') + } + return vm +} + +export function updateChildComponent ( + vm: Component, + propsData: ?Object, + listeners: ?Object, + parentVnode: MountedComponentVNode, + renderChildren: ?Array<VNode> +) { + if (process.env.NODE_ENV !== 'production') { + isUpdatingChildComponent = true + } + + // determine whether component has slot children + // we need to do this before overwriting $options._renderChildren + const hasChildren = !!( + renderChildren || // has new static slots + vm.$options._renderChildren || // has old static slots + parentVnode.data.scopedSlots || // has new scoped slots + vm.$scopedSlots !== emptyObject // has old scoped slots + ) + + vm.$options._parentVnode = parentVnode + vm.$vnode = parentVnode // update vm's placeholder node without re-render + + if (vm._vnode) { // update child tree's parent + vm._vnode.parent = parentVnode + } + vm.$options._renderChildren = renderChildren + + // update $attrs and $listeners hash + // these are also reactive so they may trigger child update if the child + // used them during render + vm.$attrs = parentVnode.data.attrs || emptyObject + vm.$listeners = listeners || emptyObject + + // update props + if (propsData && vm.$options.props) { + toggleObserving(false) + const props = vm._props + const propKeys = vm.$options._propKeys || [] + for (let i = 0; i < propKeys.length; i++) { + const key = propKeys[i] + const propOptions: any = vm.$options.props // wtf flow? + props[key] = validateProp(key, propOptions, propsData, vm) + } + toggleObserving(true) + // keep a copy of raw propsData + vm.$options.propsData = propsData + } + + // update listeners + listeners = listeners || emptyObject + const oldListeners = vm.$options._parentListeners + vm.$options._parentListeners = listeners + updateComponentListeners(vm, listeners, oldListeners) + + // resolve slots + force update if has children + if (hasChildren) { + vm.$slots = resolveSlots(renderChildren, parentVnode.context) + vm.$forceUpdate() + } + + if (process.env.NODE_ENV !== 'production') { + isUpdatingChildComponent = false + } +} + +function isInInactiveTree (vm) { + while (vm && (vm = vm.$parent)) { + if (vm._inactive) return true + } + return false +} + +export function activateChildComponent (vm: Component, direct?: boolean) { + if (direct) { + vm._directInactive = false + if (isInInactiveTree(vm)) { + return + } + } else if (vm._directInactive) { + return + } + if (vm._inactive || vm._inactive === null) { + vm._inactive = false + for (let i = 0; i < vm.$children.length; i++) { + activateChildComponent(vm.$children[i]) + } + callHook(vm, 'activated') + } +} + +export function deactivateChildComponent (vm: Component, direct?: boolean) { + if (direct) { + vm._directInactive = true + if (isInInactiveTree(vm)) { + return + } + } + if (!vm._inactive) { + vm._inactive = true + for (let i = 0; i < vm.$children.length; i++) { + deactivateChildComponent(vm.$children[i]) + } + callHook(vm, 'deactivated') + } +} + +export function callHook (vm: Component, hook: string) { + // #7573 disable dep collection when invoking lifecycle hooks + pushTarget() + const handlers = vm.$options[hook] + if (handlers) { + for (let i = 0, j = handlers.length; i < j; i++) { + try { + handlers[i].call(vm) + } catch (e) { + handleError(e, vm, `${hook} hook`) + } + } + } + if (vm._hasHookEvent) { + vm.$emit('hook:' + hook) + } + popTarget() +} diff --git a/node_modules/vue/src/core/instance/proxy.js b/node_modules/vue/src/core/instance/proxy.js new file mode 100644 index 00000000..0d6dcf80 --- /dev/null +++ b/node_modules/vue/src/core/instance/proxy.js @@ -0,0 +1,79 @@ +/* not type checking this file because flow doesn't play well with Proxy */ + +import config from 'core/config' +import { warn, makeMap, isNative } from '../util/index' + +let initProxy + +if (process.env.NODE_ENV !== 'production') { + const allowedGlobals = makeMap( + 'Infinity,undefined,NaN,isFinite,isNaN,' + + 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + + 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' + + 'require' // for Webpack/Browserify + ) + + const warnNonPresent = (target, key) => { + warn( + `Property or method "${key}" is not defined on the instance but ` + + 'referenced during render. Make sure that this property is reactive, ' + + 'either in the data option, or for class-based components, by ' + + 'initializing the property. ' + + 'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.', + target + ) + } + + const hasProxy = + typeof Proxy !== 'undefined' && isNative(Proxy) + + if (hasProxy) { + const isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact') + config.keyCodes = new Proxy(config.keyCodes, { + set (target, key, value) { + if (isBuiltInModifier(key)) { + warn(`Avoid overwriting built-in modifier in config.keyCodes: .${key}`) + return false + } else { + target[key] = value + return true + } + } + }) + } + + const hasHandler = { + has (target, key) { + const has = key in target + const isAllowed = allowedGlobals(key) || key.charAt(0) === '_' + if (!has && !isAllowed) { + warnNonPresent(target, key) + } + return has || !isAllowed + } + } + + const getHandler = { + get (target, key) { + if (typeof key === 'string' && !(key in target)) { + warnNonPresent(target, key) + } + return target[key] + } + } + + initProxy = function initProxy (vm) { + if (hasProxy) { + // determine which proxy handler to use + const options = vm.$options + const handlers = options.render && options.render._withStripped + ? getHandler + : hasHandler + vm._renderProxy = new Proxy(vm, handlers) + } else { + vm._renderProxy = vm + } + } +} + +export { initProxy } diff --git a/node_modules/vue/src/core/instance/render-helpers/bind-object-listeners.js b/node_modules/vue/src/core/instance/render-helpers/bind-object-listeners.js new file mode 100644 index 00000000..5bfc7514 --- /dev/null +++ b/node_modules/vue/src/core/instance/render-helpers/bind-object-listeners.js @@ -0,0 +1,22 @@ +/* @flow */ + +import { warn, extend, isPlainObject } from 'core/util/index' + +export function bindObjectListeners (data: any, value: any): VNodeData { + if (value) { + if (!isPlainObject(value)) { + process.env.NODE_ENV !== 'production' && warn( + 'v-on without argument expects an Object value', + this + ) + } else { + const on = data.on = data.on ? extend({}, data.on) : {} + for (const key in value) { + const existing = on[key] + const ours = value[key] + on[key] = existing ? [].concat(existing, ours) : ours + } + } + } + return data +} diff --git a/node_modules/vue/src/core/instance/render-helpers/bind-object-props.js b/node_modules/vue/src/core/instance/render-helpers/bind-object-props.js new file mode 100644 index 00000000..c71dce5c --- /dev/null +++ b/node_modules/vue/src/core/instance/render-helpers/bind-object-props.js @@ -0,0 +1,60 @@ +/* @flow */ + +import config from 'core/config' + +import { + warn, + isObject, + toObject, + isReservedAttribute +} from 'core/util/index' + +/** + * Runtime helper for merging v-bind="object" into a VNode's data. + */ +export function bindObjectProps ( + data: any, + tag: string, + value: any, + asProp: boolean, + isSync?: boolean +): VNodeData { + if (value) { + if (!isObject(value)) { + process.env.NODE_ENV !== 'production' && warn( + 'v-bind without argument expects an Object or Array value', + this + ) + } else { + if (Array.isArray(value)) { + value = toObject(value) + } + let hash + for (const key in value) { + if ( + key === 'class' || + key === 'style' || + isReservedAttribute(key) + ) { + hash = data + } else { + const type = data.attrs && data.attrs.type + hash = asProp || config.mustUseProp(tag, type, key) + ? data.domProps || (data.domProps = {}) + : data.attrs || (data.attrs = {}) + } + if (!(key in hash)) { + hash[key] = value[key] + + if (isSync) { + const on = data.on || (data.on = {}) + on[`update:${key}`] = function ($event) { + value[key] = $event + } + } + } + } + } + } + return data +} diff --git a/node_modules/vue/src/core/instance/render-helpers/check-keycodes.js b/node_modules/vue/src/core/instance/render-helpers/check-keycodes.js new file mode 100644 index 00000000..c12402b1 --- /dev/null +++ b/node_modules/vue/src/core/instance/render-helpers/check-keycodes.js @@ -0,0 +1,34 @@ +/* @flow */ + +import config from 'core/config' +import { hyphenate } from 'shared/util' + +function isKeyNotMatch<T> (expect: T | Array<T>, actual: T): boolean { + if (Array.isArray(expect)) { + return expect.indexOf(actual) === -1 + } else { + return expect !== actual + } +} + +/** + * Runtime helper for checking keyCodes from config. + * exposed as Vue.prototype._k + * passing in eventKeyName as last argument separately for backwards compat + */ +export function checkKeyCodes ( + eventKeyCode: number, + key: string, + builtInKeyCode?: number | Array<number>, + eventKeyName?: string, + builtInKeyName?: string | Array<string> +): ?boolean { + const mappedKeyCode = config.keyCodes[key] || builtInKeyCode + if (builtInKeyName && eventKeyName && !config.keyCodes[key]) { + return isKeyNotMatch(builtInKeyName, eventKeyName) + } else if (mappedKeyCode) { + return isKeyNotMatch(mappedKeyCode, eventKeyCode) + } else if (eventKeyName) { + return hyphenate(eventKeyName) !== key + } +} diff --git a/node_modules/vue/src/core/instance/render-helpers/index.js b/node_modules/vue/src/core/instance/render-helpers/index.js new file mode 100644 index 00000000..38414a9a --- /dev/null +++ b/node_modules/vue/src/core/instance/render-helpers/index.js @@ -0,0 +1,30 @@ +/* @flow */ + +import { toNumber, toString, looseEqual, looseIndexOf } from 'shared/util' +import { createTextVNode, createEmptyVNode } from 'core/vdom/vnode' +import { renderList } from './render-list' +import { renderSlot } from './render-slot' +import { resolveFilter } from './resolve-filter' +import { checkKeyCodes } from './check-keycodes' +import { bindObjectProps } from './bind-object-props' +import { renderStatic, markOnce } from './render-static' +import { bindObjectListeners } from './bind-object-listeners' +import { resolveScopedSlots } from './resolve-slots' + +export function installRenderHelpers (target: any) { + target._o = markOnce + target._n = toNumber + target._s = toString + target._l = renderList + target._t = renderSlot + target._q = looseEqual + target._i = looseIndexOf + target._m = renderStatic + target._f = resolveFilter + target._k = checkKeyCodes + target._b = bindObjectProps + target._v = createTextVNode + target._e = createEmptyVNode + target._u = resolveScopedSlots + target._g = bindObjectListeners +} diff --git a/node_modules/vue/src/core/instance/render-helpers/render-list.js b/node_modules/vue/src/core/instance/render-helpers/render-list.js new file mode 100644 index 00000000..abe99db4 --- /dev/null +++ b/node_modules/vue/src/core/instance/render-helpers/render-list.js @@ -0,0 +1,39 @@ +/* @flow */ + +import { isObject, isDef } from 'core/util/index' + +/** + * Runtime helper for rendering v-for lists. + */ +export function renderList ( + val: any, + render: ( + val: any, + keyOrIndex: string | number, + index?: number + ) => VNode +): ?Array<VNode> { + let ret: ?Array<VNode>, i, l, keys, key + if (Array.isArray(val) || typeof val === 'string') { + ret = new Array(val.length) + for (i = 0, l = val.length; i < l; i++) { + ret[i] = render(val[i], i) + } + } else if (typeof val === 'number') { + ret = new Array(val) + for (i = 0; i < val; i++) { + ret[i] = render(i + 1, i) + } + } else if (isObject(val)) { + keys = Object.keys(val) + ret = new Array(keys.length) + for (i = 0, l = keys.length; i < l; i++) { + key = keys[i] + ret[i] = render(val[key], key, i) + } + } + if (isDef(ret)) { + (ret: any)._isVList = true + } + return ret +} diff --git a/node_modules/vue/src/core/instance/render-helpers/render-slot.js b/node_modules/vue/src/core/instance/render-helpers/render-slot.js new file mode 100644 index 00000000..a58daa74 --- /dev/null +++ b/node_modules/vue/src/core/instance/render-helpers/render-slot.js @@ -0,0 +1,50 @@ +/* @flow */ + +import { extend, warn, isObject } from 'core/util/index' + +/** + * Runtime helper for rendering <slot> + */ +export function renderSlot ( + name: string, + fallback: ?Array<VNode>, + props: ?Object, + bindObject: ?Object +): ?Array<VNode> { + const scopedSlotFn = this.$scopedSlots[name] + let nodes + if (scopedSlotFn) { // scoped slot + props = props || {} + if (bindObject) { + if (process.env.NODE_ENV !== 'production' && !isObject(bindObject)) { + warn( + 'slot v-bind without argument expects an Object', + this + ) + } + props = extend(extend({}, bindObject), props) + } + nodes = scopedSlotFn(props) || fallback + } else { + const slotNodes = this.$slots[name] + // warn duplicate slot usage + if (slotNodes) { + if (process.env.NODE_ENV !== 'production' && slotNodes._rendered) { + warn( + `Duplicate presence of slot "${name}" found in the same render tree ` + + `- this will likely cause render errors.`, + this + ) + } + slotNodes._rendered = true + } + nodes = slotNodes || fallback + } + + const target = props && props.slot + if (target) { + return this.$createElement('template', { slot: target }, nodes) + } else { + return nodes + } +} diff --git a/node_modules/vue/src/core/instance/render-helpers/render-static.js b/node_modules/vue/src/core/instance/render-helpers/render-static.js new file mode 100644 index 00000000..f56510b3 --- /dev/null +++ b/node_modules/vue/src/core/instance/render-helpers/render-static.js @@ -0,0 +1,60 @@ +/* @flow */ + +/** + * Runtime helper for rendering static trees. + */ +export function renderStatic ( + index: number, + isInFor: boolean +): VNode | Array<VNode> { + const cached = this._staticTrees || (this._staticTrees = []) + let tree = cached[index] + // if has already-rendered static tree and not inside v-for, + // we can reuse the same tree. + if (tree && !isInFor) { + return tree + } + // otherwise, render a fresh tree. + tree = cached[index] = this.$options.staticRenderFns[index].call( + this._renderProxy, + null, + this // for render fns generated for functional component templates + ) + markStatic(tree, `__static__${index}`, false) + return tree +} + +/** + * Runtime helper for v-once. + * Effectively it means marking the node as static with a unique key. + */ +export function markOnce ( + tree: VNode | Array<VNode>, + index: number, + key: string +) { + markStatic(tree, `__once__${index}${key ? `_${key}` : ``}`, true) + return tree +} + +function markStatic ( + tree: VNode | Array<VNode>, + key: string, + isOnce: boolean +) { + if (Array.isArray(tree)) { + for (let i = 0; i < tree.length; i++) { + if (tree[i] && typeof tree[i] !== 'string') { + markStaticNode(tree[i], `${key}_${i}`, isOnce) + } + } + } else { + markStaticNode(tree, key, isOnce) + } +} + +function markStaticNode (node, key, isOnce) { + node.isStatic = true + node.key = key + node.isOnce = isOnce +} diff --git a/node_modules/vue/src/core/instance/render-helpers/resolve-filter.js b/node_modules/vue/src/core/instance/render-helpers/resolve-filter.js new file mode 100644 index 00000000..08a81e14 --- /dev/null +++ b/node_modules/vue/src/core/instance/render-helpers/resolve-filter.js @@ -0,0 +1,10 @@ +/* @flow */ + +import { identity, resolveAsset } from 'core/util/index' + +/** + * Runtime helper for resolving filters + */ +export function resolveFilter (id: string): Function { + return resolveAsset(this.$options, 'filters', id, true) || identity +} diff --git a/node_modules/vue/src/core/instance/render-helpers/resolve-slots.js b/node_modules/vue/src/core/instance/render-helpers/resolve-slots.js new file mode 100644 index 00000000..02f007cf --- /dev/null +++ b/node_modules/vue/src/core/instance/render-helpers/resolve-slots.js @@ -0,0 +1,65 @@ +/* @flow */ + +import type VNode from 'core/vdom/vnode' + +/** + * Runtime helper for resolving raw children VNodes into a slot object. + */ +export function resolveSlots ( + children: ?Array<VNode>, + context: ?Component +): { [key: string]: Array<VNode> } { + const slots = {} + if (!children) { + return slots + } + for (let i = 0, l = children.length; i < l; i++) { + const child = children[i] + const data = child.data + // remove slot attribute if the node is resolved as a Vue slot node + if (data && data.attrs && data.attrs.slot) { + delete data.attrs.slot + } + // named slots should only be respected if the vnode was rendered in the + // same context. + if ((child.context === context || child.fnContext === context) && + data && data.slot != null + ) { + const name = data.slot + const slot = (slots[name] || (slots[name] = [])) + if (child.tag === 'template') { + slot.push.apply(slot, child.children || []) + } else { + slot.push(child) + } + } else { + (slots.default || (slots.default = [])).push(child) + } + } + // ignore slots that contains only whitespace + for (const name in slots) { + if (slots[name].every(isWhitespace)) { + delete slots[name] + } + } + return slots +} + +function isWhitespace (node: VNode): boolean { + return (node.isComment && !node.asyncFactory) || node.text === ' ' +} + +export function resolveScopedSlots ( + fns: ScopedSlotsData, // see flow/vnode + res?: Object +): { [key: string]: Function } { + res = res || {} + for (let i = 0; i < fns.length; i++) { + if (Array.isArray(fns[i])) { + resolveScopedSlots(fns[i], res) + } else { + res[fns[i].key] = fns[i].fn + } + } + return res +} diff --git a/node_modules/vue/src/core/instance/render.js b/node_modules/vue/src/core/instance/render.js new file mode 100644 index 00000000..70fb2c0e --- /dev/null +++ b/node_modules/vue/src/core/instance/render.js @@ -0,0 +1,119 @@ +/* @flow */ + +import { + warn, + nextTick, + emptyObject, + handleError, + defineReactive +} from '../util/index' + +import { createElement } from '../vdom/create-element' +import { installRenderHelpers } from './render-helpers/index' +import { resolveSlots } from './render-helpers/resolve-slots' +import VNode, { createEmptyVNode } from '../vdom/vnode' + +import { isUpdatingChildComponent } from './lifecycle' + +export function initRender (vm: Component) { + vm._vnode = null // the root of the child tree + vm._staticTrees = null // v-once cached trees + const options = vm.$options + const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree + const renderContext = parentVnode && parentVnode.context + vm.$slots = resolveSlots(options._renderChildren, renderContext) + vm.$scopedSlots = emptyObject + // bind the createElement fn to this instance + // so that we get proper render context inside it. + // args order: tag, data, children, normalizationType, alwaysNormalize + // internal version is used by render functions compiled from templates + vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) + // normalization is always applied for the public version, used in + // user-written render functions. + vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true) + + // $attrs & $listeners are exposed for easier HOC creation. + // they need to be reactive so that HOCs using them are always updated + const parentData = parentVnode && parentVnode.data + + /* istanbul ignore else */ + if (process.env.NODE_ENV !== 'production') { + defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => { + !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm) + }, true) + defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => { + !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm) + }, true) + } else { + defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true) + defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true) + } +} + +export function renderMixin (Vue: Class<Component>) { + // install runtime convenience helpers + installRenderHelpers(Vue.prototype) + + Vue.prototype.$nextTick = function (fn: Function) { + return nextTick(fn, this) + } + + Vue.prototype._render = function (): VNode { + const vm: Component = this + const { render, _parentVnode } = vm.$options + + // reset _rendered flag on slots for duplicate slot check + if (process.env.NODE_ENV !== 'production') { + for (const key in vm.$slots) { + // $flow-disable-line + vm.$slots[key]._rendered = false + } + } + + if (_parentVnode) { + vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject + } + + // set parent vnode. this allows render functions to have access + // to the data on the placeholder node. + vm.$vnode = _parentVnode + // render self + let vnode + try { + vnode = render.call(vm._renderProxy, vm.$createElement) + } catch (e) { + handleError(e, vm, `render`) + // return error render result, + // or previous vnode to prevent render error causing blank component + /* istanbul ignore else */ + if (process.env.NODE_ENV !== 'production') { + if (vm.$options.renderError) { + try { + vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e) + } catch (e) { + handleError(e, vm, `renderError`) + vnode = vm._vnode + } + } else { + vnode = vm._vnode + } + } else { + vnode = vm._vnode + } + } + // return empty vnode in case the render function errored out + if (!(vnode instanceof VNode)) { + if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) { + warn( + 'Multiple root nodes returned from render function. Render function ' + + 'should return a single root node.', + vm + ) + } + vnode = createEmptyVNode() + } + // set parent + vnode.parent = _parentVnode + return vnode + } +} diff --git a/node_modules/vue/src/core/instance/state.js b/node_modules/vue/src/core/instance/state.js new file mode 100644 index 00000000..b1549b0d --- /dev/null +++ b/node_modules/vue/src/core/instance/state.js @@ -0,0 +1,360 @@ +/* @flow */ + +import config from '../config' +import Watcher from '../observer/watcher' +import Dep, { pushTarget, popTarget } from '../observer/dep' +import { isUpdatingChildComponent } from './lifecycle' + +import { + set, + del, + observe, + defineReactive, + toggleObserving +} from '../observer/index' + +import { + warn, + bind, + noop, + hasOwn, + hyphenate, + isReserved, + handleError, + nativeWatch, + validateProp, + isPlainObject, + isServerRendering, + isReservedAttribute +} from '../util/index' + +const sharedPropertyDefinition = { + enumerable: true, + configurable: true, + get: noop, + set: noop +} + +export function proxy (target: Object, sourceKey: string, key: string) { + sharedPropertyDefinition.get = function proxyGetter () { + return this[sourceKey][key] + } + sharedPropertyDefinition.set = function proxySetter (val) { + this[sourceKey][key] = val + } + Object.defineProperty(target, key, sharedPropertyDefinition) +} + +export function initState (vm: Component) { + vm._watchers = [] + const opts = vm.$options + if (opts.props) initProps(vm, opts.props) + if (opts.methods) initMethods(vm, opts.methods) + if (opts.data) { + initData(vm) + } else { + observe(vm._data = {}, true /* asRootData */) + } + if (opts.computed) initComputed(vm, opts.computed) + if (opts.watch && opts.watch !== nativeWatch) { + initWatch(vm, opts.watch) + } +} + +function initProps (vm: Component, propsOptions: Object) { + const propsData = vm.$options.propsData || {} + const props = vm._props = {} + // cache prop keys so that future props updates can iterate using Array + // instead of dynamic object key enumeration. + const keys = vm.$options._propKeys = [] + const isRoot = !vm.$parent + // root instance props should be converted + if (!isRoot) { + toggleObserving(false) + } + for (const key in propsOptions) { + keys.push(key) + const value = validateProp(key, propsOptions, propsData, vm) + /* istanbul ignore else */ + if (process.env.NODE_ENV !== 'production') { + const hyphenatedKey = hyphenate(key) + if (isReservedAttribute(hyphenatedKey) || + config.isReservedAttr(hyphenatedKey)) { + warn( + `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`, + vm + ) + } + defineReactive(props, key, value, () => { + if (vm.$parent && !isUpdatingChildComponent) { + warn( + `Avoid mutating a prop directly since the value will be ` + + `overwritten whenever the parent component re-renders. ` + + `Instead, use a data or computed property based on the prop's ` + + `value. Prop being mutated: "${key}"`, + vm + ) + } + }) + } else { + defineReactive(props, key, value) + } + // static props are already proxied on the component's prototype + // during Vue.extend(). We only need to proxy props defined at + // instantiation here. + if (!(key in vm)) { + proxy(vm, `_props`, key) + } + } + toggleObserving(true) +} + +function initData (vm: Component) { + let data = vm.$options.data + data = vm._data = typeof data === 'function' + ? getData(data, vm) + : data || {} + if (!isPlainObject(data)) { + data = {} + process.env.NODE_ENV !== 'production' && warn( + 'data functions should return an object:\n' + + 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function', + vm + ) + } + // proxy data on instance + const keys = Object.keys(data) + const props = vm.$options.props + const methods = vm.$options.methods + let i = keys.length + while (i--) { + const key = keys[i] + if (process.env.NODE_ENV !== 'production') { + if (methods && hasOwn(methods, key)) { + warn( + `Method "${key}" has already been defined as a data property.`, + vm + ) + } + } + if (props && hasOwn(props, key)) { + process.env.NODE_ENV !== 'production' && warn( + `The data property "${key}" is already declared as a prop. ` + + `Use prop default value instead.`, + vm + ) + } else if (!isReserved(key)) { + proxy(vm, `_data`, key) + } + } + // observe data + observe(data, true /* asRootData */) +} + +export function getData (data: Function, vm: Component): any { + // #7573 disable dep collection when invoking data getters + pushTarget() + try { + return data.call(vm, vm) + } catch (e) { + handleError(e, vm, `data()`) + return {} + } finally { + popTarget() + } +} + +const computedWatcherOptions = { lazy: true } + +function initComputed (vm: Component, computed: Object) { + // $flow-disable-line + const watchers = vm._computedWatchers = Object.create(null) + // computed properties are just getters during SSR + const isSSR = isServerRendering() + + for (const key in computed) { + const userDef = computed[key] + const getter = typeof userDef === 'function' ? userDef : userDef.get + if (process.env.NODE_ENV !== 'production' && getter == null) { + warn( + `Getter is missing for computed property "${key}".`, + vm + ) + } + + if (!isSSR) { + // create internal watcher for the computed property. + watchers[key] = new Watcher( + vm, + getter || noop, + noop, + computedWatcherOptions + ) + } + + // component-defined computed properties are already defined on the + // component prototype. We only need to define computed properties defined + // at instantiation here. + if (!(key in vm)) { + defineComputed(vm, key, userDef) + } else if (process.env.NODE_ENV !== 'production') { + if (key in vm.$data) { + warn(`The computed property "${key}" is already defined in data.`, vm) + } else if (vm.$options.props && key in vm.$options.props) { + warn(`The computed property "${key}" is already defined as a prop.`, vm) + } + } + } +} + +export function defineComputed ( + target: any, + key: string, + userDef: Object | Function +) { + const shouldCache = !isServerRendering() + if (typeof userDef === 'function') { + sharedPropertyDefinition.get = shouldCache + ? createComputedGetter(key) + : userDef + sharedPropertyDefinition.set = noop + } else { + sharedPropertyDefinition.get = userDef.get + ? shouldCache && userDef.cache !== false + ? createComputedGetter(key) + : userDef.get + : noop + sharedPropertyDefinition.set = userDef.set + ? userDef.set + : noop + } + if (process.env.NODE_ENV !== 'production' && + sharedPropertyDefinition.set === noop) { + sharedPropertyDefinition.set = function () { + warn( + `Computed property "${key}" was assigned to but it has no setter.`, + this + ) + } + } + Object.defineProperty(target, key, sharedPropertyDefinition) +} + +function createComputedGetter (key) { + return function computedGetter () { + const watcher = this._computedWatchers && this._computedWatchers[key] + if (watcher) { + if (watcher.dirty) { + watcher.evaluate() + } + if (Dep.target) { + watcher.depend() + } + return watcher.value + } + } +} + +function initMethods (vm: Component, methods: Object) { + const props = vm.$options.props + for (const key in methods) { + if (process.env.NODE_ENV !== 'production') { + if (methods[key] == null) { + warn( + `Method "${key}" has an undefined value in the component definition. ` + + `Did you reference the function correctly?`, + vm + ) + } + if (props && hasOwn(props, key)) { + warn( + `Method "${key}" has already been defined as a prop.`, + vm + ) + } + if ((key in vm) && isReserved(key)) { + warn( + `Method "${key}" conflicts with an existing Vue instance method. ` + + `Avoid defining component methods that start with _ or $.` + ) + } + } + vm[key] = methods[key] == null ? noop : bind(methods[key], vm) + } +} + +function initWatch (vm: Component, watch: Object) { + for (const key in watch) { + const handler = watch[key] + if (Array.isArray(handler)) { + for (let i = 0; i < handler.length; i++) { + createWatcher(vm, key, handler[i]) + } + } else { + createWatcher(vm, key, handler) + } + } +} + +function createWatcher ( + vm: Component, + expOrFn: string | Function, + handler: any, + options?: Object +) { + if (isPlainObject(handler)) { + options = handler + handler = handler.handler + } + if (typeof handler === 'string') { + handler = vm[handler] + } + return vm.$watch(expOrFn, handler, options) +} + +export function stateMixin (Vue: Class<Component>) { + // flow somehow has problems with directly declared definition object + // when using Object.defineProperty, so we have to procedurally build up + // the object here. + const dataDef = {} + dataDef.get = function () { return this._data } + const propsDef = {} + propsDef.get = function () { return this._props } + if (process.env.NODE_ENV !== 'production') { + dataDef.set = function (newData: Object) { + warn( + 'Avoid replacing instance root $data. ' + + 'Use nested data properties instead.', + this + ) + } + propsDef.set = function () { + warn(`$props is readonly.`, this) + } + } + Object.defineProperty(Vue.prototype, '$data', dataDef) + Object.defineProperty(Vue.prototype, '$props', propsDef) + + Vue.prototype.$set = set + Vue.prototype.$delete = del + + Vue.prototype.$watch = function ( + expOrFn: string | Function, + cb: any, + options?: Object + ): Function { + const vm: Component = this + if (isPlainObject(cb)) { + return createWatcher(vm, expOrFn, cb, options) + } + options = options || {} + options.user = true + const watcher = new Watcher(vm, expOrFn, cb, options) + if (options.immediate) { + cb.call(vm, watcher.value) + } + return function unwatchFn () { + watcher.teardown() + } + } +} diff --git a/node_modules/vue/src/core/observer/array.js b/node_modules/vue/src/core/observer/array.js new file mode 100644 index 00000000..684e37ec --- /dev/null +++ b/node_modules/vue/src/core/observer/array.js @@ -0,0 +1,45 @@ +/* + * not type checking this file because flow doesn't play well with + * dynamically accessing methods on Array prototype + */ + +import { def } from '../util/index' + +const arrayProto = Array.prototype +export const arrayMethods = Object.create(arrayProto) + +const methodsToPatch = [ + 'push', + 'pop', + 'shift', + 'unshift', + 'splice', + 'sort', + 'reverse' +] + +/** + * Intercept mutating methods and emit events + */ +methodsToPatch.forEach(function (method) { + // cache original method + const original = arrayProto[method] + def(arrayMethods, method, function mutator (...args) { + const result = original.apply(this, args) + const ob = this.__ob__ + let inserted + switch (method) { + case 'push': + case 'unshift': + inserted = args + break + case 'splice': + inserted = args.slice(2) + break + } + if (inserted) ob.observeArray(inserted) + // notify change + ob.dep.notify() + return result + }) +}) diff --git a/node_modules/vue/src/core/observer/dep.js b/node_modules/vue/src/core/observer/dep.js new file mode 100644 index 00000000..abf3b275 --- /dev/null +++ b/node_modules/vue/src/core/observer/dep.js @@ -0,0 +1,58 @@ +/* @flow */ + +import type Watcher from './watcher' +import { remove } from '../util/index' + +let uid = 0 + +/** + * A dep is an observable that can have multiple + * directives subscribing to it. + */ +export default class Dep { + static target: ?Watcher; + id: number; + subs: Array<Watcher>; + + constructor () { + this.id = uid++ + this.subs = [] + } + + addSub (sub: Watcher) { + this.subs.push(sub) + } + + removeSub (sub: Watcher) { + remove(this.subs, sub) + } + + depend () { + if (Dep.target) { + Dep.target.addDep(this) + } + } + + notify () { + // stabilize the subscriber list first + const subs = this.subs.slice() + for (let i = 0, l = subs.length; i < l; i++) { + subs[i].update() + } + } +} + +// the current target watcher being evaluated. +// this is globally unique because there could be only one +// watcher being evaluated at any time. +Dep.target = null +const targetStack = [] + +export function pushTarget (_target: ?Watcher) { + if (Dep.target) targetStack.push(Dep.target) + Dep.target = _target +} + +export function popTarget () { + Dep.target = targetStack.pop() +} diff --git a/node_modules/vue/src/core/observer/index.js b/node_modules/vue/src/core/observer/index.js new file mode 100644 index 00000000..4a030ed7 --- /dev/null +++ b/node_modules/vue/src/core/observer/index.js @@ -0,0 +1,273 @@ +/* @flow */ + +import Dep from './dep' +import VNode from '../vdom/vnode' +import { arrayMethods } from './array' +import { + def, + warn, + hasOwn, + hasProto, + isObject, + isPlainObject, + isPrimitive, + isUndef, + isValidArrayIndex, + isServerRendering +} from '../util/index' + +const arrayKeys = Object.getOwnPropertyNames(arrayMethods) + +/** + * In some cases we may want to disable observation inside a component's + * update computation. + */ +export let shouldObserve: boolean = true + +export function toggleObserving (value: boolean) { + shouldObserve = value +} + +/** + * Observer class that is attached to each observed + * object. Once attached, the observer converts the target + * object's property keys into getter/setters that + * collect dependencies and dispatch updates. + */ +export class Observer { + value: any; + dep: Dep; + vmCount: number; // number of vms that has this object as root $data + + constructor (value: any) { + this.value = value + this.dep = new Dep() + this.vmCount = 0 + def(value, '__ob__', this) + if (Array.isArray(value)) { + const augment = hasProto + ? protoAugment + : copyAugment + augment(value, arrayMethods, arrayKeys) + this.observeArray(value) + } else { + this.walk(value) + } + } + + /** + * Walk through each property and convert them into + * getter/setters. This method should only be called when + * value type is Object. + */ + walk (obj: Object) { + const keys = Object.keys(obj) + for (let i = 0; i < keys.length; i++) { + defineReactive(obj, keys[i]) + } + } + + /** + * Observe a list of Array items. + */ + observeArray (items: Array<any>) { + for (let i = 0, l = items.length; i < l; i++) { + observe(items[i]) + } + } +} + +// helpers + +/** + * Augment an target Object or Array by intercepting + * the prototype chain using __proto__ + */ +function protoAugment (target, src: Object, keys: any) { + /* eslint-disable no-proto */ + target.__proto__ = src + /* eslint-enable no-proto */ +} + +/** + * Augment an target Object or Array by defining + * hidden properties. + */ +/* istanbul ignore next */ +function copyAugment (target: Object, src: Object, keys: Array<string>) { + for (let i = 0, l = keys.length; i < l; i++) { + const key = keys[i] + def(target, key, src[key]) + } +} + +/** + * Attempt to create an observer instance for a value, + * returns the new observer if successfully observed, + * or the existing observer if the value already has one. + */ +export function observe (value: any, asRootData: ?boolean): Observer | void { + if (!isObject(value) || value instanceof VNode) { + return + } + let ob: Observer | void + if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { + ob = value.__ob__ + } else if ( + shouldObserve && + !isServerRendering() && + (Array.isArray(value) || isPlainObject(value)) && + Object.isExtensible(value) && + !value._isVue + ) { + ob = new Observer(value) + } + if (asRootData && ob) { + ob.vmCount++ + } + return ob +} + +/** + * Define a reactive property on an Object. + */ +export function defineReactive ( + obj: Object, + key: string, + val: any, + customSetter?: ?Function, + shallow?: boolean +) { + const dep = new Dep() + + const property = Object.getOwnPropertyDescriptor(obj, key) + if (property && property.configurable === false) { + return + } + + // cater for pre-defined getter/setters + const getter = property && property.get + if (!getter && arguments.length === 2) { + val = obj[key] + } + const setter = property && property.set + + let childOb = !shallow && observe(val) + Object.defineProperty(obj, key, { + enumerable: true, + configurable: true, + get: function reactiveGetter () { + const value = getter ? getter.call(obj) : val + if (Dep.target) { + dep.depend() + if (childOb) { + childOb.dep.depend() + if (Array.isArray(value)) { + dependArray(value) + } + } + } + return value + }, + set: function reactiveSetter (newVal) { + const value = getter ? getter.call(obj) : val + /* eslint-disable no-self-compare */ + if (newVal === value || (newVal !== newVal && value !== value)) { + return + } + /* eslint-enable no-self-compare */ + if (process.env.NODE_ENV !== 'production' && customSetter) { + customSetter() + } + if (setter) { + setter.call(obj, newVal) + } else { + val = newVal + } + childOb = !shallow && observe(newVal) + dep.notify() + } + }) +} + +/** + * Set a property on an object. Adds the new property and + * triggers change notification if the property doesn't + * already exist. + */ +export function set (target: Array<any> | Object, key: any, val: any): any { + if (process.env.NODE_ENV !== 'production' && + (isUndef(target) || isPrimitive(target)) + ) { + warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`) + } + if (Array.isArray(target) && isValidArrayIndex(key)) { + target.length = Math.max(target.length, key) + target.splice(key, 1, val) + return val + } + if (key in target && !(key in Object.prototype)) { + target[key] = val + return val + } + const ob = (target: any).__ob__ + if (target._isVue || (ob && ob.vmCount)) { + process.env.NODE_ENV !== 'production' && warn( + 'Avoid adding reactive properties to a Vue instance or its root $data ' + + 'at runtime - declare it upfront in the data option.' + ) + return val + } + if (!ob) { + target[key] = val + return val + } + defineReactive(ob.value, key, val) + ob.dep.notify() + return val +} + +/** + * Delete a property and trigger change if necessary. + */ +export function del (target: Array<any> | Object, key: any) { + if (process.env.NODE_ENV !== 'production' && + (isUndef(target) || isPrimitive(target)) + ) { + warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`) + } + if (Array.isArray(target) && isValidArrayIndex(key)) { + target.splice(key, 1) + return + } + const ob = (target: any).__ob__ + if (target._isVue || (ob && ob.vmCount)) { + process.env.NODE_ENV !== 'production' && warn( + 'Avoid deleting properties on a Vue instance or its root $data ' + + '- just set it to null.' + ) + return + } + if (!hasOwn(target, key)) { + return + } + delete target[key] + if (!ob) { + return + } + ob.dep.notify() +} + +/** + * Collect dependencies on array elements when the array is touched, since + * we cannot intercept array element access like property getters. + */ +function dependArray (value: Array<any>) { + for (let e, i = 0, l = value.length; i < l; i++) { + e = value[i] + e && e.__ob__ && e.__ob__.dep.depend() + if (Array.isArray(e)) { + dependArray(e) + } + } +} diff --git a/node_modules/vue/src/core/observer/scheduler.js b/node_modules/vue/src/core/observer/scheduler.js new file mode 100644 index 00000000..fce86e5f --- /dev/null +++ b/node_modules/vue/src/core/observer/scheduler.js @@ -0,0 +1,148 @@ +/* @flow */ + +import type Watcher from './watcher' +import config from '../config' +import { callHook, activateChildComponent } from '../instance/lifecycle' + +import { + warn, + nextTick, + devtools +} from '../util/index' + +export const MAX_UPDATE_COUNT = 100 + +const queue: Array<Watcher> = [] +const activatedChildren: Array<Component> = [] +let has: { [key: number]: ?true } = {} +let circular: { [key: number]: number } = {} +let waiting = false +let flushing = false +let index = 0 + +/** + * Reset the scheduler's state. + */ +function resetSchedulerState () { + index = queue.length = activatedChildren.length = 0 + has = {} + if (process.env.NODE_ENV !== 'production') { + circular = {} + } + waiting = flushing = false +} + +/** + * Flush both queues and run the watchers. + */ +function flushSchedulerQueue () { + flushing = true + let watcher, id + + // Sort queue before flush. + // This ensures that: + // 1. Components are updated from parent to child. (because parent is always + // created before the child) + // 2. A component's user watchers are run before its render watcher (because + // user watchers are created before the render watcher) + // 3. If a component is destroyed during a parent component's watcher run, + // its watchers can be skipped. + queue.sort((a, b) => a.id - b.id) + + // do not cache length because more watchers might be pushed + // as we run existing watchers + for (index = 0; index < queue.length; index++) { + watcher = queue[index] + id = watcher.id + has[id] = null + watcher.run() + // in dev build, check and stop circular updates. + if (process.env.NODE_ENV !== 'production' && has[id] != null) { + circular[id] = (circular[id] || 0) + 1 + if (circular[id] > MAX_UPDATE_COUNT) { + warn( + 'You may have an infinite update loop ' + ( + watcher.user + ? `in watcher with expression "${watcher.expression}"` + : `in a component render function.` + ), + watcher.vm + ) + break + } + } + } + + // keep copies of post queues before resetting state + const activatedQueue = activatedChildren.slice() + const updatedQueue = queue.slice() + + resetSchedulerState() + + // call component updated and activated hooks + callActivatedHooks(activatedQueue) + callUpdatedHooks(updatedQueue) + + // devtool hook + /* istanbul ignore if */ + if (devtools && config.devtools) { + devtools.emit('flush') + } +} + +function callUpdatedHooks (queue) { + let i = queue.length + while (i--) { + const watcher = queue[i] + const vm = watcher.vm + if (vm._watcher === watcher && vm._isMounted) { + callHook(vm, 'updated') + } + } +} + +/** + * Queue a kept-alive component that was activated during patch. + * The queue will be processed after the entire tree has been patched. + */ +export function queueActivatedComponent (vm: Component) { + // setting _inactive to false here so that a render function can + // rely on checking whether it's in an inactive tree (e.g. router-view) + vm._inactive = false + activatedChildren.push(vm) +} + +function callActivatedHooks (queue) { + for (let i = 0; i < queue.length; i++) { + queue[i]._inactive = true + activateChildComponent(queue[i], true /* true */) + } +} + +/** + * Push a watcher into the watcher queue. + * Jobs with duplicate IDs will be skipped unless it's + * pushed when the queue is being flushed. + */ +export function queueWatcher (watcher: Watcher) { + const id = watcher.id + if (has[id] == null) { + has[id] = true + if (!flushing) { + queue.push(watcher) + } else { + // if already flushing, splice the watcher based on its id + // if already past its id, it will be run next immediately. + let i = queue.length - 1 + while (i > index && queue[i].id > watcher.id) { + i-- + } + queue.splice(i + 1, 0, watcher) + } + // queue the flush + if (!waiting) { + waiting = true + nextTick(flushSchedulerQueue) + } + } +} diff --git a/node_modules/vue/src/core/observer/traverse.js b/node_modules/vue/src/core/observer/traverse.js new file mode 100644 index 00000000..6b26524a --- /dev/null +++ b/node_modules/vue/src/core/observer/traverse.js @@ -0,0 +1,40 @@ +/* @flow */ + +import { _Set as Set, isObject } from '../util/index' +import type { SimpleSet } from '../util/index' +import VNode from '../vdom/vnode' + +const seenObjects = new Set() + +/** + * Recursively traverse an object to evoke all converted + * getters, so that every nested property inside the object + * is collected as a "deep" dependency. + */ +export function traverse (val: any) { + _traverse(val, seenObjects) + seenObjects.clear() +} + +function _traverse (val: any, seen: SimpleSet) { + let i, keys + const isA = Array.isArray(val) + if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) { + return + } + if (val.__ob__) { + const depId = val.__ob__.dep.id + if (seen.has(depId)) { + return + } + seen.add(depId) + } + if (isA) { + i = val.length + while (i--) _traverse(val[i], seen) + } else { + keys = Object.keys(val) + i = keys.length + while (i--) _traverse(val[keys[i]], seen) + } +} diff --git a/node_modules/vue/src/core/observer/watcher.js b/node_modules/vue/src/core/observer/watcher.js new file mode 100644 index 00000000..48a2e612 --- /dev/null +++ b/node_modules/vue/src/core/observer/watcher.js @@ -0,0 +1,240 @@ +/* @flow */ + +import { + warn, + remove, + isObject, + parsePath, + _Set as Set, + handleError +} from '../util/index' + +import { traverse } from './traverse' +import { queueWatcher } from './scheduler' +import Dep, { pushTarget, popTarget } from './dep' + +import type { SimpleSet } from '../util/index' + +let uid = 0 + +/** + * A watcher parses an expression, collects dependencies, + * and fires callback when the expression value changes. + * This is used for both the $watch() api and directives. + */ +export default class Watcher { + vm: Component; + expression: string; + cb: Function; + id: number; + deep: boolean; + user: boolean; + lazy: boolean; + sync: boolean; + dirty: boolean; + active: boolean; + deps: Array<Dep>; + newDeps: Array<Dep>; + depIds: SimpleSet; + newDepIds: SimpleSet; + getter: Function; + value: any; + + constructor ( + vm: Component, + expOrFn: string | Function, + cb: Function, + options?: ?Object, + isRenderWatcher?: boolean + ) { + this.vm = vm + if (isRenderWatcher) { + vm._watcher = this + } + vm._watchers.push(this) + // options + if (options) { + this.deep = !!options.deep + this.user = !!options.user + this.lazy = !!options.lazy + this.sync = !!options.sync + } else { + this.deep = this.user = this.lazy = this.sync = false + } + this.cb = cb + this.id = ++uid // uid for batching + this.active = true + this.dirty = this.lazy // for lazy watchers + this.deps = [] + this.newDeps = [] + this.depIds = new Set() + this.newDepIds = new Set() + this.expression = process.env.NODE_ENV !== 'production' + ? expOrFn.toString() + : '' + // parse expression for getter + if (typeof expOrFn === 'function') { + this.getter = expOrFn + } else { + this.getter = parsePath(expOrFn) + if (!this.getter) { + this.getter = function () {} + process.env.NODE_ENV !== 'production' && warn( + `Failed watching path: "${expOrFn}" ` + + 'Watcher only accepts simple dot-delimited paths. ' + + 'For full control, use a function instead.', + vm + ) + } + } + this.value = this.lazy + ? undefined + : this.get() + } + + /** + * Evaluate the getter, and re-collect dependencies. + */ + get () { + pushTarget(this) + let value + const vm = this.vm + try { + value = this.getter.call(vm, vm) + } catch (e) { + if (this.user) { + handleError(e, vm, `getter for watcher "${this.expression}"`) + } else { + throw e + } + } finally { + // "touch" every property so they are all tracked as + // dependencies for deep watching + if (this.deep) { + traverse(value) + } + popTarget() + this.cleanupDeps() + } + return value + } + + /** + * Add a dependency to this directive. + */ + addDep (dep: Dep) { + const id = dep.id + if (!this.newDepIds.has(id)) { + this.newDepIds.add(id) + this.newDeps.push(dep) + if (!this.depIds.has(id)) { + dep.addSub(this) + } + } + } + + /** + * Clean up for dependency collection. + */ + cleanupDeps () { + let i = this.deps.length + while (i--) { + const dep = this.deps[i] + if (!this.newDepIds.has(dep.id)) { + dep.removeSub(this) + } + } + let tmp = this.depIds + this.depIds = this.newDepIds + this.newDepIds = tmp + this.newDepIds.clear() + tmp = this.deps + this.deps = this.newDeps + this.newDeps = tmp + this.newDeps.length = 0 + } + + /** + * Subscriber interface. + * Will be called when a dependency changes. + */ + update () { + /* istanbul ignore else */ + if (this.lazy) { + this.dirty = true + } else if (this.sync) { + this.run() + } else { + queueWatcher(this) + } + } + + /** + * Scheduler job interface. + * Will be called by the scheduler. + */ + run () { + if (this.active) { + const value = this.get() + if ( + value !== this.value || + // Deep watchers and watchers on Object/Arrays should fire even + // when the value is the same, because the value may + // have mutated. + isObject(value) || + this.deep + ) { + // set new value + const oldValue = this.value + this.value = value + if (this.user) { + try { + this.cb.call(this.vm, value, oldValue) + } catch (e) { + handleError(e, this.vm, `callback for watcher "${this.expression}"`) + } + } else { + this.cb.call(this.vm, value, oldValue) + } + } + } + } + + /** + * Evaluate the value of the watcher. + * This only gets called for lazy watchers. + */ + evaluate () { + this.value = this.get() + this.dirty = false + } + + /** + * Depend on all deps collected by this watcher. + */ + depend () { + let i = this.deps.length + while (i--) { + this.deps[i].depend() + } + } + + /** + * Remove self from all dependencies' subscriber list. + */ + teardown () { + if (this.active) { + // remove self from vm's watcher list + // this is a somewhat expensive operation so we skip it + // if the vm is being destroyed. + if (!this.vm._isBeingDestroyed) { + remove(this.vm._watchers, this) + } + let i = this.deps.length + while (i--) { + this.deps[i].removeSub(this) + } + this.active = false + } + } +} diff --git a/node_modules/vue/src/core/util/debug.js b/node_modules/vue/src/core/util/debug.js new file mode 100644 index 00000000..6b7315f4 --- /dev/null +++ b/node_modules/vue/src/core/util/debug.js @@ -0,0 +1,100 @@ +/* @flow */ + +import config from '../config' +import { noop } from 'shared/util' + +export let warn = noop +export let tip = noop +export let generateComponentTrace = (noop: any) // work around flow check +export let formatComponentName = (noop: any) + +if (process.env.NODE_ENV !== 'production') { + const hasConsole = typeof console !== 'undefined' + const classifyRE = /(?:^|[-_])(\w)/g + const classify = str => str + .replace(classifyRE, c => c.toUpperCase()) + .replace(/[-_]/g, '') + + warn = (msg, vm) => { + const trace = vm ? generateComponentTrace(vm) : '' + + if (config.warnHandler) { + config.warnHandler.call(null, msg, vm, trace) + } else if (hasConsole && (!config.silent)) { + console.error(`[Vue warn]: ${msg}${trace}`) + } + } + + tip = (msg, vm) => { + if (hasConsole && (!config.silent)) { + console.warn(`[Vue tip]: ${msg}` + ( + vm ? generateComponentTrace(vm) : '' + )) + } + } + + formatComponentName = (vm, includeFile) => { + if (vm.$root === vm) { + return '<Root>' + } + const options = typeof vm === 'function' && vm.cid != null + ? vm.options + : vm._isVue + ? vm.$options || vm.constructor.options + : vm || {} + let name = options.name || options._componentTag + const file = options.__file + if (!name && file) { + const match = file.match(/([^/\\]+)\.vue$/) + name = match && match[1] + } + + return ( + (name ? `<${classify(name)}>` : `<Anonymous>`) + + (file && includeFile !== false ? ` at ${file}` : '') + ) + } + + const repeat = (str, n) => { + let res = '' + while (n) { + if (n % 2 === 1) res += str + if (n > 1) str += str + n >>= 1 + } + return res + } + + generateComponentTrace = vm => { + if (vm._isVue && vm.$parent) { + const tree = [] + let currentRecursiveSequence = 0 + while (vm) { + if (tree.length > 0) { + const last = tree[tree.length - 1] + if (last.constructor === vm.constructor) { + currentRecursiveSequence++ + vm = vm.$parent + continue + } else if (currentRecursiveSequence > 0) { + tree[tree.length - 1] = [last, currentRecursiveSequence] + currentRecursiveSequence = 0 + } + } + tree.push(vm) + vm = vm.$parent + } + return '\n\nfound in\n\n' + tree + .map((vm, i) => `${ + i === 0 ? '---> ' : repeat(' ', 5 + i * 2) + }${ + Array.isArray(vm) + ? `${formatComponentName(vm[0])}... (${vm[1]} recursive calls)` + : formatComponentName(vm) + }`) + .join('\n') + } else { + return `\n\n(found in ${formatComponentName(vm)})` + } + } +} diff --git a/node_modules/vue/src/core/util/env.js b/node_modules/vue/src/core/util/env.js new file mode 100644 index 00000000..07cadd9c --- /dev/null +++ b/node_modules/vue/src/core/util/env.js @@ -0,0 +1,95 @@ +/* @flow */ + +// can we use __proto__? +export const hasProto = '__proto__' in {} + +// Browser environment sniffing +export const inBrowser = typeof window !== 'undefined' +export const inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform +export const weexPlatform = inWeex && WXEnvironment.platform.toLowerCase() +export const UA = inBrowser && window.navigator.userAgent.toLowerCase() +export const isIE = UA && /msie|trident/.test(UA) +export const isIE9 = UA && UA.indexOf('msie 9.0') > 0 +export const isEdge = UA && UA.indexOf('edge/') > 0 +export const isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android') +export const isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios') +export const isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge + +// Firefox has a "watch" function on Object.prototype... +export const nativeWatch = ({}).watch + +export let supportsPassive = false +if (inBrowser) { + try { + const opts = {} + Object.defineProperty(opts, 'passive', ({ + get () { + /* istanbul ignore next */ + supportsPassive = true + } + }: Object)) // https://github.com/facebook/flow/issues/285 + window.addEventListener('test-passive', null, opts) + } catch (e) {} +} + +// this needs to be lazy-evaled because vue may be required before +// vue-server-renderer can set VUE_ENV +let _isServer +export const isServerRendering = () => { + if (_isServer === undefined) { + /* istanbul ignore if */ + if (!inBrowser && !inWeex && typeof global !== 'undefined') { + // detect presence of vue-server-renderer and avoid + // Webpack shimming the process + _isServer = global['process'].env.VUE_ENV === 'server' + } else { + _isServer = false + } + } + return _isServer +} + +// detect devtools +export const devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__ + +/* istanbul ignore next */ +export function isNative (Ctor: any): boolean { + return typeof Ctor === 'function' && /native code/.test(Ctor.toString()) +} + +export const hasSymbol = + typeof Symbol !== 'undefined' && isNative(Symbol) && + typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys) + +let _Set +/* istanbul ignore if */ // $flow-disable-line +if (typeof Set !== 'undefined' && isNative(Set)) { + // use native Set when available. + _Set = Set +} else { + // a non-standard Set polyfill that only works with primitive keys. + _Set = class Set implements SimpleSet { + set: Object; + constructor () { + this.set = Object.create(null) + } + has (key: string | number) { + return this.set[key] === true + } + add (key: string | number) { + this.set[key] = true + } + clear () { + this.set = Object.create(null) + } + } +} + +interface SimpleSet { + has(key: string | number): boolean; + add(key: string | number): mixed; + clear(): void; +} + +export { _Set } +export type { SimpleSet } diff --git a/node_modules/vue/src/core/util/error.js b/node_modules/vue/src/core/util/error.js new file mode 100644 index 00000000..b7d05a6d --- /dev/null +++ b/node_modules/vue/src/core/util/error.js @@ -0,0 +1,48 @@ +/* @flow */ + +import config from '../config' +import { warn } from './debug' +import { inBrowser, inWeex } from './env' + +export function handleError (err: Error, vm: any, info: string) { + if (vm) { + let cur = vm + while ((cur = cur.$parent)) { + const hooks = cur.$options.errorCaptured + if (hooks) { + for (let i = 0; i < hooks.length; i++) { + try { + const capture = hooks[i].call(cur, err, vm, info) === false + if (capture) return + } catch (e) { + globalHandleError(e, cur, 'errorCaptured hook') + } + } + } + } + } + globalHandleError(err, vm, info) +} + +function globalHandleError (err, vm, info) { + if (config.errorHandler) { + try { + return config.errorHandler.call(null, err, vm, info) + } catch (e) { + logError(e, null, 'config.errorHandler') + } + } + logError(err, vm, info) +} + +function logError (err, vm, info) { + if (process.env.NODE_ENV !== 'production') { + warn(`Error in ${info}: "${err.toString()}"`, vm) + } + /* istanbul ignore else */ + if ((inBrowser || inWeex) && typeof console !== 'undefined') { + console.error(err) + } else { + throw err + } +} diff --git a/node_modules/vue/src/core/util/index.js b/node_modules/vue/src/core/util/index.js new file mode 100644 index 00000000..115d77cd --- /dev/null +++ b/node_modules/vue/src/core/util/index.js @@ -0,0 +1,11 @@ +/* @flow */ + +export * from 'shared/util' +export * from './lang' +export * from './env' +export * from './options' +export * from './debug' +export * from './props' +export * from './error' +export * from './next-tick' +export { defineReactive } from '../observer/index' diff --git a/node_modules/vue/src/core/util/lang.js b/node_modules/vue/src/core/util/lang.js new file mode 100644 index 00000000..96b8219a --- /dev/null +++ b/node_modules/vue/src/core/util/lang.js @@ -0,0 +1,39 @@ +/* @flow */ + +/** + * Check if a string starts with $ or _ + */ +export function isReserved (str: string): boolean { + const c = (str + '').charCodeAt(0) + return c === 0x24 || c === 0x5F +} + +/** + * Define a property. + */ +export function def (obj: Object, key: string, val: any, enumerable?: boolean) { + Object.defineProperty(obj, key, { + value: val, + enumerable: !!enumerable, + writable: true, + configurable: true + }) +} + +/** + * Parse simple path. + */ +const bailRE = /[^\w.$]/ +export function parsePath (path: string): any { + if (bailRE.test(path)) { + return + } + const segments = path.split('.') + return function (obj) { + for (let i = 0; i < segments.length; i++) { + if (!obj) return + obj = obj[segments[i]] + } + return obj + } +} diff --git a/node_modules/vue/src/core/util/next-tick.js b/node_modules/vue/src/core/util/next-tick.js new file mode 100644 index 00000000..07a9e841 --- /dev/null +++ b/node_modules/vue/src/core/util/next-tick.js @@ -0,0 +1,117 @@ +/* @flow */ +/* globals MessageChannel */ + +import { noop } from 'shared/util' +import { handleError } from './error' +import { isIOS, isNative } from './env' + +const callbacks = [] +let pending = false + +function flushCallbacks () { + pending = false + const copies = callbacks.slice(0) + callbacks.length = 0 + for (let i = 0; i < copies.length; i++) { + copies[i]() + } +} + +// Here we have async deferring wrappers using both microtasks and (macro) tasks. +// In < 2.4 we used microtasks everywhere, but there are some scenarios where +// microtasks have too high a priority and fire in between supposedly +// sequential events (e.g. #4521, #6690) or even between bubbling of the same +// event (#6566). However, using (macro) tasks everywhere also has subtle problems +// when state is changed right before repaint (e.g. #6813, out-in transitions). +// Here we use microtask by default, but expose a way to force (macro) task when +// needed (e.g. in event handlers attached by v-on). +let microTimerFunc +let macroTimerFunc +let useMacroTask = false + +// Determine (macro) task defer implementation. +// Technically setImmediate should be the ideal choice, but it's only available +// in IE. The only polyfill that consistently queues the callback after all DOM +// events triggered in the same loop is by using MessageChannel. +/* istanbul ignore if */ +if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { + macroTimerFunc = () => { + setImmediate(flushCallbacks) + } +} else if (typeof MessageChannel !== 'undefined' && ( + isNative(MessageChannel) || + // PhantomJS + MessageChannel.toString() === '[object MessageChannelConstructor]' +)) { + const channel = new MessageChannel() + const port = channel.port2 + channel.port1.onmessage = flushCallbacks + macroTimerFunc = () => { + port.postMessage(1) + } +} else { + /* istanbul ignore next */ + macroTimerFunc = () => { + setTimeout(flushCallbacks, 0) + } +} + +// Determine microtask defer implementation. +/* istanbul ignore next, $flow-disable-line */ +if (typeof Promise !== 'undefined' && isNative(Promise)) { + const p = Promise.resolve() + microTimerFunc = () => { + p.then(flushCallbacks) + // in problematic UIWebViews, Promise.then doesn't completely break, but + // it can get stuck in a weird state where callbacks are pushed into the + // microtask queue but the queue isn't being flushed, until the browser + // needs to do some other work, e.g. handle a timer. Therefore we can + // "force" the microtask queue to be flushed by adding an empty timer. + if (isIOS) setTimeout(noop) + } +} else { + // fallback to macro + microTimerFunc = macroTimerFunc +} + +/** + * Wrap a function so that if any code inside triggers state change, + * the changes are queued using a (macro) task instead of a microtask. + */ +export function withMacroTask (fn: Function): Function { + return fn._withTask || (fn._withTask = function () { + useMacroTask = true + const res = fn.apply(null, arguments) + useMacroTask = false + return res + }) +} + +export function nextTick (cb?: Function, ctx?: Object) { + let _resolve + callbacks.push(() => { + if (cb) { + try { + cb.call(ctx) + } catch (e) { + handleError(e, ctx, 'nextTick') + } + } else if (_resolve) { + _resolve(ctx) + } + }) + if (!pending) { + pending = true + if (useMacroTask) { + macroTimerFunc() + } else { + microTimerFunc() + } + } + // $flow-disable-line + if (!cb && typeof Promise !== 'undefined') { + return new Promise(resolve => { + _resolve = resolve + }) + } +} diff --git a/node_modules/vue/src/core/util/options.js b/node_modules/vue/src/core/util/options.js new file mode 100644 index 00000000..5f0d10d3 --- /dev/null +++ b/node_modules/vue/src/core/util/options.js @@ -0,0 +1,438 @@ +/* @flow */ + +import config from '../config' +import { warn } from './debug' +import { nativeWatch } from './env' +import { set } from '../observer/index' + +import { + ASSET_TYPES, + LIFECYCLE_HOOKS +} from 'shared/constants' + +import { + extend, + hasOwn, + camelize, + toRawType, + capitalize, + isBuiltInTag, + isPlainObject +} from 'shared/util' + +/** + * Option overwriting strategies are functions that handle + * how to merge a parent option value and a child option + * value into the final value. + */ +const strats = config.optionMergeStrategies + +/** + * Options with restrictions + */ +if (process.env.NODE_ENV !== 'production') { + strats.el = strats.propsData = function (parent, child, vm, key) { + if (!vm) { + warn( + `option "${key}" can only be used during instance ` + + 'creation with the `new` keyword.' + ) + } + return defaultStrat(parent, child) + } +} + +/** + * Helper that recursively merges two data objects together. + */ +function mergeData (to: Object, from: ?Object): Object { + if (!from) return to + let key, toVal, fromVal + const keys = Object.keys(from) + for (let i = 0; i < keys.length; i++) { + key = keys[i] + toVal = to[key] + fromVal = from[key] + if (!hasOwn(to, key)) { + set(to, key, fromVal) + } else if (isPlainObject(toVal) && isPlainObject(fromVal)) { + mergeData(toVal, fromVal) + } + } + return to +} + +/** + * Data + */ +export function mergeDataOrFn ( + parentVal: any, + childVal: any, + vm?: Component +): ?Function { + if (!vm) { + // in a Vue.extend merge, both should be functions + if (!childVal) { + return parentVal + } + if (!parentVal) { + return childVal + } + // when parentVal & childVal are both present, + // we need to return a function that returns the + // merged result of both functions... no need to + // check if parentVal is a function here because + // it has to be a function to pass previous merges. + return function mergedDataFn () { + return mergeData( + typeof childVal === 'function' ? childVal.call(this, this) : childVal, + typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal + ) + } + } else { + return function mergedInstanceDataFn () { + // instance merge + const instanceData = typeof childVal === 'function' + ? childVal.call(vm, vm) + : childVal + const defaultData = typeof parentVal === 'function' + ? parentVal.call(vm, vm) + : parentVal + if (instanceData) { + return mergeData(instanceData, defaultData) + } else { + return defaultData + } + } + } +} + +strats.data = function ( + parentVal: any, + childVal: any, + vm?: Component +): ?Function { + if (!vm) { + if (childVal && typeof childVal !== 'function') { + process.env.NODE_ENV !== 'production' && warn( + 'The "data" option should be a function ' + + 'that returns a per-instance value in component ' + + 'definitions.', + vm + ) + + return parentVal + } + return mergeDataOrFn(parentVal, childVal) + } + + return mergeDataOrFn(parentVal, childVal, vm) +} + +/** + * Hooks and props are merged as arrays. + */ +function mergeHook ( + parentVal: ?Array<Function>, + childVal: ?Function | ?Array<Function> +): ?Array<Function> { + return childVal + ? parentVal + ? parentVal.concat(childVal) + : Array.isArray(childVal) + ? childVal + : [childVal] + : parentVal +} + +LIFECYCLE_HOOKS.forEach(hook => { + strats[hook] = mergeHook +}) + +/** + * Assets + * + * When a vm is present (instance creation), we need to do + * a three-way merge between constructor options, instance + * options and parent options. + */ +function mergeAssets ( + parentVal: ?Object, + childVal: ?Object, + vm?: Component, + key: string +): Object { + const res = Object.create(parentVal || null) + if (childVal) { + process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm) + return extend(res, childVal) + } else { + return res + } +} + +ASSET_TYPES.forEach(function (type) { + strats[type + 's'] = mergeAssets +}) + +/** + * Watchers. + * + * Watchers hashes should not overwrite one + * another, so we merge them as arrays. + */ +strats.watch = function ( + parentVal: ?Object, + childVal: ?Object, + vm?: Component, + key: string +): ?Object { + // work around Firefox's Object.prototype.watch... + if (parentVal === nativeWatch) parentVal = undefined + if (childVal === nativeWatch) childVal = undefined + /* istanbul ignore if */ + if (!childVal) return Object.create(parentVal || null) + if (process.env.NODE_ENV !== 'production') { + assertObjectType(key, childVal, vm) + } + if (!parentVal) return childVal + const ret = {} + extend(ret, parentVal) + for (const key in childVal) { + let parent = ret[key] + const child = childVal[key] + if (parent && !Array.isArray(parent)) { + parent = [parent] + } + ret[key] = parent + ? parent.concat(child) + : Array.isArray(child) ? child : [child] + } + return ret +} + +/** + * Other object hashes. + */ +strats.props = +strats.methods = +strats.inject = +strats.computed = function ( + parentVal: ?Object, + childVal: ?Object, + vm?: Component, + key: string +): ?Object { + if (childVal && process.env.NODE_ENV !== 'production') { + assertObjectType(key, childVal, vm) + } + if (!parentVal) return childVal + const ret = Object.create(null) + extend(ret, parentVal) + if (childVal) extend(ret, childVal) + return ret +} +strats.provide = mergeDataOrFn + +/** + * Default strategy. + */ +const defaultStrat = function (parentVal: any, childVal: any): any { + return childVal === undefined + ? parentVal + : childVal +} + +/** + * Validate component names + */ +function checkComponents (options: Object) { + for (const key in options.components) { + validateComponentName(key) + } +} + +export function validateComponentName (name: string) { + if (!/^[a-zA-Z][\w-]*$/.test(name)) { + warn( + 'Invalid component name: "' + name + '". Component names ' + + 'can only contain alphanumeric characters and the hyphen, ' + + 'and must start with a letter.' + ) + } + if (isBuiltInTag(name) || config.isReservedTag(name)) { + warn( + 'Do not use built-in or reserved HTML elements as component ' + + 'id: ' + name + ) + } +} + +/** + * Ensure all props option syntax are normalized into the + * Object-based format. + */ +function normalizeProps (options: Object, vm: ?Component) { + const props = options.props + if (!props) return + const res = {} + let i, val, name + if (Array.isArray(props)) { + i = props.length + while (i--) { + val = props[i] + if (typeof val === 'string') { + name = camelize(val) + res[name] = { type: null } + } else if (process.env.NODE_ENV !== 'production') { + warn('props must be strings when using array syntax.') + } + } + } else if (isPlainObject(props)) { + for (const key in props) { + val = props[key] + name = camelize(key) + res[name] = isPlainObject(val) + ? val + : { type: val } + } + } else if (process.env.NODE_ENV !== 'production') { + warn( + `Invalid value for option "props": expected an Array or an Object, ` + + `but got ${toRawType(props)}.`, + vm + ) + } + options.props = res +} + +/** + * Normalize all injections into Object-based format + */ +function normalizeInject (options: Object, vm: ?Component) { + const inject = options.inject + if (!inject) return + const normalized = options.inject = {} + if (Array.isArray(inject)) { + for (let i = 0; i < inject.length; i++) { + normalized[inject[i]] = { from: inject[i] } + } + } else if (isPlainObject(inject)) { + for (const key in inject) { + const val = inject[key] + normalized[key] = isPlainObject(val) + ? extend({ from: key }, val) + : { from: val } + } + } else if (process.env.NODE_ENV !== 'production') { + warn( + `Invalid value for option "inject": expected an Array or an Object, ` + + `but got ${toRawType(inject)}.`, + vm + ) + } +} + +/** + * Normalize raw function directives into object format. + */ +function normalizeDirectives (options: Object) { + const dirs = options.directives + if (dirs) { + for (const key in dirs) { + const def = dirs[key] + if (typeof def === 'function') { + dirs[key] = { bind: def, update: def } + } + } + } +} + +function assertObjectType (name: string, value: any, vm: ?Component) { + if (!isPlainObject(value)) { + warn( + `Invalid value for option "${name}": expected an Object, ` + + `but got ${toRawType(value)}.`, + vm + ) + } +} + +/** + * Merge two option objects into a new one. + * Core utility used in both instantiation and inheritance. + */ +export function mergeOptions ( + parent: Object, + child: Object, + vm?: Component +): Object { + if (process.env.NODE_ENV !== 'production') { + checkComponents(child) + } + + if (typeof child === 'function') { + child = child.options + } + + normalizeProps(child, vm) + normalizeInject(child, vm) + normalizeDirectives(child) + const extendsFrom = child.extends + if (extendsFrom) { + parent = mergeOptions(parent, extendsFrom, vm) + } + if (child.mixins) { + for (let i = 0, l = child.mixins.length; i < l; i++) { + parent = mergeOptions(parent, child.mixins[i], vm) + } + } + const options = {} + let key + for (key in parent) { + mergeField(key) + } + for (key in child) { + if (!hasOwn(parent, key)) { + mergeField(key) + } + } + function mergeField (key) { + const strat = strats[key] || defaultStrat + options[key] = strat(parent[key], child[key], vm, key) + } + return options +} + +/** + * Resolve an asset. + * This function is used because child instances need access + * to assets defined in its ancestor chain. + */ +export function resolveAsset ( + options: Object, + type: string, + id: string, + warnMissing?: boolean +): any { + /* istanbul ignore if */ + if (typeof id !== 'string') { + return + } + const assets = options[type] + // check local registration variations first + if (hasOwn(assets, id)) return assets[id] + const camelizedId = camelize(id) + if (hasOwn(assets, camelizedId)) return assets[camelizedId] + const PascalCaseId = capitalize(camelizedId) + if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId] + // fallback to prototype chain + const res = assets[id] || assets[camelizedId] || assets[PascalCaseId] + if (process.env.NODE_ENV !== 'production' && warnMissing && !res) { + warn( + 'Failed to resolve ' + type.slice(0, -1) + ': ' + id, + options + ) + } + return res +} diff --git a/node_modules/vue/src/core/util/perf.js b/node_modules/vue/src/core/util/perf.js new file mode 100644 index 00000000..4a537b2d --- /dev/null +++ b/node_modules/vue/src/core/util/perf.js @@ -0,0 +1,24 @@ +import { inBrowser } from './env' + +export let mark +export let measure + +if (process.env.NODE_ENV !== 'production') { + const perf = inBrowser && window.performance + /* istanbul ignore if */ + if ( + perf && + perf.mark && + perf.measure && + perf.clearMarks && + perf.clearMeasures + ) { + mark = tag => perf.mark(tag) + measure = (name, startTag, endTag) => { + perf.measure(name, startTag, endTag) + perf.clearMarks(startTag) + perf.clearMarks(endTag) + perf.clearMeasures(name) + } + } +} diff --git a/node_modules/vue/src/core/util/props.js b/node_modules/vue/src/core/util/props.js new file mode 100644 index 00000000..a92643f9 --- /dev/null +++ b/node_modules/vue/src/core/util/props.js @@ -0,0 +1,202 @@ +/* @flow */ + +import { warn } from './debug' +import { observe, toggleObserving, shouldObserve } from '../observer/index' +import { + hasOwn, + isObject, + toRawType, + hyphenate, + capitalize, + isPlainObject +} from 'shared/util' + +type PropOptions = { + type: Function | Array<Function> | null, + default: any, + required: ?boolean, + validator: ?Function +}; + +export function validateProp ( + key: string, + propOptions: Object, + propsData: Object, + vm?: Component +): any { + const prop = propOptions[key] + const absent = !hasOwn(propsData, key) + let value = propsData[key] + // boolean casting + const booleanIndex = getTypeIndex(Boolean, prop.type) + if (booleanIndex > -1) { + if (absent && !hasOwn(prop, 'default')) { + value = false + } else if (value === '' || value === hyphenate(key)) { + // only cast empty string / same name to boolean if + // boolean has higher priority + const stringIndex = getTypeIndex(String, prop.type) + if (stringIndex < 0 || booleanIndex < stringIndex) { + value = true + } + } + } + // check default value + if (value === undefined) { + value = getPropDefaultValue(vm, prop, key) + // since the default value is a fresh copy, + // make sure to observe it. + const prevShouldObserve = shouldObserve + toggleObserving(true) + observe(value) + toggleObserving(prevShouldObserve) + } + if ( + process.env.NODE_ENV !== 'production' && + // skip validation for weex recycle-list child component props + !(__WEEX__ && isObject(value) && ('@binding' in value)) + ) { + assertProp(prop, key, value, vm, absent) + } + return value +} + +/** + * Get the default value of a prop. + */ +function getPropDefaultValue (vm: ?Component, prop: PropOptions, key: string): any { + // no default, return undefined + if (!hasOwn(prop, 'default')) { + return undefined + } + const def = prop.default + // warn against non-factory defaults for Object & Array + if (process.env.NODE_ENV !== 'production' && isObject(def)) { + warn( + 'Invalid default value for prop "' + key + '": ' + + 'Props with type Object/Array must use a factory function ' + + 'to return the default value.', + vm + ) + } + // the raw prop value was also undefined from previous render, + // return previous default value to avoid unnecessary watcher trigger + if (vm && vm.$options.propsData && + vm.$options.propsData[key] === undefined && + vm._props[key] !== undefined + ) { + return vm._props[key] + } + // call factory function for non-Function types + // a value is Function if its prototype is function even across different execution context + return typeof def === 'function' && getType(prop.type) !== 'Function' + ? def.call(vm) + : def +} + +/** + * Assert whether a prop is valid. + */ +function assertProp ( + prop: PropOptions, + name: string, + value: any, + vm: ?Component, + absent: boolean +) { + if (prop.required && absent) { + warn( + 'Missing required prop: "' + name + '"', + vm + ) + return + } + if (value == null && !prop.required) { + return + } + let type = prop.type + let valid = !type || type === true + const expectedTypes = [] + if (type) { + if (!Array.isArray(type)) { + type = [type] + } + for (let i = 0; i < type.length && !valid; i++) { + const assertedType = assertType(value, type[i]) + expectedTypes.push(assertedType.expectedType || '') + valid = assertedType.valid + } + } + if (!valid) { + warn( + `Invalid prop: type check failed for prop "${name}".` + + ` Expected ${expectedTypes.map(capitalize).join(', ')}` + + `, got ${toRawType(value)}.`, + vm + ) + return + } + const validator = prop.validator + if (validator) { + if (!validator(value)) { + warn( + 'Invalid prop: custom validator check failed for prop "' + name + '".', + vm + ) + } + } +} + +const simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/ + +function assertType (value: any, type: Function): { + valid: boolean; + expectedType: string; +} { + let valid + const expectedType = getType(type) + if (simpleCheckRE.test(expectedType)) { + const t = typeof value + valid = t === expectedType.toLowerCase() + // for primitive wrapper objects + if (!valid && t === 'object') { + valid = value instanceof type + } + } else if (expectedType === 'Object') { + valid = isPlainObject(value) + } else if (expectedType === 'Array') { + valid = Array.isArray(value) + } else { + valid = value instanceof type + } + return { + valid, + expectedType + } +} + +/** + * Use function string name to check built-in types, + * because a simple equality check will fail when running + * across different vms / iframes. + */ +function getType (fn) { + const match = fn && fn.toString().match(/^\s*function (\w+)/) + return match ? match[1] : '' +} + +function isSameType (a, b) { + return getType(a) === getType(b) +} + +function getTypeIndex (type, expectedTypes): number { + if (!Array.isArray(expectedTypes)) { + return isSameType(expectedTypes, type) ? 0 : -1 + } + for (let i = 0, len = expectedTypes.length; i < len; i++) { + if (isSameType(expectedTypes[i], type)) { + return i + } + } + return -1 +} diff --git a/node_modules/vue/src/core/vdom/create-component.js b/node_modules/vue/src/core/vdom/create-component.js new file mode 100644 index 00000000..89578224 --- /dev/null +++ b/node_modules/vue/src/core/vdom/create-component.js @@ -0,0 +1,257 @@ +/* @flow */ + +import VNode from './vnode' +import { resolveConstructorOptions } from 'core/instance/init' +import { queueActivatedComponent } from 'core/observer/scheduler' +import { createFunctionalComponent } from './create-functional-component' + +import { + warn, + isDef, + isUndef, + isTrue, + isObject +} from '../util/index' + +import { + resolveAsyncComponent, + createAsyncPlaceholder, + extractPropsFromVNodeData +} from './helpers/index' + +import { + callHook, + activeInstance, + updateChildComponent, + activateChildComponent, + deactivateChildComponent +} from '../instance/lifecycle' + +import { + isRecyclableComponent, + renderRecyclableComponentTemplate +} from 'weex/runtime/recycle-list/render-component-template' + +// inline hooks to be invoked on component VNodes during patch +const componentVNodeHooks = { + init ( + vnode: VNodeWithData, + hydrating: boolean, + parentElm: ?Node, + refElm: ?Node + ): ?boolean { + if ( + vnode.componentInstance && + !vnode.componentInstance._isDestroyed && + vnode.data.keepAlive + ) { + // kept-alive components, treat as a patch + const mountedNode: any = vnode // work around flow + componentVNodeHooks.prepatch(mountedNode, mountedNode) + } else { + const child = vnode.componentInstance = createComponentInstanceForVnode( + vnode, + activeInstance, + parentElm, + refElm + ) + child.$mount(hydrating ? vnode.elm : undefined, hydrating) + } + }, + + prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) { + const options = vnode.componentOptions + const child = vnode.componentInstance = oldVnode.componentInstance + updateChildComponent( + child, + options.propsData, // updated props + options.listeners, // updated listeners + vnode, // new parent vnode + options.children // new children + ) + }, + + insert (vnode: MountedComponentVNode) { + const { context, componentInstance } = vnode + if (!componentInstance._isMounted) { + componentInstance._isMounted = true + callHook(componentInstance, 'mounted') + } + if (vnode.data.keepAlive) { + if (context._isMounted) { + // vue-router#1212 + // During updates, a kept-alive component's child components may + // change, so directly walking the tree here may call activated hooks + // on incorrect children. Instead we push them into a queue which will + // be processed after the whole patch process ended. + queueActivatedComponent(componentInstance) + } else { + activateChildComponent(componentInstance, true /* direct */) + } + } + }, + + destroy (vnode: MountedComponentVNode) { + const { componentInstance } = vnode + if (!componentInstance._isDestroyed) { + if (!vnode.data.keepAlive) { + componentInstance.$destroy() + } else { + deactivateChildComponent(componentInstance, true /* direct */) + } + } + } +} + +const hooksToMerge = Object.keys(componentVNodeHooks) + +export function createComponent ( + Ctor: Class<Component> | Function | Object | void, + data: ?VNodeData, + context: Component, + children: ?Array<VNode>, + tag?: string +): VNode | Array<VNode> | void { + if (isUndef(Ctor)) { + return + } + + const baseCtor = context.$options._base + + // plain options object: turn it into a constructor + if (isObject(Ctor)) { + Ctor = baseCtor.extend(Ctor) + } + + // if at this stage it's not a constructor or an async component factory, + // reject. + if (typeof Ctor !== 'function') { + if (process.env.NODE_ENV !== 'production') { + warn(`Invalid Component definition: ${String(Ctor)}`, context) + } + return + } + + // async component + let asyncFactory + if (isUndef(Ctor.cid)) { + asyncFactory = Ctor + Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context) + if (Ctor === undefined) { + // return a placeholder node for async component, which is rendered + // as a comment node but preserves all the raw information for the node. + // the information will be used for async server-rendering and hydration. + return createAsyncPlaceholder( + asyncFactory, + data, + context, + children, + tag + ) + } + } + + data = data || {} + + // resolve constructor options in case global mixins are applied after + // component constructor creation + resolveConstructorOptions(Ctor) + + // transform component v-model data into props & events + if (isDef(data.model)) { + transformModel(Ctor.options, data) + } + + // extract props + const propsData = extractPropsFromVNodeData(data, Ctor, tag) + + // functional component + if (isTrue(Ctor.options.functional)) { + return createFunctionalComponent(Ctor, propsData, data, context, children) + } + + // extract listeners, since these needs to be treated as + // child component listeners instead of DOM listeners + const listeners = data.on + // replace with listeners with .native modifier + // so it gets processed during parent component patch. + data.on = data.nativeOn + + if (isTrue(Ctor.options.abstract)) { + // abstract components do not keep anything + // other than props & listeners & slot + + // work around flow + const slot = data.slot + data = {} + if (slot) { + data.slot = slot + } + } + + // install component management hooks onto the placeholder node + installComponentHooks(data) + + // return a placeholder vnode + const name = Ctor.options.name || tag + const vnode = new VNode( + `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`, + data, undefined, undefined, undefined, context, + { Ctor, propsData, listeners, tag, children }, + asyncFactory + ) + + // Weex specific: invoke recycle-list optimized @render function for + // extracting cell-slot template. + // https://github.com/Hanks10100/weex-native-directive/tree/master/component + /* istanbul ignore if */ + if (__WEEX__ && isRecyclableComponent(vnode)) { + return renderRecyclableComponentTemplate(vnode) + } + + return vnode +} + +export function createComponentInstanceForVnode ( + vnode: any, // we know it's MountedComponentVNode but flow doesn't + parent: any, // activeInstance in lifecycle state + parentElm?: ?Node, + refElm?: ?Node +): Component { + const options: InternalComponentOptions = { + _isComponent: true, + parent, + _parentVnode: vnode, + _parentElm: parentElm || null, + _refElm: refElm || null + } + // check inline-template render functions + const inlineTemplate = vnode.data.inlineTemplate + if (isDef(inlineTemplate)) { + options.render = inlineTemplate.render + options.staticRenderFns = inlineTemplate.staticRenderFns + } + return new vnode.componentOptions.Ctor(options) +} + +function installComponentHooks (data: VNodeData) { + const hooks = data.hook || (data.hook = {}) + for (let i = 0; i < hooksToMerge.length; i++) { + const key = hooksToMerge[i] + hooks[key] = componentVNodeHooks[key] + } +} + +// transform component v-model info (value and callback) into +// prop and event handler respectively. +function transformModel (options, data: any) { + const prop = (options.model && options.model.prop) || 'value' + const event = (options.model && options.model.event) || 'input' + ;(data.props || (data.props = {}))[prop] = data.model.value + const on = data.on || (data.on = {}) + if (isDef(on[event])) { + on[event] = [data.model.callback].concat(on[event]) + } else { + on[event] = data.model.callback + } +} diff --git a/node_modules/vue/src/core/vdom/create-element.js b/node_modules/vue/src/core/vdom/create-element.js new file mode 100644 index 00000000..c5cde2c6 --- /dev/null +++ b/node_modules/vue/src/core/vdom/create-element.js @@ -0,0 +1,160 @@ +/* @flow */ + +import config from '../config' +import VNode, { createEmptyVNode } from './vnode' +import { createComponent } from './create-component' +import { traverse } from '../observer/traverse' + +import { + warn, + isDef, + isUndef, + isTrue, + isObject, + isPrimitive, + resolveAsset +} from '../util/index' + +import { + normalizeChildren, + simpleNormalizeChildren +} from './helpers/index' + +const SIMPLE_NORMALIZE = 1 +const ALWAYS_NORMALIZE = 2 + +// wrapper function for providing a more flexible interface +// without getting yelled at by flow +export function createElement ( + context: Component, + tag: any, + data: any, + children: any, + normalizationType: any, + alwaysNormalize: boolean +): VNode | Array<VNode> { + if (Array.isArray(data) || isPrimitive(data)) { + normalizationType = children + children = data + data = undefined + } + if (isTrue(alwaysNormalize)) { + normalizationType = ALWAYS_NORMALIZE + } + return _createElement(context, tag, data, children, normalizationType) +} + +export function _createElement ( + context: Component, + tag?: string | Class<Component> | Function | Object, + data?: VNodeData, + children?: any, + normalizationType?: number +): VNode | Array<VNode> { + if (isDef(data) && isDef((data: any).__ob__)) { + process.env.NODE_ENV !== 'production' && warn( + `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` + + 'Always create fresh vnode data objects in each render!', + context + ) + return createEmptyVNode() + } + // object syntax in v-bind + if (isDef(data) && isDef(data.is)) { + tag = data.is + } + if (!tag) { + // in case of component :is set to falsy value + return createEmptyVNode() + } + // warn against non-primitive key + if (process.env.NODE_ENV !== 'production' && + isDef(data) && isDef(data.key) && !isPrimitive(data.key) + ) { + if (!__WEEX__ || !('@binding' in data.key)) { + warn( + 'Avoid using non-primitive value as key, ' + + 'use string/number value instead.', + context + ) + } + } + // support single function children as default scoped slot + if (Array.isArray(children) && + typeof children[0] === 'function' + ) { + data = data || {} + data.scopedSlots = { default: children[0] } + children.length = 0 + } + if (normalizationType === ALWAYS_NORMALIZE) { + children = normalizeChildren(children) + } else if (normalizationType === SIMPLE_NORMALIZE) { + children = simpleNormalizeChildren(children) + } + let vnode, ns + if (typeof tag === 'string') { + let Ctor + ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) + if (config.isReservedTag(tag)) { + // platform built-in elements + vnode = new VNode( + config.parsePlatformTagName(tag), data, children, + undefined, undefined, context + ) + } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { + // component + vnode = createComponent(Ctor, data, context, children, tag) + } else { + // unknown or unlisted namespaced elements + // check at runtime because it may get assigned a namespace when its + // parent normalizes children + vnode = new VNode( + tag, data, children, + undefined, undefined, context + ) + } + } else { + // direct component options / constructor + vnode = createComponent(tag, data, context, children) + } + if (Array.isArray(vnode)) { + return vnode + } else if (isDef(vnode)) { + if (isDef(ns)) applyNS(vnode, ns) + if (isDef(data)) registerDeepBindings(data) + return vnode + } else { + return createEmptyVNode() + } +} + +function applyNS (vnode, ns, force) { + vnode.ns = ns + if (vnode.tag === 'foreignObject') { + // use default namespace inside foreignObject + ns = undefined + force = true + } + if (isDef(vnode.children)) { + for (let i = 0, l = vnode.children.length; i < l; i++) { + const child = vnode.children[i] + if (isDef(child.tag) && ( + isUndef(child.ns) || (isTrue(force) && child.tag !== 'svg'))) { + applyNS(child, ns, force) + } + } + } +} + +// ref #5318 +// necessary to ensure parent re-render when deep bindings like :style and +// :class are used on slot nodes +function registerDeepBindings (data) { + if (isObject(data.style)) { + traverse(data.style) + } + if (isObject(data.class)) { + traverse(data.class) + } +} diff --git a/node_modules/vue/src/core/vdom/create-functional-component.js b/node_modules/vue/src/core/vdom/create-functional-component.js new file mode 100644 index 00000000..efed801d --- /dev/null +++ b/node_modules/vue/src/core/vdom/create-functional-component.js @@ -0,0 +1,136 @@ +/* @flow */ + +import VNode, { cloneVNode } from './vnode' +import { createElement } from './create-element' +import { resolveInject } from '../instance/inject' +import { normalizeChildren } from '../vdom/helpers/normalize-children' +import { resolveSlots } from '../instance/render-helpers/resolve-slots' +import { installRenderHelpers } from '../instance/render-helpers/index' + +import { + isDef, + isTrue, + hasOwn, + camelize, + emptyObject, + validateProp +} from '../util/index' + +export function FunctionalRenderContext ( + data: VNodeData, + props: Object, + children: ?Array<VNode>, + parent: Component, + Ctor: Class<Component> +) { + const options = Ctor.options + // ensure the createElement function in functional components + // gets a unique context - this is necessary for correct named slot check + let contextVm + if (hasOwn(parent, '_uid')) { + contextVm = Object.create(parent) + // $flow-disable-line + contextVm._original = parent + } else { + // the context vm passed in is a functional context as well. + // in this case we want to make sure we are able to get a hold to the + // real context instance. + contextVm = parent + // $flow-disable-line + parent = parent._original + } + const isCompiled = isTrue(options._compiled) + const needNormalization = !isCompiled + + this.data = data + this.props = props + this.children = children + this.parent = parent + this.listeners = data.on || emptyObject + this.injections = resolveInject(options.inject, parent) + this.slots = () => resolveSlots(children, parent) + + // support for compiled functional template + if (isCompiled) { + // exposing $options for renderStatic() + this.$options = options + // pre-resolve slots for renderSlot() + this.$slots = this.slots() + this.$scopedSlots = data.scopedSlots || emptyObject + } + + if (options._scopeId) { + this._c = (a, b, c, d) => { + const vnode = createElement(contextVm, a, b, c, d, needNormalization) + if (vnode && !Array.isArray(vnode)) { + vnode.fnScopeId = options._scopeId + vnode.fnContext = parent + } + return vnode + } + } else { + this._c = (a, b, c, d) => createElement(contextVm, a, b, c, d, needNormalization) + } +} + +installRenderHelpers(FunctionalRenderContext.prototype) + +export function createFunctionalComponent ( + Ctor: Class<Component>, + propsData: ?Object, + data: VNodeData, + contextVm: Component, + children: ?Array<VNode> +): VNode | Array<VNode> | void { + const options = Ctor.options + const props = {} + const propOptions = options.props + if (isDef(propOptions)) { + for (const key in propOptions) { + props[key] = validateProp(key, propOptions, propsData || emptyObject) + } + } else { + if (isDef(data.attrs)) mergeProps(props, data.attrs) + if (isDef(data.props)) mergeProps(props, data.props) + } + + const renderContext = new FunctionalRenderContext( + data, + props, + children, + contextVm, + Ctor + ) + + const vnode = options.render.call(null, renderContext._c, renderContext) + + if (vnode instanceof VNode) { + return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options) + } else if (Array.isArray(vnode)) { + const vnodes = normalizeChildren(vnode) || [] + const res = new Array(vnodes.length) + for (let i = 0; i < vnodes.length; i++) { + res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options) + } + return res + } +} + +function cloneAndMarkFunctionalResult (vnode, data, contextVm, options) { + // #7817 clone node before setting fnContext, otherwise if the node is reused + // (e.g. it was from a cached normal slot) the fnContext causes named slots + // that should not be matched to match. + const clone = cloneVNode(vnode) + clone.fnContext = contextVm + clone.fnOptions = options + if (data.slot) { + (clone.data || (clone.data = {})).slot = data.slot + } + return clone +} + +function mergeProps (to, from) { + for (const key in from) { + to[camelize(key)] = from[key] + } +} diff --git a/node_modules/vue/src/core/vdom/helpers/extract-props.js b/node_modules/vue/src/core/vdom/helpers/extract-props.js new file mode 100644 index 00000000..f2a41661 --- /dev/null +++ b/node_modules/vue/src/core/vdom/helpers/extract-props.js @@ -0,0 +1,75 @@ +/* @flow */ + +import { + tip, + hasOwn, + isDef, + isUndef, + hyphenate, + formatComponentName +} from 'core/util/index' + +export function extractPropsFromVNodeData ( + data: VNodeData, + Ctor: Class<Component>, + tag?: string +): ?Object { + // we are only extracting raw values here. + // validation and default values are handled in the child + // component itself. + const propOptions = Ctor.options.props + if (isUndef(propOptions)) { + return + } + const res = {} + const { attrs, props } = data + if (isDef(attrs) || isDef(props)) { + for (const key in propOptions) { + const altKey = hyphenate(key) + if (process.env.NODE_ENV !== 'production') { + const keyInLowerCase = key.toLowerCase() + if ( + key !== keyInLowerCase && + attrs && hasOwn(attrs, keyInLowerCase) + ) { + tip( + `Prop "${keyInLowerCase}" is passed to component ` + + `${formatComponentName(tag || Ctor)}, but the declared prop name is` + + ` "${key}". ` + + `Note that HTML attributes are case-insensitive and camelCased ` + + `props need to use their kebab-case equivalents when using in-DOM ` + + `templates. You should probably use "${altKey}" instead of "${key}".` + ) + } + } + checkProp(res, props, key, altKey, true) || + checkProp(res, attrs, key, altKey, false) + } + } + return res +} + +function checkProp ( + res: Object, + hash: ?Object, + key: string, + altKey: string, + preserve: boolean +): boolean { + if (isDef(hash)) { + if (hasOwn(hash, key)) { + res[key] = hash[key] + if (!preserve) { + delete hash[key] + } + return true + } else if (hasOwn(hash, altKey)) { + res[key] = hash[altKey] + if (!preserve) { + delete hash[altKey] + } + return true + } + } + return false +} diff --git a/node_modules/vue/src/core/vdom/helpers/get-first-component-child.js b/node_modules/vue/src/core/vdom/helpers/get-first-component-child.js new file mode 100644 index 00000000..f8649dd8 --- /dev/null +++ b/node_modules/vue/src/core/vdom/helpers/get-first-component-child.js @@ -0,0 +1,15 @@ +/* @flow */ + +import { isDef } from 'shared/util' +import { isAsyncPlaceholder } from './is-async-placeholder' + +export function getFirstComponentChild (children: ?Array<VNode>): ?VNode { + if (Array.isArray(children)) { + for (let i = 0; i < children.length; i++) { + const c = children[i] + if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) { + return c + } + } + } +} diff --git a/node_modules/vue/src/core/vdom/helpers/index.js b/node_modules/vue/src/core/vdom/helpers/index.js new file mode 100644 index 00000000..320cf4c4 --- /dev/null +++ b/node_modules/vue/src/core/vdom/helpers/index.js @@ -0,0 +1,9 @@ +/* @flow */ + +export * from './merge-hook' +export * from './extract-props' +export * from './update-listeners' +export * from './normalize-children' +export * from './resolve-async-component' +export * from './get-first-component-child' +export * from './is-async-placeholder' diff --git a/node_modules/vue/src/core/vdom/helpers/is-async-placeholder.js b/node_modules/vue/src/core/vdom/helpers/is-async-placeholder.js new file mode 100644 index 00000000..6c4a6596 --- /dev/null +++ b/node_modules/vue/src/core/vdom/helpers/is-async-placeholder.js @@ -0,0 +1,5 @@ +/* @flow */ + +export function isAsyncPlaceholder (node: VNode): boolean { + return node.isComment && node.asyncFactory +} diff --git a/node_modules/vue/src/core/vdom/helpers/merge-hook.js b/node_modules/vue/src/core/vdom/helpers/merge-hook.js new file mode 100644 index 00000000..0bb96e8b --- /dev/null +++ b/node_modules/vue/src/core/vdom/helpers/merge-hook.js @@ -0,0 +1,38 @@ +/* @flow */ + +import VNode from '../vnode' +import { createFnInvoker } from './update-listeners' +import { remove, isDef, isUndef, isTrue } from 'shared/util' + +export function mergeVNodeHook (def: Object, hookKey: string, hook: Function) { + if (def instanceof VNode) { + def = def.data.hook || (def.data.hook = {}) + } + let invoker + const oldHook = def[hookKey] + + function wrappedHook () { + hook.apply(this, arguments) + // important: remove merged hook to ensure it's called only once + // and prevent memory leak + remove(invoker.fns, wrappedHook) + } + + if (isUndef(oldHook)) { + // no existing hook + invoker = createFnInvoker([wrappedHook]) + } else { + /* istanbul ignore if */ + if (isDef(oldHook.fns) && isTrue(oldHook.merged)) { + // already a merged invoker + invoker = oldHook + invoker.fns.push(wrappedHook) + } else { + // existing plain hook + invoker = createFnInvoker([oldHook, wrappedHook]) + } + } + + invoker.merged = true + def[hookKey] = invoker +} diff --git a/node_modules/vue/src/core/vdom/helpers/normalize-children.js b/node_modules/vue/src/core/vdom/helpers/normalize-children.js new file mode 100644 index 00000000..a4622ef4 --- /dev/null +++ b/node_modules/vue/src/core/vdom/helpers/normalize-children.js @@ -0,0 +1,89 @@ +/* @flow */ + +import VNode, { createTextVNode } from 'core/vdom/vnode' +import { isFalse, isTrue, isDef, isUndef, isPrimitive } from 'shared/util' + +// The template compiler attempts to minimize the need for normalization by +// statically analyzing the template at compile time. +// +// For plain HTML markup, normalization can be completely skipped because the +// generated render function is guaranteed to return Array<VNode>. There are +// two cases where extra normalization is needed: + +// 1. When the children contains components - because a functional component +// may return an Array instead of a single root. In this case, just a simple +// normalization is needed - if any child is an Array, we flatten the whole +// thing with Array.prototype.concat. It is guaranteed to be only 1-level deep +// because functional components already normalize their own children. +export function simpleNormalizeChildren (children: any) { + for (let i = 0; i < children.length; i++) { + if (Array.isArray(children[i])) { + return Array.prototype.concat.apply([], children) + } + } + return children +} + +// 2. When the children contains constructs that always generated nested Arrays, +// e.g. <template>, <slot>, v-for, or when the children is provided by user +// with hand-written render functions / JSX. In such cases a full normalization +// is needed to cater to all possible types of children values. +export function normalizeChildren (children: any): ?Array<VNode> { + return isPrimitive(children) + ? [createTextVNode(children)] + : Array.isArray(children) + ? normalizeArrayChildren(children) + : undefined +} + +function isTextNode (node): boolean { + return isDef(node) && isDef(node.text) && isFalse(node.isComment) +} + +function normalizeArrayChildren (children: any, nestedIndex?: string): Array<VNode> { + const res = [] + let i, c, lastIndex, last + for (i = 0; i < children.length; i++) { + c = children[i] + if (isUndef(c) || typeof c === 'boolean') continue + lastIndex = res.length - 1 + last = res[lastIndex] + // nested + if (Array.isArray(c)) { + if (c.length > 0) { + c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`) + // merge adjacent text nodes + if (isTextNode(c[0]) && isTextNode(last)) { + res[lastIndex] = createTextVNode(last.text + (c[0]: any).text) + c.shift() + } + res.push.apply(res, c) + } + } else if (isPrimitive(c)) { + if (isTextNode(last)) { + // merge adjacent text nodes + // this is necessary for SSR hydration because text nodes are + // essentially merged when rendered to HTML strings + res[lastIndex] = createTextVNode(last.text + c) + } else if (c !== '') { + // convert primitive to vnode + res.push(createTextVNode(c)) + } + } else { + if (isTextNode(c) && isTextNode(last)) { + // merge adjacent text nodes + res[lastIndex] = createTextVNode(last.text + c.text) + } else { + // default key for nested array children (likely generated by v-for) + if (isTrue(children._isVList) && + isDef(c.tag) && + isUndef(c.key) && + isDef(nestedIndex)) { + c.key = `__vlist${nestedIndex}_${i}__` + } + res.push(c) + } + } + } + return res +} diff --git a/node_modules/vue/src/core/vdom/helpers/resolve-async-component.js b/node_modules/vue/src/core/vdom/helpers/resolve-async-component.js new file mode 100644 index 00000000..99ce5ce4 --- /dev/null +++ b/node_modules/vue/src/core/vdom/helpers/resolve-async-component.js @@ -0,0 +1,140 @@ +/* @flow */ + +import { + warn, + once, + isDef, + isUndef, + isTrue, + isObject, + hasSymbol +} from 'core/util/index' + +import { createEmptyVNode } from 'core/vdom/vnode' + +function ensureCtor (comp: any, base) { + if ( + comp.__esModule || + (hasSymbol && comp[Symbol.toStringTag] === 'Module') + ) { + comp = comp.default + } + return isObject(comp) + ? base.extend(comp) + : comp +} + +export function createAsyncPlaceholder ( + factory: Function, + data: ?VNodeData, + context: Component, + children: ?Array<VNode>, + tag: ?string +): VNode { + const node = createEmptyVNode() + node.asyncFactory = factory + node.asyncMeta = { data, context, children, tag } + return node +} + +export function resolveAsyncComponent ( + factory: Function, + baseCtor: Class<Component>, + context: Component +): Class<Component> | void { + if (isTrue(factory.error) && isDef(factory.errorComp)) { + return factory.errorComp + } + + if (isDef(factory.resolved)) { + return factory.resolved + } + + if (isTrue(factory.loading) && isDef(factory.loadingComp)) { + return factory.loadingComp + } + + if (isDef(factory.contexts)) { + // already pending + factory.contexts.push(context) + } else { + const contexts = factory.contexts = [context] + let sync = true + + const forceRender = () => { + for (let i = 0, l = contexts.length; i < l; i++) { + contexts[i].$forceUpdate() + } + } + + const resolve = once((res: Object | Class<Component>) => { + // cache resolved + factory.resolved = ensureCtor(res, baseCtor) + // invoke callbacks only if this is not a synchronous resolve + // (async resolves are shimmed as synchronous during SSR) + if (!sync) { + forceRender() + } + }) + + const reject = once(reason => { + process.env.NODE_ENV !== 'production' && warn( + `Failed to resolve async component: ${String(factory)}` + + (reason ? `\nReason: ${reason}` : '') + ) + if (isDef(factory.errorComp)) { + factory.error = true + forceRender() + } + }) + + const res = factory(resolve, reject) + + if (isObject(res)) { + if (typeof res.then === 'function') { + // () => Promise + if (isUndef(factory.resolved)) { + res.then(resolve, reject) + } + } else if (isDef(res.component) && typeof res.component.then === 'function') { + res.component.then(resolve, reject) + + if (isDef(res.error)) { + factory.errorComp = ensureCtor(res.error, baseCtor) + } + + if (isDef(res.loading)) { + factory.loadingComp = ensureCtor(res.loading, baseCtor) + if (res.delay === 0) { + factory.loading = true + } else { + setTimeout(() => { + if (isUndef(factory.resolved) && isUndef(factory.error)) { + factory.loading = true + forceRender() + } + }, res.delay || 200) + } + } + + if (isDef(res.timeout)) { + setTimeout(() => { + if (isUndef(factory.resolved)) { + reject( + process.env.NODE_ENV !== 'production' + ? `timeout (${res.timeout}ms)` + : null + ) + } + }, res.timeout) + } + } + } + + sync = false + // return in case resolved synchronously + return factory.loading + ? factory.loadingComp + : factory.resolved + } +} diff --git a/node_modules/vue/src/core/vdom/helpers/update-listeners.js b/node_modules/vue/src/core/vdom/helpers/update-listeners.js new file mode 100644 index 00000000..11c5a2cd --- /dev/null +++ b/node_modules/vue/src/core/vdom/helpers/update-listeners.js @@ -0,0 +1,83 @@ +/* @flow */ + +import { warn } from 'core/util/index' +import { cached, isUndef, isPlainObject } from 'shared/util' + +const normalizeEvent = cached((name: string): { + name: string, + once: boolean, + capture: boolean, + passive: boolean, + handler?: Function, + params?: Array<any> +} => { + const passive = name.charAt(0) === '&' + name = passive ? name.slice(1) : name + const once = name.charAt(0) === '~' // Prefixed last, checked first + name = once ? name.slice(1) : name + const capture = name.charAt(0) === '!' + name = capture ? name.slice(1) : name + return { + name, + once, + capture, + passive + } +}) + +export function createFnInvoker (fns: Function | Array<Function>): Function { + function invoker () { + const fns = invoker.fns + if (Array.isArray(fns)) { + const cloned = fns.slice() + for (let i = 0; i < cloned.length; i++) { + cloned[i].apply(null, arguments) + } + } else { + // return handler return value for single handlers + return fns.apply(null, arguments) + } + } + invoker.fns = fns + return invoker +} + +export function updateListeners ( + on: Object, + oldOn: Object, + add: Function, + remove: Function, + vm: Component +) { + let name, def, cur, old, event + for (name in on) { + def = cur = on[name] + old = oldOn[name] + event = normalizeEvent(name) + /* istanbul ignore if */ + if (__WEEX__ && isPlainObject(def)) { + cur = def.handler + event.params = def.params + } + if (isUndef(cur)) { + process.env.NODE_ENV !== 'production' && warn( + `Invalid handler for event "${event.name}": got ` + String(cur), + vm + ) + } else if (isUndef(old)) { + if (isUndef(cur.fns)) { + cur = on[name] = createFnInvoker(cur) + } + add(event.name, cur, event.once, event.capture, event.passive, event.params) + } else if (cur !== old) { + old.fns = cur + on[name] = old + } + } + for (name in oldOn) { + if (isUndef(on[name])) { + event = normalizeEvent(name) + remove(event.name, oldOn[name], event.capture) + } + } +} diff --git a/node_modules/vue/src/core/vdom/modules/directives.js b/node_modules/vue/src/core/vdom/modules/directives.js new file mode 100644 index 00000000..639982fa --- /dev/null +++ b/node_modules/vue/src/core/vdom/modules/directives.js @@ -0,0 +1,119 @@ +/* @flow */ + +import { emptyNode } from 'core/vdom/patch' +import { resolveAsset, handleError } from 'core/util/index' +import { mergeVNodeHook } from 'core/vdom/helpers/index' + +export default { + create: updateDirectives, + update: updateDirectives, + destroy: function unbindDirectives (vnode: VNodeWithData) { + updateDirectives(vnode, emptyNode) + } +} + +function updateDirectives (oldVnode: VNodeWithData, vnode: VNodeWithData) { + if (oldVnode.data.directives || vnode.data.directives) { + _update(oldVnode, vnode) + } +} + +function _update (oldVnode, vnode) { + const isCreate = oldVnode === emptyNode + const isDestroy = vnode === emptyNode + const oldDirs = normalizeDirectives(oldVnode.data.directives, oldVnode.context) + const newDirs = normalizeDirectives(vnode.data.directives, vnode.context) + + const dirsWithInsert = [] + const dirsWithPostpatch = [] + + let key, oldDir, dir + for (key in newDirs) { + oldDir = oldDirs[key] + dir = newDirs[key] + if (!oldDir) { + // new directive, bind + callHook(dir, 'bind', vnode, oldVnode) + if (dir.def && dir.def.inserted) { + dirsWithInsert.push(dir) + } + } else { + // existing directive, update + dir.oldValue = oldDir.value + callHook(dir, 'update', vnode, oldVnode) + if (dir.def && dir.def.componentUpdated) { + dirsWithPostpatch.push(dir) + } + } + } + + if (dirsWithInsert.length) { + const callInsert = () => { + for (let i = 0; i < dirsWithInsert.length; i++) { + callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode) + } + } + if (isCreate) { + mergeVNodeHook(vnode, 'insert', callInsert) + } else { + callInsert() + } + } + + if (dirsWithPostpatch.length) { + mergeVNodeHook(vnode, 'postpatch', () => { + for (let i = 0; i < dirsWithPostpatch.length; i++) { + callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode) + } + }) + } + + if (!isCreate) { + for (key in oldDirs) { + if (!newDirs[key]) { + // no longer present, unbind + callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy) + } + } + } +} + +const emptyModifiers = Object.create(null) + +function normalizeDirectives ( + dirs: ?Array<VNodeDirective>, + vm: Component +): { [key: string]: VNodeDirective } { + const res = Object.create(null) + if (!dirs) { + // $flow-disable-line + return res + } + let i, dir + for (i = 0; i < dirs.length; i++) { + dir = dirs[i] + if (!dir.modifiers) { + // $flow-disable-line + dir.modifiers = emptyModifiers + } + res[getRawDirName(dir)] = dir + dir.def = resolveAsset(vm.$options, 'directives', dir.name, true) + } + // $flow-disable-line + return res +} + +function getRawDirName (dir: VNodeDirective): string { + return dir.rawName || `${dir.name}.${Object.keys(dir.modifiers || {}).join('.')}` +} + +function callHook (dir, hook, vnode, oldVnode, isDestroy) { + const fn = dir.def && dir.def[hook] + if (fn) { + try { + fn(vnode.elm, dir, vnode, oldVnode, isDestroy) + } catch (e) { + handleError(e, vnode.context, `directive ${dir.name} ${hook} hook`) + } + } +} diff --git a/node_modules/vue/src/core/vdom/modules/index.js b/node_modules/vue/src/core/vdom/modules/index.js new file mode 100644 index 00000000..727c8646 --- /dev/null +++ b/node_modules/vue/src/core/vdom/modules/index.js @@ -0,0 +1,7 @@ +import directives from './directives' +import ref from './ref' + +export default [ + ref, + directives +] diff --git a/node_modules/vue/src/core/vdom/modules/ref.js b/node_modules/vue/src/core/vdom/modules/ref.js new file mode 100644 index 00000000..aa9bdcfc --- /dev/null +++ b/node_modules/vue/src/core/vdom/modules/ref.js @@ -0,0 +1,45 @@ +/* @flow */ + +import { remove, isDef } from 'shared/util' + +export default { + create (_: any, vnode: VNodeWithData) { + registerRef(vnode) + }, + update (oldVnode: VNodeWithData, vnode: VNodeWithData) { + if (oldVnode.data.ref !== vnode.data.ref) { + registerRef(oldVnode, true) + registerRef(vnode) + } + }, + destroy (vnode: VNodeWithData) { + registerRef(vnode, true) + } +} + +export function registerRef (vnode: VNodeWithData, isRemoval: ?boolean) { + const key = vnode.data.ref + if (!isDef(key)) return + + const vm = vnode.context + const ref = vnode.componentInstance || vnode.elm + const refs = vm.$refs + if (isRemoval) { + if (Array.isArray(refs[key])) { + remove(refs[key], ref) + } else if (refs[key] === ref) { + refs[key] = undefined + } + } else { + if (vnode.data.refInFor) { + if (!Array.isArray(refs[key])) { + refs[key] = [ref] + } else if (refs[key].indexOf(ref) < 0) { + // $flow-disable-line + refs[key].push(ref) + } + } else { + refs[key] = ref + } + } +} diff --git a/node_modules/vue/src/core/vdom/patch.js b/node_modules/vue/src/core/vdom/patch.js new file mode 100644 index 00000000..9f3bcc65 --- /dev/null +++ b/node_modules/vue/src/core/vdom/patch.js @@ -0,0 +1,787 @@ +/** + * Virtual DOM patching algorithm based on Snabbdom by + * Simon Friis Vindum (@paldepind) + * Licensed under the MIT License + * https://github.com/paldepind/snabbdom/blob/master/LICENSE + * + * modified by Evan You (@yyx990803) + * + * Not type-checking this because this file is perf-critical and the cost + * of making flow understand it is not worth it. + */ + +import VNode, { cloneVNode } from './vnode' +import config from '../config' +import { SSR_ATTR } from 'shared/constants' +import { registerRef } from './modules/ref' +import { traverse } from '../observer/traverse' +import { activeInstance } from '../instance/lifecycle' +import { isTextInputType } from 'web/util/element' + +import { + warn, + isDef, + isUndef, + isTrue, + makeMap, + isRegExp, + isPrimitive +} from '../util/index' + +export const emptyNode = new VNode('', {}, []) + +const hooks = ['create', 'activate', 'update', 'remove', 'destroy'] + +function sameVnode (a, b) { + return ( + a.key === b.key && ( + ( + a.tag === b.tag && + a.isComment === b.isComment && + isDef(a.data) === isDef(b.data) && + sameInputType(a, b) + ) || ( + isTrue(a.isAsyncPlaceholder) && + a.asyncFactory === b.asyncFactory && + isUndef(b.asyncFactory.error) + ) + ) + ) +} + +function sameInputType (a, b) { + if (a.tag !== 'input') return true + let i + const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type + const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type + return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB) +} + +function createKeyToOldIdx (children, beginIdx, endIdx) { + let i, key + const map = {} + for (i = beginIdx; i <= endIdx; ++i) { + key = children[i].key + if (isDef(key)) map[key] = i + } + return map +} + +export function createPatchFunction (backend) { + let i, j + const cbs = {} + + const { modules, nodeOps } = backend + + for (i = 0; i < hooks.length; ++i) { + cbs[hooks[i]] = [] + for (j = 0; j < modules.length; ++j) { + if (isDef(modules[j][hooks[i]])) { + cbs[hooks[i]].push(modules[j][hooks[i]]) + } + } + } + + function emptyNodeAt (elm) { + return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm) + } + + function createRmCb (childElm, listeners) { + function remove () { + if (--remove.listeners === 0) { + removeNode(childElm) + } + } + remove.listeners = listeners + return remove + } + + function removeNode (el) { + const parent = nodeOps.parentNode(el) + // element may have already been removed due to v-html / v-text + if (isDef(parent)) { + nodeOps.removeChild(parent, el) + } + } + + function isUnknownElement (vnode, inVPre) { + return ( + !inVPre && + !vnode.ns && + !( + config.ignoredElements.length && + config.ignoredElements.some(ignore => { + return isRegExp(ignore) + ? ignore.test(vnode.tag) + : ignore === vnode.tag + }) + ) && + config.isUnknownElement(vnode.tag) + ) + } + + let creatingElmInVPre = 0 + + function createElm ( + vnode, + insertedVnodeQueue, + parentElm, + refElm, + nested, + ownerArray, + index + ) { + if (isDef(vnode.elm) && isDef(ownerArray)) { + // This vnode was used in a previous render! + // now it's used as a new node, overwriting its elm would cause + // potential patch errors down the road when it's used as an insertion + // reference node. Instead, we clone the node on-demand before creating + // associated DOM element for it. + vnode = ownerArray[index] = cloneVNode(vnode) + } + + vnode.isRootInsert = !nested // for transition enter check + if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { + return + } + + const data = vnode.data + const children = vnode.children + const tag = vnode.tag + if (isDef(tag)) { + if (process.env.NODE_ENV !== 'production') { + if (data && data.pre) { + creatingElmInVPre++ + } + if (isUnknownElement(vnode, creatingElmInVPre)) { + warn( + 'Unknown custom element: <' + tag + '> - did you ' + + 'register the component correctly? For recursive components, ' + + 'make sure to provide the "name" option.', + vnode.context + ) + } + } + + vnode.elm = vnode.ns + ? nodeOps.createElementNS(vnode.ns, tag) + : nodeOps.createElement(tag, vnode) + setScope(vnode) + + /* istanbul ignore if */ + if (__WEEX__) { + // in Weex, the default insertion order is parent-first. + // List items can be optimized to use children-first insertion + // with append="tree". + const appendAsTree = isDef(data) && isTrue(data.appendAsTree) + if (!appendAsTree) { + if (isDef(data)) { + invokeCreateHooks(vnode, insertedVnodeQueue) + } + insert(parentElm, vnode.elm, refElm) + } + createChildren(vnode, children, insertedVnodeQueue) + if (appendAsTree) { + if (isDef(data)) { + invokeCreateHooks(vnode, insertedVnodeQueue) + } + insert(parentElm, vnode.elm, refElm) + } + } else { + createChildren(vnode, children, insertedVnodeQueue) + if (isDef(data)) { + invokeCreateHooks(vnode, insertedVnodeQueue) + } + insert(parentElm, vnode.elm, refElm) + } + + if (process.env.NODE_ENV !== 'production' && data && data.pre) { + creatingElmInVPre-- + } + } else if (isTrue(vnode.isComment)) { + vnode.elm = nodeOps.createComment(vnode.text) + insert(parentElm, vnode.elm, refElm) + } else { + vnode.elm = nodeOps.createTextNode(vnode.text) + insert(parentElm, vnode.elm, refElm) + } + } + + function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) { + let i = vnode.data + if (isDef(i)) { + const isReactivated = isDef(vnode.componentInstance) && i.keepAlive + if (isDef(i = i.hook) && isDef(i = i.init)) { + i(vnode, false /* hydrating */, parentElm, refElm) + } + // after calling the init hook, if the vnode is a child component + // it should've created a child instance and mounted it. the child + // component also has set the placeholder vnode's elm. + // in that case we can just return the element and be done. + if (isDef(vnode.componentInstance)) { + initComponent(vnode, insertedVnodeQueue) + if (isTrue(isReactivated)) { + reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm) + } + return true + } + } + } + + function initComponent (vnode, insertedVnodeQueue) { + if (isDef(vnode.data.pendingInsert)) { + insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert) + vnode.data.pendingInsert = null + } + vnode.elm = vnode.componentInstance.$el + if (isPatchable(vnode)) { + invokeCreateHooks(vnode, insertedVnodeQueue) + setScope(vnode) + } else { + // empty component root. + // skip all element-related modules except for ref (#3455) + registerRef(vnode) + // make sure to invoke the insert hook + insertedVnodeQueue.push(vnode) + } + } + + function reactivateComponent (vnode, insertedVnodeQueue, parentElm, refElm) { + let i + // hack for #4339: a reactivated component with inner transition + // does not trigger because the inner node's created hooks are not called + // again. It's not ideal to involve module-specific logic in here but + // there doesn't seem to be a better way to do it. + let innerNode = vnode + while (innerNode.componentInstance) { + innerNode = innerNode.componentInstance._vnode + if (isDef(i = innerNode.data) && isDef(i = i.transition)) { + for (i = 0; i < cbs.activate.length; ++i) { + cbs.activate[i](emptyNode, innerNode) + } + insertedVnodeQueue.push(innerNode) + break + } + } + // unlike a newly created component, + // a reactivated keep-alive component doesn't insert itself + insert(parentElm, vnode.elm, refElm) + } + + function insert (parent, elm, ref) { + if (isDef(parent)) { + if (isDef(ref)) { + if (ref.parentNode === parent) { + nodeOps.insertBefore(parent, elm, ref) + } + } else { + nodeOps.appendChild(parent, elm) + } + } + } + + function createChildren (vnode, children, insertedVnodeQueue) { + if (Array.isArray(children)) { + if (process.env.NODE_ENV !== 'production') { + checkDuplicateKeys(children) + } + for (let i = 0; i < children.length; ++i) { + createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i) + } + } else if (isPrimitive(vnode.text)) { + nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text))) + } + } + + function isPatchable (vnode) { + while (vnode.componentInstance) { + vnode = vnode.componentInstance._vnode + } + return isDef(vnode.tag) + } + + function invokeCreateHooks (vnode, insertedVnodeQueue) { + for (let i = 0; i < cbs.create.length; ++i) { + cbs.create[i](emptyNode, vnode) + } + i = vnode.data.hook // Reuse variable + if (isDef(i)) { + if (isDef(i.create)) i.create(emptyNode, vnode) + if (isDef(i.insert)) insertedVnodeQueue.push(vnode) + } + } + + // set scope id attribute for scoped CSS. + // this is implemented as a special case to avoid the overhead + // of going through the normal attribute patching process. + function setScope (vnode) { + let i + if (isDef(i = vnode.fnScopeId)) { + nodeOps.setStyleScope(vnode.elm, i) + } else { + let ancestor = vnode + while (ancestor) { + if (isDef(i = ancestor.context) && isDef(i = i.$options._scopeId)) { + nodeOps.setStyleScope(vnode.elm, i) + } + ancestor = ancestor.parent + } + } + // for slot content they should also get the scopeId from the host instance. + if (isDef(i = activeInstance) && + i !== vnode.context && + i !== vnode.fnContext && + isDef(i = i.$options._scopeId) + ) { + nodeOps.setStyleScope(vnode.elm, i) + } + } + + function addVnodes (parentElm, refElm, vnodes, startIdx, endIdx, insertedVnodeQueue) { + for (; startIdx <= endIdx; ++startIdx) { + createElm(vnodes[startIdx], insertedVnodeQueue, parentElm, refElm, false, vnodes, startIdx) + } + } + + function invokeDestroyHook (vnode) { + let i, j + const data = vnode.data + if (isDef(data)) { + if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode) + for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode) + } + if (isDef(i = vnode.children)) { + for (j = 0; j < vnode.children.length; ++j) { + invokeDestroyHook(vnode.children[j]) + } + } + } + + function removeVnodes (parentElm, vnodes, startIdx, endIdx) { + for (; startIdx <= endIdx; ++startIdx) { + const ch = vnodes[startIdx] + if (isDef(ch)) { + if (isDef(ch.tag)) { + removeAndInvokeRemoveHook(ch) + invokeDestroyHook(ch) + } else { // Text node + removeNode(ch.elm) + } + } + } + } + + function removeAndInvokeRemoveHook (vnode, rm) { + if (isDef(rm) || isDef(vnode.data)) { + let i + const listeners = cbs.remove.length + 1 + if (isDef(rm)) { + // we have a recursively passed down rm callback + // increase the listeners count + rm.listeners += listeners + } else { + // directly removing + rm = createRmCb(vnode.elm, listeners) + } + // recursively invoke hooks on child component root node + if (isDef(i = vnode.componentInstance) && isDef(i = i._vnode) && isDef(i.data)) { + removeAndInvokeRemoveHook(i, rm) + } + for (i = 0; i < cbs.remove.length; ++i) { + cbs.remove[i](vnode, rm) + } + if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) { + i(vnode, rm) + } else { + rm() + } + } else { + removeNode(vnode.elm) + } + } + + function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) { + let oldStartIdx = 0 + let newStartIdx = 0 + let oldEndIdx = oldCh.length - 1 + let oldStartVnode = oldCh[0] + let oldEndVnode = oldCh[oldEndIdx] + let newEndIdx = newCh.length - 1 + let newStartVnode = newCh[0] + let newEndVnode = newCh[newEndIdx] + let oldKeyToIdx, idxInOld, vnodeToMove, refElm + + // removeOnly is a special flag used only by <transition-group> + // to ensure removed elements stay in correct relative positions + // during leaving transitions + const canMove = !removeOnly + + if (process.env.NODE_ENV !== 'production') { + checkDuplicateKeys(newCh) + } + + while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { + if (isUndef(oldStartVnode)) { + oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left + } else if (isUndef(oldEndVnode)) { + oldEndVnode = oldCh[--oldEndIdx] + } else if (sameVnode(oldStartVnode, newStartVnode)) { + patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue) + oldStartVnode = oldCh[++oldStartIdx] + newStartVnode = newCh[++newStartIdx] + } else if (sameVnode(oldEndVnode, newEndVnode)) { + patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue) + oldEndVnode = oldCh[--oldEndIdx] + newEndVnode = newCh[--newEndIdx] + } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right + patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue) + canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)) + oldStartVnode = oldCh[++oldStartIdx] + newEndVnode = newCh[--newEndIdx] + } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left + patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue) + canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) + oldEndVnode = oldCh[--oldEndIdx] + newStartVnode = newCh[++newStartIdx] + } else { + if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) + idxInOld = isDef(newStartVnode.key) + ? oldKeyToIdx[newStartVnode.key] + : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) + if (isUndef(idxInOld)) { // New element + createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) + } else { + vnodeToMove = oldCh[idxInOld] + if (sameVnode(vnodeToMove, newStartVnode)) { + patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue) + oldCh[idxInOld] = undefined + canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm) + } else { + // same key but different element. treat as new element + createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) + } + } + newStartVnode = newCh[++newStartIdx] + } + } + if (oldStartIdx > oldEndIdx) { + refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm + addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue) + } else if (newStartIdx > newEndIdx) { + removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx) + } + } + + function checkDuplicateKeys (children) { + const seenKeys = {} + for (let i = 0; i < children.length; i++) { + const vnode = children[i] + const key = vnode.key + if (isDef(key)) { + if (seenKeys[key]) { + warn( + `Duplicate keys detected: '${key}'. This may cause an update error.`, + vnode.context + ) + } else { + seenKeys[key] = true + } + } + } + } + + function findIdxInOld (node, oldCh, start, end) { + for (let i = start; i < end; i++) { + const c = oldCh[i] + if (isDef(c) && sameVnode(node, c)) return i + } + } + + function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) { + if (oldVnode === vnode) { + return + } + + const elm = vnode.elm = oldVnode.elm + + if (isTrue(oldVnode.isAsyncPlaceholder)) { + if (isDef(vnode.asyncFactory.resolved)) { + hydrate(oldVnode.elm, vnode, insertedVnodeQueue) + } else { + vnode.isAsyncPlaceholder = true + } + return + } + + // reuse element for static trees. + // note we only do this if the vnode is cloned - + // if the new node is not cloned it means the render functions have been + // reset by the hot-reload-api and we need to do a proper re-render. + if (isTrue(vnode.isStatic) && + isTrue(oldVnode.isStatic) && + vnode.key === oldVnode.key && + (isTrue(vnode.isCloned) || isTrue(vnode.isOnce)) + ) { + vnode.componentInstance = oldVnode.componentInstance + return + } + + let i + const data = vnode.data + if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) { + i(oldVnode, vnode) + } + + const oldCh = oldVnode.children + const ch = vnode.children + if (isDef(data) && isPatchable(vnode)) { + for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode) + if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode) + } + if (isUndef(vnode.text)) { + if (isDef(oldCh) && isDef(ch)) { + if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) + } else if (isDef(ch)) { + if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '') + addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue) + } else if (isDef(oldCh)) { + removeVnodes(elm, oldCh, 0, oldCh.length - 1) + } else if (isDef(oldVnode.text)) { + nodeOps.setTextContent(elm, '') + } + } else if (oldVnode.text !== vnode.text) { + nodeOps.setTextContent(elm, vnode.text) + } + if (isDef(data)) { + if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode) + } + } + + function invokeInsertHook (vnode, queue, initial) { + // delay insert hooks for component root nodes, invoke them after the + // element is really inserted + if (isTrue(initial) && isDef(vnode.parent)) { + vnode.parent.data.pendingInsert = queue + } else { + for (let i = 0; i < queue.length; ++i) { + queue[i].data.hook.insert(queue[i]) + } + } + } + + let hydrationBailed = false + // list of modules that can skip create hook during hydration because they + // are already rendered on the client or has no need for initialization + // Note: style is excluded because it relies on initial clone for future + // deep updates (#7063). + const isRenderedModule = makeMap('attrs,class,staticClass,staticStyle,key') + + // Note: this is a browser-only function so we can assume elms are DOM nodes. + function hydrate (elm, vnode, insertedVnodeQueue, inVPre) { + let i + const { tag, data, children } = vnode + inVPre = inVPre || (data && data.pre) + vnode.elm = elm + + if (isTrue(vnode.isComment) && isDef(vnode.asyncFactory)) { + vnode.isAsyncPlaceholder = true + return true + } + // assert node match + if (process.env.NODE_ENV !== 'production') { + if (!assertNodeMatch(elm, vnode, inVPre)) { + return false + } + } + if (isDef(data)) { + if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode, true /* hydrating */) + if (isDef(i = vnode.componentInstance)) { + // child component. it should have hydrated its own tree. + initComponent(vnode, insertedVnodeQueue) + return true + } + } + if (isDef(tag)) { + if (isDef(children)) { + // empty element, allow client to pick up and populate children + if (!elm.hasChildNodes()) { + createChildren(vnode, children, insertedVnodeQueue) + } else { + // v-html and domProps: innerHTML + if (isDef(i = data) && isDef(i = i.domProps) && isDef(i = i.innerHTML)) { + if (i !== elm.innerHTML) { + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production' && + typeof console !== 'undefined' && + !hydrationBailed + ) { + hydrationBailed = true + console.warn('Parent: ', elm) + console.warn('server innerHTML: ', i) + console.warn('client innerHTML: ', elm.innerHTML) + } + return false + } + } else { + // iterate and compare children lists + let childrenMatch = true + let childNode = elm.firstChild + for (let i = 0; i < children.length; i++) { + if (!childNode || !hydrate(childNode, children[i], insertedVnodeQueue, inVPre)) { + childrenMatch = false + break + } + childNode = childNode.nextSibling + } + // if childNode is not null, it means the actual childNodes list is + // longer than the virtual children list. + if (!childrenMatch || childNode) { + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production' && + typeof console !== 'undefined' && + !hydrationBailed + ) { + hydrationBailed = true + console.warn('Parent: ', elm) + console.warn('Mismatching childNodes vs. VNodes: ', elm.childNodes, children) + } + return false + } + } + } + } + if (isDef(data)) { + let fullInvoke = false + for (const key in data) { + if (!isRenderedModule(key)) { + fullInvoke = true + invokeCreateHooks(vnode, insertedVnodeQueue) + break + } + } + if (!fullInvoke && data['class']) { + // ensure collecting deps for deep class bindings for future updates + traverse(data['class']) + } + } + } else if (elm.data !== vnode.text) { + elm.data = vnode.text + } + return true + } + + function assertNodeMatch (node, vnode, inVPre) { + if (isDef(vnode.tag)) { + return vnode.tag.indexOf('vue-component') === 0 || ( + !isUnknownElement(vnode, inVPre) && + vnode.tag.toLowerCase() === (node.tagName && node.tagName.toLowerCase()) + ) + } else { + return node.nodeType === (vnode.isComment ? 8 : 3) + } + } + + return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) { + if (isUndef(vnode)) { + if (isDef(oldVnode)) invokeDestroyHook(oldVnode) + return + } + + let isInitialPatch = false + const insertedVnodeQueue = [] + + if (isUndef(oldVnode)) { + // empty mount (likely as component), create new root element + isInitialPatch = true + createElm(vnode, insertedVnodeQueue, parentElm, refElm) + } else { + const isRealElement = isDef(oldVnode.nodeType) + if (!isRealElement && sameVnode(oldVnode, vnode)) { + // patch existing root node + patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly) + } else { + if (isRealElement) { + // mounting to a real element + // check if this is server-rendered content and if we can perform + // a successful hydration. + if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) { + oldVnode.removeAttribute(SSR_ATTR) + hydrating = true + } + if (isTrue(hydrating)) { + if (hydrate(oldVnode, vnode, insertedVnodeQueue)) { + invokeInsertHook(vnode, insertedVnodeQueue, true) + return oldVnode + } else if (process.env.NODE_ENV !== 'production') { + warn( + 'The client-side rendered virtual DOM tree is not matching ' + + 'server-rendered content. This is likely caused by incorrect ' + + 'HTML markup, for example nesting block-level elements inside ' + + '<p>, or missing <tbody>. Bailing hydration and performing ' + + 'full client-side render.' + ) + } + } + // either not server-rendered, or hydration failed. + // create an empty node and replace it + oldVnode = emptyNodeAt(oldVnode) + } + + // replacing existing element + const oldElm = oldVnode.elm + const parentElm = nodeOps.parentNode(oldElm) + + // create new node + createElm( + vnode, + insertedVnodeQueue, + // extremely rare edge case: do not insert if old element is in a + // leaving transition. Only happens when combining transition + + // keep-alive + HOCs. (#4590) + oldElm._leaveCb ? null : parentElm, + nodeOps.nextSibling(oldElm) + ) + + // update parent placeholder node element, recursively + if (isDef(vnode.parent)) { + let ancestor = vnode.parent + const patchable = isPatchable(vnode) + while (ancestor) { + for (let i = 0; i < cbs.destroy.length; ++i) { + cbs.destroy[i](ancestor) + } + ancestor.elm = vnode.elm + if (patchable) { + for (let i = 0; i < cbs.create.length; ++i) { + cbs.create[i](emptyNode, ancestor) + } + // #6513 + // invoke insert hooks that may have been merged by create hooks. + // e.g. for directives that uses the "inserted" hook. + const insert = ancestor.data.hook.insert + if (insert.merged) { + // start at index 1 to avoid re-invoking component mounted hook + for (let i = 1; i < insert.fns.length; i++) { + insert.fns[i]() + } + } + } else { + registerRef(ancestor) + } + ancestor = ancestor.parent + } + } + + // destroy old node + if (isDef(parentElm)) { + removeVnodes(parentElm, [oldVnode], 0, 0) + } else if (isDef(oldVnode.tag)) { + invokeDestroyHook(oldVnode) + } + } + } + + invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch) + return vnode.elm + } +} diff --git a/node_modules/vue/src/core/vdom/vnode.js b/node_modules/vue/src/core/vdom/vnode.js new file mode 100644 index 00000000..88e80cec --- /dev/null +++ b/node_modules/vue/src/core/vdom/vnode.js @@ -0,0 +1,108 @@ +/* @flow */ + +export default class VNode { + tag: string | void; + data: VNodeData | void; + children: ?Array<VNode>; + text: string | void; + elm: Node | void; + ns: string | void; + context: Component | void; // rendered in this component's scope + key: string | number | void; + componentOptions: VNodeComponentOptions | void; + componentInstance: Component | void; // component instance + parent: VNode | void; // component placeholder node + + // strictly internal + raw: boolean; // contains raw HTML? (server only) + isStatic: boolean; // hoisted static node + isRootInsert: boolean; // necessary for enter transition check + isComment: boolean; // empty comment placeholder? + isCloned: boolean; // is a cloned node? + isOnce: boolean; // is a v-once node? + asyncFactory: Function | void; // async component factory function + asyncMeta: Object | void; + isAsyncPlaceholder: boolean; + ssrContext: Object | void; + fnContext: Component | void; // real context vm for functional nodes + fnOptions: ?ComponentOptions; // for SSR caching + fnScopeId: ?string; // functional scope id support + + constructor ( + tag?: string, + data?: VNodeData, + children?: ?Array<VNode>, + text?: string, + elm?: Node, + context?: Component, + componentOptions?: VNodeComponentOptions, + asyncFactory?: Function + ) { + this.tag = tag + this.data = data + this.children = children + this.text = text + this.elm = elm + this.ns = undefined + this.context = context + this.fnContext = undefined + this.fnOptions = undefined + this.fnScopeId = undefined + this.key = data && data.key + this.componentOptions = componentOptions + this.componentInstance = undefined + this.parent = undefined + this.raw = false + this.isStatic = false + this.isRootInsert = true + this.isComment = false + this.isCloned = false + this.isOnce = false + this.asyncFactory = asyncFactory + this.asyncMeta = undefined + this.isAsyncPlaceholder = false + } + + // DEPRECATED: alias for componentInstance for backwards compat. + /* istanbul ignore next */ + get child (): Component | void { + return this.componentInstance + } +} + +export const createEmptyVNode = (text: string = '') => { + const node = new VNode() + node.text = text + node.isComment = true + return node +} + +export function createTextVNode (val: string | number) { + return new VNode(undefined, undefined, undefined, String(val)) +} + +// optimized shallow clone +// used for static nodes and slot nodes because they may be reused across +// multiple renders, cloning them avoids errors when DOM manipulations rely +// on their elm reference. +export function cloneVNode (vnode: VNode): VNode { + const cloned = new VNode( + vnode.tag, + vnode.data, + vnode.children, + vnode.text, + vnode.elm, + vnode.context, + vnode.componentOptions, + vnode.asyncFactory + ) + cloned.ns = vnode.ns + cloned.isStatic = vnode.isStatic + cloned.key = vnode.key + cloned.isComment = vnode.isComment + cloned.fnContext = vnode.fnContext + cloned.fnOptions = vnode.fnOptions + cloned.fnScopeId = vnode.fnScopeId + cloned.isCloned = true + return cloned +} |
