options.js 8.3 KB

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