diff options
Diffstat (limited to 'node_modules/vue/src/core/instance')
17 files changed, 1653 insertions, 0 deletions
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() + } + } +} |
