123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- /* @flow */
- import config from '../config'
- import { warn } from './debug'
- import { set } from '../observer/index'
- import { unicodeRegExp } from './lang'
- import { nativeWatch, hasSymbol } from './env'
- 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 = hasSymbol
- ? Reflect.ownKeys(from)
- : Object.keys(from)
- for (let i = 0; i < keys.length; i++) {
- key = keys[i]
- // in case the object is already observed...
- if (key === '__ob__') continue
- toVal = to[key]
- fromVal = from[key]
- if (!hasOwn(to, key)) {
- set(to, key, fromVal)
- } else if (
- toVal !== fromVal &&
- 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> {
- const res = childVal
- ? parentVal
- ? parentVal.concat(childVal)
- : Array.isArray(childVal)
- ? childVal
- : [childVal]
- : parentVal
- return res
- ? dedupeHooks(res)
- : res
- }
- function dedupeHooks (hooks) {
- const res = []
- for (let i = 0; i < hooks.length; i++) {
- if (res.indexOf(hooks[i]) === -1) {
- res.push(hooks[i])
- }
- }
- return res
- }
- 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 (!new RegExp(`^[a-zA-Z][\\-\\.0-9_${unicodeRegExp.source}]*$`).test(name)) {
- warn(
- 'Invalid component name: "' + name + '". Component names ' +
- 'should conform to valid custom element name in html5 specification.'
- )
- }
- 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)
- // Apply extends and mixins on the child options,
- // but only if it is a raw options object that isn't
- // the result of another mergeOptions call.
- // Only merged options has the _base property.
- if (!child._base) {
- if (child.extends) {
- parent = mergeOptions(parent, child.extends, 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
- }
|