options.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. /* @flow */
  2. import Vue from '../instance/index'
  3. import config from '../config'
  4. import { warn } from './debug'
  5. import { set } from '../observer/index'
  6. import {
  7. extend,
  8. isObject,
  9. isPlainObject,
  10. hasOwn,
  11. camelize,
  12. capitalize,
  13. isBuiltInTag
  14. } from 'shared/util'
  15. /**
  16. * Option overwriting strategies are functions that handle
  17. * how to merge a parent option value and a child option
  18. * value into the final value.
  19. */
  20. const strats = config.optionMergeStrategies
  21. /**
  22. * Options with restrictions
  23. */
  24. if (process.env.NODE_ENV !== 'production') {
  25. strats.el = strats.propsData = function (parent, child, vm, key) {
  26. if (!vm) {
  27. warn(
  28. `option "${key}" can only be used during instance ` +
  29. 'creation with the `new` keyword.'
  30. )
  31. }
  32. return defaultStrat(parent, child)
  33. }
  34. }
  35. /**
  36. * Helper that recursively merges two data objects together.
  37. */
  38. function mergeData (to: Object, from: ?Object): Object {
  39. let key, toVal, fromVal
  40. for (key in from) {
  41. toVal = to[key]
  42. fromVal = from[key]
  43. if (!hasOwn(to, key)) {
  44. set(to, key, fromVal)
  45. } else if (isObject(toVal) && isObject(fromVal)) {
  46. mergeData(toVal, fromVal)
  47. }
  48. }
  49. return to
  50. }
  51. /**
  52. * Data
  53. */
  54. strats.data = function (
  55. parentVal: any,
  56. childVal: any,
  57. vm?: Component
  58. ): ?Function {
  59. if (!vm) {
  60. // in a Vue.extend merge, both should be functions
  61. if (!childVal) {
  62. return parentVal
  63. }
  64. if (typeof childVal !== 'function') {
  65. process.env.NODE_ENV !== 'production' && warn(
  66. 'The "data" option should be a function ' +
  67. 'that returns a per-instance value in component ' +
  68. 'definitions.',
  69. vm
  70. )
  71. return parentVal
  72. }
  73. if (!parentVal) {
  74. return childVal
  75. }
  76. // when parentVal & childVal are both present,
  77. // we need to return a function that returns the
  78. // merged result of both functions... no need to
  79. // check if parentVal is a function here because
  80. // it has to be a function to pass previous merges.
  81. return function mergedDataFn () {
  82. return mergeData(
  83. childVal.call(this),
  84. parentVal.call(this)
  85. )
  86. }
  87. } else if (parentVal || childVal) {
  88. return function mergedInstanceDataFn () {
  89. // instance merge
  90. const instanceData = typeof childVal === 'function'
  91. ? childVal.call(vm)
  92. : childVal
  93. const defaultData = typeof parentVal === 'function'
  94. ? parentVal.call(vm)
  95. : undefined
  96. if (instanceData) {
  97. return mergeData(instanceData, defaultData)
  98. } else {
  99. return defaultData
  100. }
  101. }
  102. }
  103. }
  104. /**
  105. * Hooks and param attributes are merged as arrays.
  106. */
  107. function mergeHook (
  108. parentVal: ?Array<Function>,
  109. childVal: ?Function | ?Array<Function>
  110. ): ?Array<Function> {
  111. return childVal
  112. ? parentVal
  113. ? parentVal.concat(childVal)
  114. : Array.isArray(childVal)
  115. ? childVal
  116. : [childVal]
  117. : parentVal
  118. }
  119. config._lifecycleHooks.forEach(hook => {
  120. strats[hook] = mergeHook
  121. })
  122. /**
  123. * Assets
  124. *
  125. * When a vm is present (instance creation), we need to do
  126. * a three-way merge between constructor options, instance
  127. * options and parent options.
  128. */
  129. function mergeAssets (parentVal: ?Object, childVal: ?Object): Object {
  130. const res = Object.create(parentVal || null)
  131. return childVal
  132. ? extend(res, childVal)
  133. : res
  134. }
  135. config._assetTypes.forEach(function (type) {
  136. strats[type + 's'] = mergeAssets
  137. })
  138. /**
  139. * Watchers.
  140. *
  141. * Watchers hashes should not overwrite one
  142. * another, so we merge them as arrays.
  143. */
  144. strats.watch = function (parentVal: ?Object, childVal: ?Object): ?Object {
  145. /* istanbul ignore if */
  146. if (!childVal) return parentVal
  147. if (!parentVal) return childVal
  148. const ret = {}
  149. extend(ret, parentVal)
  150. for (const key in childVal) {
  151. let parent = ret[key]
  152. const child = childVal[key]
  153. if (parent && !Array.isArray(parent)) {
  154. parent = [parent]
  155. }
  156. ret[key] = parent
  157. ? parent.concat(child)
  158. : [child]
  159. }
  160. return ret
  161. }
  162. /**
  163. * Other object hashes.
  164. */
  165. strats.props =
  166. strats.methods =
  167. strats.computed = function (parentVal: ?Object, childVal: ?Object): ?Object {
  168. if (!childVal) return parentVal
  169. if (!parentVal) return childVal
  170. const ret = Object.create(null)
  171. extend(ret, parentVal)
  172. extend(ret, childVal)
  173. return ret
  174. }
  175. /**
  176. * Default strategy.
  177. */
  178. const defaultStrat = function (parentVal: any, childVal: any): any {
  179. return childVal === undefined
  180. ? parentVal
  181. : childVal
  182. }
  183. /**
  184. * Validate component names
  185. */
  186. function checkComponents (options: Object) {
  187. for (const key in options.components) {
  188. const lower = key.toLowerCase()
  189. if (isBuiltInTag(lower) || config.isReservedTag(lower)) {
  190. warn(
  191. 'Do not use built-in or reserved HTML elements as component ' +
  192. 'id: ' + key
  193. )
  194. }
  195. }
  196. }
  197. /**
  198. * Ensure all props option syntax are normalized into the
  199. * Object-based format.
  200. */
  201. function normalizeProps (options: Object) {
  202. const props = options.props
  203. if (!props) return
  204. const res = {}
  205. let i, val, name
  206. if (Array.isArray(props)) {
  207. i = props.length
  208. while (i--) {
  209. val = props[i]
  210. if (typeof val === 'string') {
  211. name = camelize(val)
  212. res[name] = { type: null }
  213. } else if (process.env.NODE_ENV !== 'production') {
  214. warn('props must be strings when using array syntax.')
  215. }
  216. }
  217. } else if (isPlainObject(props)) {
  218. for (const key in props) {
  219. val = props[key]
  220. name = camelize(key)
  221. res[name] = isPlainObject(val)
  222. ? val
  223. : { type: val }
  224. }
  225. }
  226. options.props = res
  227. }
  228. /**
  229. * Normalize raw function directives into object format.
  230. */
  231. function normalizeDirectives (options: Object) {
  232. const dirs = options.directives
  233. if (dirs) {
  234. for (const key in dirs) {
  235. const def = dirs[key]
  236. if (typeof def === 'function') {
  237. dirs[key] = { bind: def, update: def }
  238. }
  239. }
  240. }
  241. }
  242. /**
  243. * Merge two option objects into a new one.
  244. * Core utility used in both instantiation and inheritance.
  245. */
  246. export function mergeOptions (
  247. parent: Object,
  248. child: Object,
  249. vm?: Component
  250. ): Object {
  251. if (process.env.NODE_ENV !== 'production') {
  252. checkComponents(child)
  253. }
  254. normalizeProps(child)
  255. normalizeDirectives(child)
  256. const extendsFrom = child.extends
  257. if (extendsFrom) {
  258. parent = typeof extendsFrom === 'function'
  259. ? mergeOptions(parent, extendsFrom.options, vm)
  260. : mergeOptions(parent, extendsFrom, vm)
  261. }
  262. if (child.mixins) {
  263. for (let i = 0, l = child.mixins.length; i < l; i++) {
  264. let mixin = child.mixins[i]
  265. if (mixin.prototype instanceof Vue) {
  266. mixin = mixin.options
  267. }
  268. parent = mergeOptions(parent, mixin, vm)
  269. }
  270. }
  271. const options = {}
  272. let key
  273. for (key in parent) {
  274. mergeField(key)
  275. }
  276. for (key in child) {
  277. if (!hasOwn(parent, key)) {
  278. mergeField(key)
  279. }
  280. }
  281. function mergeField (key) {
  282. const strat = strats[key] || defaultStrat
  283. options[key] = strat(parent[key], child[key], vm, key)
  284. }
  285. return options
  286. }
  287. /**
  288. * Resolve an asset.
  289. * This function is used because child instances need access
  290. * to assets defined in its ancestor chain.
  291. */
  292. export function resolveAsset (
  293. options: Object,
  294. type: string,
  295. id: string,
  296. warnMissing?: boolean
  297. ): any {
  298. /* istanbul ignore if */
  299. if (typeof id !== 'string') {
  300. return
  301. }
  302. const assets = options[type]
  303. const res = assets[id] ||
  304. // camelCase ID
  305. assets[camelize(id)] ||
  306. // Pascal Case ID
  307. assets[capitalize(camelize(id))]
  308. if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
  309. warn(
  310. 'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
  311. options
  312. )
  313. }
  314. return res
  315. }