1f2a4031f9b6fcde798bb5a5f467a15ab00d54710c181bee8666505a157c1f1685f9577c9ac125386de89e0686248671e0b61d11ccea7b83a7568cfb9203c3 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. /* @flow */
  2. import config from '../config'
  3. import { warn } from './debug'
  4. import { set } from '../observer/index'
  5. import { unicodeRegExp } from './lang'
  6. import { nativeWatch, hasSymbol } from './env'
  7. import {
  8. ASSET_TYPES,
  9. LIFECYCLE_HOOKS
  10. } from 'shared/constants'
  11. import {
  12. extend,
  13. hasOwn,
  14. camelize,
  15. toRawType,
  16. capitalize,
  17. isBuiltInTag,
  18. isPlainObject
  19. } from 'shared/util'
  20. /**
  21. * Option overwriting strategies are functions that handle
  22. * how to merge a parent option value and a child option
  23. * value into the final value.
  24. */
  25. const strats = config.optionMergeStrategies
  26. /**
  27. * Options with restrictions
  28. */
  29. if (process.env.NODE_ENV !== 'production') {
  30. strats.el = strats.propsData = function (parent, child, vm, key) {
  31. if (!vm) {
  32. warn(
  33. `option "${key}" can only be used during instance ` +
  34. 'creation with the `new` keyword.'
  35. )
  36. }
  37. return defaultStrat(parent, child)
  38. }
  39. }
  40. /**
  41. * Helper that recursively merges two data objects together.
  42. */
  43. function mergeData (to: Object, from: ?Object): Object {
  44. if (!from) return to
  45. let key, toVal, fromVal
  46. const keys = hasSymbol
  47. ? Reflect.ownKeys(from)
  48. : Object.keys(from)
  49. for (let i = 0; i < keys.length; i++) {
  50. key = keys[i]
  51. // in case the object is already observed...
  52. if (key === '__ob__') continue
  53. toVal = to[key]
  54. fromVal = from[key]
  55. if (!hasOwn(to, key)) {
  56. set(to, key, fromVal)
  57. } else if (
  58. toVal !== fromVal &&
  59. isPlainObject(toVal) &&
  60. isPlainObject(fromVal)
  61. ) {
  62. mergeData(toVal, fromVal)
  63. }
  64. }
  65. return to
  66. }
  67. /**
  68. * Data
  69. */
  70. export function mergeDataOrFn (
  71. parentVal: any,
  72. childVal: any,
  73. vm?: Component
  74. ): ?Function {
  75. if (!vm) {
  76. // in a Vue.extend merge, both should be functions
  77. if (!childVal) {
  78. return parentVal
  79. }
  80. if (!parentVal) {
  81. return childVal
  82. }
  83. // when parentVal & childVal are both present,
  84. // we need to return a function that returns the
  85. // merged result of both functions... no need to
  86. // check if parentVal is a function here because
  87. // it has to be a function to pass previous merges.
  88. return function mergedDataFn () {
  89. return mergeData(
  90. typeof childVal === 'function' ? childVal.call(this, this) : childVal,
  91. typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
  92. )
  93. }
  94. } else {
  95. return function mergedInstanceDataFn () {
  96. // instance merge
  97. const instanceData = typeof childVal === 'function'
  98. ? childVal.call(vm, vm)
  99. : childVal
  100. const defaultData = typeof parentVal === 'function'
  101. ? parentVal.call(vm, vm)
  102. : parentVal
  103. if (instanceData) {
  104. return mergeData(instanceData, defaultData)
  105. } else {
  106. return defaultData
  107. }
  108. }
  109. }
  110. }
  111. strats.data = function (
  112. parentVal: any,
  113. childVal: any,
  114. vm?: Component
  115. ): ?Function {
  116. if (!vm) {
  117. if (childVal && typeof childVal !== 'function') {
  118. process.env.NODE_ENV !== 'production' && warn(
  119. 'The "data" option should be a function ' +
  120. 'that returns a per-instance value in component ' +
  121. 'definitions.',
  122. vm
  123. )
  124. return parentVal
  125. }
  126. return mergeDataOrFn(parentVal, childVal)
  127. }
  128. return mergeDataOrFn(parentVal, childVal, vm)
  129. }
  130. /**
  131. * Hooks and props are merged as arrays.
  132. */
  133. function mergeHook (
  134. parentVal: ?Array<Function>,
  135. childVal: ?Function | ?Array<Function>
  136. ): ?Array<Function> {
  137. const res = childVal
  138. ? parentVal
  139. ? parentVal.concat(childVal)
  140. : Array.isArray(childVal)
  141. ? childVal
  142. : [childVal]
  143. : parentVal
  144. return res
  145. ? dedupeHooks(res)
  146. : res
  147. }
  148. function dedupeHooks (hooks) {
  149. const res = []
  150. for (let i = 0; i < hooks.length; i++) {
  151. if (res.indexOf(hooks[i]) === -1) {
  152. res.push(hooks[i])
  153. }
  154. }
  155. return res
  156. }
  157. LIFECYCLE_HOOKS.forEach(hook => {
  158. strats[hook] = mergeHook
  159. })
  160. /**
  161. * Assets
  162. *
  163. * When a vm is present (instance creation), we need to do
  164. * a three-way merge between constructor options, instance
  165. * options and parent options.
  166. */
  167. function mergeAssets (
  168. parentVal: ?Object,
  169. childVal: ?Object,
  170. vm?: Component,
  171. key: string
  172. ): Object {
  173. const res = Object.create(parentVal || null)
  174. if (childVal) {
  175. process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm)
  176. return extend(res, childVal)
  177. } else {
  178. return res
  179. }
  180. }
  181. ASSET_TYPES.forEach(function (type) {
  182. strats[type + 's'] = mergeAssets
  183. })
  184. /**
  185. * Watchers.
  186. *
  187. * Watchers hashes should not overwrite one
  188. * another, so we merge them as arrays.
  189. */
  190. strats.watch = function (
  191. parentVal: ?Object,
  192. childVal: ?Object,
  193. vm?: Component,
  194. key: string
  195. ): ?Object {
  196. // work around Firefox's Object.prototype.watch...
  197. if (parentVal === nativeWatch) parentVal = undefined
  198. if (childVal === nativeWatch) childVal = undefined
  199. /* istanbul ignore if */
  200. if (!childVal) return Object.create(parentVal || null)
  201. if (process.env.NODE_ENV !== 'production') {
  202. assertObjectType(key, childVal, vm)
  203. }
  204. if (!parentVal) return childVal
  205. const ret = {}
  206. extend(ret, parentVal)
  207. for (const key in childVal) {
  208. let parent = ret[key]
  209. const child = childVal[key]
  210. if (parent && !Array.isArray(parent)) {
  211. parent = [parent]
  212. }
  213. ret[key] = parent
  214. ? parent.concat(child)
  215. : Array.isArray(child) ? child : [child]
  216. }
  217. return ret
  218. }
  219. /**
  220. * Other object hashes.
  221. */
  222. strats.props =
  223. strats.methods =
  224. strats.inject =
  225. strats.computed = function (
  226. parentVal: ?Object,
  227. childVal: ?Object,
  228. vm?: Component,
  229. key: string
  230. ): ?Object {
  231. if (childVal && process.env.NODE_ENV !== 'production') {
  232. assertObjectType(key, childVal, vm)
  233. }
  234. if (!parentVal) return childVal
  235. const ret = Object.create(null)
  236. extend(ret, parentVal)
  237. if (childVal) extend(ret, childVal)
  238. return ret
  239. }
  240. strats.provide = mergeDataOrFn
  241. /**
  242. * Default strategy.
  243. */
  244. const defaultStrat = function (parentVal: any, childVal: any): any {
  245. return childVal === undefined
  246. ? parentVal
  247. : childVal
  248. }
  249. /**
  250. * Validate component names
  251. */
  252. function checkComponents (options: Object) {
  253. for (const key in options.components) {
  254. validateComponentName(key)
  255. }
  256. }
  257. export function validateComponentName (name: string) {
  258. if (!new RegExp(`^[a-zA-Z][\\-\\.0-9_${unicodeRegExp.source}]*$`).test(name)) {
  259. warn(
  260. 'Invalid component name: "' + name + '". Component names ' +
  261. 'should conform to valid custom element name in html5 specification.'
  262. )
  263. }
  264. if (isBuiltInTag(name) || config.isReservedTag(name)) {
  265. warn(
  266. 'Do not use built-in or reserved HTML elements as component ' +
  267. 'id: ' + name
  268. )
  269. }
  270. }
  271. /**
  272. * Ensure all props option syntax are normalized into the
  273. * Object-based format.
  274. */
  275. function normalizeProps (options: Object, vm: ?Component) {
  276. const props = options.props
  277. if (!props) return
  278. const res = {}
  279. let i, val, name
  280. if (Array.isArray(props)) {
  281. i = props.length
  282. while (i--) {
  283. val = props[i]
  284. if (typeof val === 'string') {
  285. name = camelize(val)
  286. res[name] = { type: null }
  287. } else if (process.env.NODE_ENV !== 'production') {
  288. warn('props must be strings when using array syntax.')
  289. }
  290. }
  291. } else if (isPlainObject(props)) {
  292. for (const key in props) {
  293. val = props[key]
  294. name = camelize(key)
  295. res[name] = isPlainObject(val)
  296. ? val
  297. : { type: val }
  298. }
  299. } else if (process.env.NODE_ENV !== 'production') {
  300. warn(
  301. `Invalid value for option "props": expected an Array or an Object, ` +
  302. `but got ${toRawType(props)}.`,
  303. vm
  304. )
  305. }
  306. options.props = res
  307. }
  308. /**
  309. * Normalize all injections into Object-based format
  310. */
  311. function normalizeInject (options: Object, vm: ?Component) {
  312. const inject = options.inject
  313. if (!inject) return
  314. const normalized = options.inject = {}
  315. if (Array.isArray(inject)) {
  316. for (let i = 0; i < inject.length; i++) {
  317. normalized[inject[i]] = { from: inject[i] }
  318. }
  319. } else if (isPlainObject(inject)) {
  320. for (const key in inject) {
  321. const val = inject[key]
  322. normalized[key] = isPlainObject(val)
  323. ? extend({ from: key }, val)
  324. : { from: val }
  325. }
  326. } else if (process.env.NODE_ENV !== 'production') {
  327. warn(
  328. `Invalid value for option "inject": expected an Array or an Object, ` +
  329. `but got ${toRawType(inject)}.`,
  330. vm
  331. )
  332. }
  333. }
  334. /**
  335. * Normalize raw function directives into object format.
  336. */
  337. function normalizeDirectives (options: Object) {
  338. const dirs = options.directives
  339. if (dirs) {
  340. for (const key in dirs) {
  341. const def = dirs[key]
  342. if (typeof def === 'function') {
  343. dirs[key] = { bind: def, update: def }
  344. }
  345. }
  346. }
  347. }
  348. function assertObjectType (name: string, value: any, vm: ?Component) {
  349. if (!isPlainObject(value)) {
  350. warn(
  351. `Invalid value for option "${name}": expected an Object, ` +
  352. `but got ${toRawType(value)}.`,
  353. vm
  354. )
  355. }
  356. }
  357. /**
  358. * Merge two option objects into a new one.
  359. * Core utility used in both instantiation and inheritance.
  360. */
  361. export function mergeOptions (
  362. parent: Object,
  363. child: Object,
  364. vm?: Component
  365. ): Object {
  366. if (process.env.NODE_ENV !== 'production') {
  367. checkComponents(child)
  368. }
  369. if (typeof child === 'function') {
  370. child = child.options
  371. }
  372. normalizeProps(child, vm)
  373. normalizeInject(child, vm)
  374. normalizeDirectives(child)
  375. // Apply extends and mixins on the child options,
  376. // but only if it is a raw options object that isn't
  377. // the result of another mergeOptions call.
  378. // Only merged options has the _base property.
  379. if (!child._base) {
  380. if (child.extends) {
  381. parent = mergeOptions(parent, child.extends, vm)
  382. }
  383. if (child.mixins) {
  384. for (let i = 0, l = child.mixins.length; i < l; i++) {
  385. parent = mergeOptions(parent, child.mixins[i], vm)
  386. }
  387. }
  388. }
  389. const options = {}
  390. let key
  391. for (key in parent) {
  392. mergeField(key)
  393. }
  394. for (key in child) {
  395. if (!hasOwn(parent, key)) {
  396. mergeField(key)
  397. }
  398. }
  399. function mergeField (key) {
  400. const strat = strats[key] || defaultStrat
  401. options[key] = strat(parent[key], child[key], vm, key)
  402. }
  403. return options
  404. }
  405. /**
  406. * Resolve an asset.
  407. * This function is used because child instances need access
  408. * to assets defined in its ancestor chain.
  409. */
  410. export function resolveAsset (
  411. options: Object,
  412. type: string,
  413. id: string,
  414. warnMissing?: boolean
  415. ): any {
  416. /* istanbul ignore if */
  417. if (typeof id !== 'string') {
  418. return
  419. }
  420. const assets = options[type]
  421. // check local registration variations first
  422. if (hasOwn(assets, id)) return assets[id]
  423. const camelizedId = camelize(id)
  424. if (hasOwn(assets, camelizedId)) return assets[camelizedId]
  425. const PascalCaseId = capitalize(camelizedId)
  426. if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]
  427. // fallback to prototype chain
  428. const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
  429. if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
  430. warn(
  431. 'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
  432. options
  433. )
  434. }
  435. return res
  436. }