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