options.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. import Vue from '../instance/vue'
  2. import config from '../config'
  3. import {
  4. extend,
  5. set,
  6. isObject,
  7. isArray,
  8. isPlainObject,
  9. hasOwn,
  10. camelize,
  11. hyphenate
  12. } from './lang'
  13. import { warn } from './debug'
  14. import { commonTagRE, reservedTagRE } from './component'
  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. * All strategy functions follow the same signature:
  21. *
  22. * @param {*} parentVal
  23. * @param {*} childVal
  24. * @param {Vue} [vm]
  25. */
  26. var strats = config.optionMergeStrategies = Object.create(null)
  27. /**
  28. * Helper that recursively merges two data objects together.
  29. */
  30. function mergeData (to, from) {
  31. var key, toVal, fromVal
  32. for (key in from) {
  33. toVal = to[key]
  34. fromVal = from[key]
  35. if (!hasOwn(to, key)) {
  36. set(to, key, fromVal)
  37. } else if (isObject(toVal) && isObject(fromVal)) {
  38. mergeData(toVal, fromVal)
  39. }
  40. }
  41. return to
  42. }
  43. /**
  44. * Data
  45. */
  46. strats.data = function (parentVal, childVal, vm) {
  47. if (!vm) {
  48. // in a Vue.extend merge, both should be functions
  49. if (!childVal) {
  50. return parentVal
  51. }
  52. if (typeof childVal !== 'function') {
  53. process.env.NODE_ENV !== 'production' && warn(
  54. 'The "data" option should be a function ' +
  55. 'that returns a per-instance value in component ' +
  56. 'definitions.',
  57. vm
  58. )
  59. return parentVal
  60. }
  61. if (!parentVal) {
  62. return childVal
  63. }
  64. // when parentVal & childVal are both present,
  65. // we need to return a function that returns the
  66. // merged result of both functions... no need to
  67. // check if parentVal is a function here because
  68. // it has to be a function to pass previous merges.
  69. return function mergedDataFn () {
  70. return mergeData(
  71. childVal.call(this),
  72. parentVal.call(this)
  73. )
  74. }
  75. } else if (parentVal || childVal) {
  76. return function mergedInstanceDataFn () {
  77. // instance merge
  78. var instanceData = typeof childVal === 'function'
  79. ? childVal.call(vm)
  80. : childVal
  81. var defaultData = typeof parentVal === 'function'
  82. ? parentVal.call(vm)
  83. : undefined
  84. if (instanceData) {
  85. return mergeData(instanceData, defaultData)
  86. } else {
  87. return defaultData
  88. }
  89. }
  90. }
  91. }
  92. /**
  93. * El
  94. */
  95. strats.el = function (parentVal, childVal, vm) {
  96. if (!vm && childVal && typeof childVal !== 'function') {
  97. process.env.NODE_ENV !== 'production' && warn(
  98. 'The "el" option should be a function ' +
  99. 'that returns a per-instance value in component ' +
  100. 'definitions.',
  101. vm
  102. )
  103. return
  104. }
  105. var ret = childVal || parentVal
  106. // invoke the element factory if this is instance merge
  107. return vm && typeof ret === 'function'
  108. ? ret.call(vm)
  109. : ret
  110. }
  111. /**
  112. * Hooks and param attributes are merged as arrays.
  113. */
  114. strats.init =
  115. strats.created =
  116. strats.ready =
  117. strats.attached =
  118. strats.detached =
  119. strats.beforeCompile =
  120. strats.compiled =
  121. strats.beforeDestroy =
  122. strats.destroyed =
  123. strats.activate = function (parentVal, childVal) {
  124. return childVal
  125. ? parentVal
  126. ? parentVal.concat(childVal)
  127. : isArray(childVal)
  128. ? childVal
  129. : [childVal]
  130. : parentVal
  131. }
  132. /**
  133. * Assets
  134. *
  135. * When a vm is present (instance creation), we need to do
  136. * a three-way merge between constructor options, instance
  137. * options and parent options.
  138. */
  139. function mergeAssets (parentVal, childVal) {
  140. var res = Object.create(parentVal)
  141. return childVal
  142. ? extend(res, guardArrayAssets(childVal))
  143. : res
  144. }
  145. config._assetTypes.forEach(function (type) {
  146. strats[type + 's'] = mergeAssets
  147. })
  148. /**
  149. * Events & Watchers.
  150. *
  151. * Events & watchers hashes should not overwrite one
  152. * another, so we merge them as arrays.
  153. */
  154. strats.watch =
  155. strats.events = function (parentVal, childVal) {
  156. if (!childVal) return parentVal
  157. if (!parentVal) return childVal
  158. var ret = {}
  159. extend(ret, parentVal)
  160. for (var key in childVal) {
  161. var parent = ret[key]
  162. var child = childVal[key]
  163. if (parent && !isArray(parent)) {
  164. parent = [parent]
  165. }
  166. ret[key] = parent
  167. ? parent.concat(child)
  168. : [child]
  169. }
  170. return ret
  171. }
  172. /**
  173. * Other object hashes.
  174. */
  175. strats.props =
  176. strats.methods =
  177. strats.computed = function (parentVal, childVal) {
  178. if (!childVal) return parentVal
  179. if (!parentVal) return childVal
  180. var ret = Object.create(null)
  181. extend(ret, parentVal)
  182. extend(ret, childVal)
  183. return ret
  184. }
  185. /**
  186. * Default strategy.
  187. */
  188. var defaultStrat = function (parentVal, childVal) {
  189. return childVal === undefined
  190. ? parentVal
  191. : childVal
  192. }
  193. /**
  194. * Make sure component options get converted to actual
  195. * constructors.
  196. *
  197. * @param {Object} options
  198. */
  199. function guardComponents (options) {
  200. if (options.components) {
  201. var components = options.components =
  202. guardArrayAssets(options.components)
  203. var ids = Object.keys(components)
  204. var def
  205. if (process.env.NODE_ENV !== 'production') {
  206. var map = options._componentNameMap = {}
  207. }
  208. for (var i = 0, l = ids.length; i < l; i++) {
  209. var key = ids[i]
  210. if (commonTagRE.test(key) || reservedTagRE.test(key)) {
  211. process.env.NODE_ENV !== 'production' && warn(
  212. 'Do not use built-in or reserved HTML elements as component ' +
  213. 'id: ' + key
  214. )
  215. continue
  216. }
  217. // record a all lowercase <-> kebab-case mapping for
  218. // possible custom element case error warning
  219. if (process.env.NODE_ENV !== 'production') {
  220. map[key.replace(/-/g, '').toLowerCase()] = hyphenate(key)
  221. }
  222. def = components[key]
  223. if (isPlainObject(def)) {
  224. components[key] = Vue.extend(def)
  225. }
  226. }
  227. }
  228. }
  229. /**
  230. * Ensure all props option syntax are normalized into the
  231. * Object-based format.
  232. *
  233. * @param {Object} options
  234. */
  235. function guardProps (options) {
  236. var props = options.props
  237. var i, val
  238. if (isArray(props)) {
  239. options.props = {}
  240. i = props.length
  241. while (i--) {
  242. val = props[i]
  243. if (typeof val === 'string') {
  244. options.props[val] = null
  245. } else if (val.name) {
  246. options.props[val.name] = val
  247. }
  248. }
  249. } else if (isPlainObject(props)) {
  250. var keys = Object.keys(props)
  251. i = keys.length
  252. while (i--) {
  253. val = props[keys[i]]
  254. if (typeof val === 'function') {
  255. props[keys[i]] = { type: val }
  256. }
  257. }
  258. }
  259. }
  260. /**
  261. * Guard an Array-format assets option and converted it
  262. * into the key-value Object format.
  263. *
  264. * @param {Object|Array} assets
  265. * @return {Object}
  266. */
  267. function guardArrayAssets (assets) {
  268. if (isArray(assets)) {
  269. var res = {}
  270. var i = assets.length
  271. var asset
  272. while (i--) {
  273. asset = assets[i]
  274. var id = typeof asset === 'function'
  275. ? ((asset.options && asset.options.name) || asset.id)
  276. : (asset.name || asset.id)
  277. if (!id) {
  278. process.env.NODE_ENV !== 'production' && warn(
  279. 'Array-syntax assets must provide a "name" or "id" field.'
  280. )
  281. } else {
  282. res[id] = asset
  283. }
  284. }
  285. return res
  286. }
  287. return assets
  288. }
  289. /**
  290. * Merge two option objects into a new one.
  291. * Core utility used in both instantiation and inheritance.
  292. *
  293. * @param {Object} parent
  294. * @param {Object} child
  295. * @param {Vue} [vm] - if vm is present, indicates this is
  296. * an instantiation merge.
  297. */
  298. export function mergeOptions (parent, child, vm) {
  299. guardComponents(child)
  300. guardProps(child)
  301. if (process.env.NODE_ENV !== 'production') {
  302. if (child.propsData && !vm) {
  303. warn('propsData can only be used as an instantiation option.')
  304. }
  305. }
  306. var options = {}
  307. var key
  308. if (child.extends) {
  309. parent = typeof child.extends === 'function'
  310. ? mergeOptions(parent, child.extends.options, vm)
  311. : mergeOptions(parent, child.extends, vm)
  312. }
  313. if (child.mixins) {
  314. for (var i = 0, l = child.mixins.length; i < l; i++) {
  315. parent = mergeOptions(parent, child.mixins[i], vm)
  316. }
  317. }
  318. for (key in parent) {
  319. mergeField(key)
  320. }
  321. for (key in child) {
  322. if (!hasOwn(parent, key)) {
  323. mergeField(key)
  324. }
  325. }
  326. function mergeField (key) {
  327. var strat = strats[key] || defaultStrat
  328. options[key] = strat(parent[key], child[key], vm, key)
  329. }
  330. return options
  331. }
  332. /**
  333. * Resolve an asset.
  334. * This function is used because child instances need access
  335. * to assets defined in its ancestor chain.
  336. *
  337. * @param {Object} options
  338. * @param {String} type
  339. * @param {String} id
  340. * @param {Boolean} warnMissing
  341. * @return {Object|Function}
  342. */
  343. export function resolveAsset (options, type, id, warnMissing) {
  344. /* istanbul ignore if */
  345. if (typeof id !== 'string') {
  346. return
  347. }
  348. var assets = options[type]
  349. var camelizedId
  350. var res = assets[id] ||
  351. // camelCase ID
  352. assets[camelizedId = camelize(id)] ||
  353. // Pascal Case ID
  354. assets[camelizedId.charAt(0).toUpperCase() + camelizedId.slice(1)]
  355. if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
  356. warn(
  357. 'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
  358. options
  359. )
  360. }
  361. return res
  362. }