| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- import Vue from '../instance/vue'
- import config from '../config'
- import {
- extend,
- set,
- isObject,
- isArray,
- isPlainObject,
- hasOwn,
- camelize,
- hyphenate
- } from './lang'
- import { warn } from './debug'
- import { commonTagRE, reservedTagRE } from './component'
- /**
- * Option overwriting strategies are functions that handle
- * how to merge a parent option value and a child option
- * value into the final value.
- *
- * All strategy functions follow the same signature:
- *
- * @param {*} parentVal
- * @param {*} childVal
- * @param {Vue} [vm]
- */
- var strats = config.optionMergeStrategies = Object.create(null)
- /**
- * Helper that recursively merges two data objects together.
- */
- function mergeData (to, from) {
- var key, toVal, fromVal
- for (key in from) {
- toVal = to[key]
- fromVal = from[key]
- if (!hasOwn(to, key)) {
- set(to, key, fromVal)
- } else if (isObject(toVal) && isObject(fromVal)) {
- mergeData(toVal, fromVal)
- }
- }
- return to
- }
- /**
- * Data
- */
- strats.data = function (parentVal, childVal, vm) {
- if (!vm) {
- // in a Vue.extend merge, both should be functions
- if (!childVal) {
- return parentVal
- }
- if (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
- }
- 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(
- childVal.call(this),
- parentVal.call(this)
- )
- }
- } else if (parentVal || childVal) {
- return function mergedInstanceDataFn () {
- // instance merge
- var instanceData = typeof childVal === 'function'
- ? childVal.call(vm)
- : childVal
- var defaultData = typeof parentVal === 'function'
- ? parentVal.call(vm)
- : undefined
- if (instanceData) {
- return mergeData(instanceData, defaultData)
- } else {
- return defaultData
- }
- }
- }
- }
- /**
- * El
- */
- strats.el = function (parentVal, childVal, vm) {
- if (!vm && childVal && typeof childVal !== 'function') {
- process.env.NODE_ENV !== 'production' && warn(
- 'The "el" option should be a function ' +
- 'that returns a per-instance value in component ' +
- 'definitions.',
- vm
- )
- return
- }
- var ret = childVal || parentVal
- // invoke the element factory if this is instance merge
- return vm && typeof ret === 'function'
- ? ret.call(vm)
- : ret
- }
- /**
- * Hooks and param attributes are merged as arrays.
- */
- strats.init =
- strats.created =
- strats.ready =
- strats.attached =
- strats.detached =
- strats.beforeCompile =
- strats.compiled =
- strats.beforeDestroy =
- strats.destroyed =
- strats.activate = function (parentVal, childVal) {
- return childVal
- ? parentVal
- ? parentVal.concat(childVal)
- : isArray(childVal)
- ? childVal
- : [childVal]
- : parentVal
- }
- /**
- * 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, childVal) {
- var res = Object.create(parentVal)
- return childVal
- ? extend(res, guardArrayAssets(childVal))
- : res
- }
- config._assetTypes.forEach(function (type) {
- strats[type + 's'] = mergeAssets
- })
- /**
- * Events & Watchers.
- *
- * Events & watchers hashes should not overwrite one
- * another, so we merge them as arrays.
- */
- strats.watch =
- strats.events = function (parentVal, childVal) {
- if (!childVal) return parentVal
- if (!parentVal) return childVal
- var ret = {}
- extend(ret, parentVal)
- for (var key in childVal) {
- var parent = ret[key]
- var child = childVal[key]
- if (parent && !isArray(parent)) {
- parent = [parent]
- }
- ret[key] = parent
- ? parent.concat(child)
- : [child]
- }
- return ret
- }
- /**
- * Other object hashes.
- */
- strats.props =
- strats.methods =
- strats.computed = function (parentVal, childVal) {
- if (!childVal) return parentVal
- if (!parentVal) return childVal
- var ret = Object.create(null)
- extend(ret, parentVal)
- extend(ret, childVal)
- return ret
- }
- /**
- * Default strategy.
- */
- var defaultStrat = function (parentVal, childVal) {
- return childVal === undefined
- ? parentVal
- : childVal
- }
- /**
- * Make sure component options get converted to actual
- * constructors.
- *
- * @param {Object} options
- */
- function guardComponents (options) {
- if (options.components) {
- var components = options.components =
- guardArrayAssets(options.components)
- var ids = Object.keys(components)
- var def
- if (process.env.NODE_ENV !== 'production') {
- var map = options._componentNameMap = {}
- }
- for (var i = 0, l = ids.length; i < l; i++) {
- var key = ids[i]
- if (commonTagRE.test(key) || reservedTagRE.test(key)) {
- process.env.NODE_ENV !== 'production' && warn(
- 'Do not use built-in or reserved HTML elements as component ' +
- 'id: ' + key
- )
- continue
- }
- // record a all lowercase <-> kebab-case mapping for
- // possible custom element case error warning
- if (process.env.NODE_ENV !== 'production') {
- map[key.replace(/-/g, '').toLowerCase()] = hyphenate(key)
- }
- def = components[key]
- if (isPlainObject(def)) {
- components[key] = Vue.extend(def)
- }
- }
- }
- }
- /**
- * Ensure all props option syntax are normalized into the
- * Object-based format.
- *
- * @param {Object} options
- */
- function guardProps (options) {
- var props = options.props
- var i, val
- if (isArray(props)) {
- options.props = {}
- i = props.length
- while (i--) {
- val = props[i]
- if (typeof val === 'string') {
- options.props[val] = null
- } else if (val.name) {
- options.props[val.name] = val
- }
- }
- } else if (isPlainObject(props)) {
- var keys = Object.keys(props)
- i = keys.length
- while (i--) {
- val = props[keys[i]]
- if (typeof val === 'function') {
- props[keys[i]] = { type: val }
- }
- }
- }
- }
- /**
- * Guard an Array-format assets option and converted it
- * into the key-value Object format.
- *
- * @param {Object|Array} assets
- * @return {Object}
- */
- function guardArrayAssets (assets) {
- if (isArray(assets)) {
- var res = {}
- var i = assets.length
- var asset
- while (i--) {
- asset = assets[i]
- var id = typeof asset === 'function'
- ? ((asset.options && asset.options.name) || asset.id)
- : (asset.name || asset.id)
- if (!id) {
- process.env.NODE_ENV !== 'production' && warn(
- 'Array-syntax assets must provide a "name" or "id" field.'
- )
- } else {
- res[id] = asset
- }
- }
- return res
- }
- return assets
- }
- /**
- * Merge two option objects into a new one.
- * Core utility used in both instantiation and inheritance.
- *
- * @param {Object} parent
- * @param {Object} child
- * @param {Vue} [vm] - if vm is present, indicates this is
- * an instantiation merge.
- */
- export function mergeOptions (parent, child, vm) {
- guardComponents(child)
- guardProps(child)
- if (process.env.NODE_ENV !== 'production') {
- if (child.propsData && !vm) {
- warn('propsData can only be used as an instantiation option.')
- }
- }
- var options = {}
- var key
- if (child.extends) {
- parent = typeof child.extends === 'function'
- ? mergeOptions(parent, child.extends.options, vm)
- : mergeOptions(parent, child.extends, vm)
- }
- if (child.mixins) {
- for (var i = 0, l = child.mixins.length; i < l; i++) {
- parent = mergeOptions(parent, child.mixins[i], vm)
- }
- }
- for (key in parent) {
- mergeField(key)
- }
- for (key in child) {
- if (!hasOwn(parent, key)) {
- mergeField(key)
- }
- }
- function mergeField (key) {
- var 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.
- *
- * @param {Object} options
- * @param {String} type
- * @param {String} id
- * @param {Boolean} warnMissing
- * @return {Object|Function}
- */
- export function resolveAsset (options, type, id, warnMissing) {
- /* istanbul ignore if */
- if (typeof id !== 'string') {
- return
- }
- var assets = options[type]
- var camelizedId
- var res = assets[id] ||
- // camelCase ID
- assets[camelizedId = camelize(id)] ||
- // Pascal Case ID
- assets[camelizedId.charAt(0).toUpperCase() + camelizedId.slice(1)]
- if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
- warn(
- 'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
- options
- )
- }
- return res
- }
|