src/core/util/options.ts TYPESCRIPT 490 lines View on github.com → Search inside
1import config from '../config'2import { warn } from './debug'3import { set } from '../observer/index'4import { unicodeRegExp } from './lang'5import { nativeWatch, hasSymbol } from './env'6import { isArray, isFunction } from 'shared/util'78import { ASSET_TYPES, LIFECYCLE_HOOKS } from 'shared/constants'910import {11  extend,12  hasOwn,13  camelize,14  toRawType,15  capitalize,16  isBuiltInTag,17  isPlainObject18} from 'shared/util'19import type { Component } from 'types/component'20import type { ComponentOptions } from 'types/options'2122/**23 * Option overwriting strategies are functions that handle24 * how to merge a parent option value and a child option25 * value into the final value.26 */27const strats = config.optionMergeStrategies2829/**30 * Options with restrictions31 */32if (__DEV__) {33  strats.el = strats.propsData = function (34    parent: any,35    child: any,36    vm: any,37    key: any38  ) {39    if (!vm) {40      warn(41        `option "${key}" can only be used during instance ` +42          'creation with the `new` keyword.'43      )44    }45    return defaultStrat(parent, child)46  }47}4849/**50 * Helper that recursively merges two data objects together.51 */52function mergeData(53  to: Record<string | symbol, any>,54  from: Record<string | symbol, any> | null,55  recursive = true56): Record<PropertyKey, any> {57  if (!from) return to58  let key, toVal, fromVal5960  const keys = hasSymbol61    ? (Reflect.ownKeys(from) as string[])62    : Object.keys(from)6364  for (let i = 0; i < keys.length; i++) {65    key = keys[i]66    // in case the object is already observed...67    if (key === '__ob__') continue68    toVal = to[key]69    fromVal = from[key]70    if (!recursive || !hasOwn(to, key)) {71      set(to, key, fromVal)72    } else if (73      toVal !== fromVal &&74      isPlainObject(toVal) &&75      isPlainObject(fromVal)76    ) {77      mergeData(toVal, fromVal)78    }79  }80  return to81}8283/**84 * Data85 */86export function mergeDataOrFn(87  parentVal: any,88  childVal: any,89  vm?: Component90): Function | null {91  if (!vm) {92    // in a Vue.extend merge, both should be functions93    if (!childVal) {94      return parentVal95    }96    if (!parentVal) {97      return childVal98    }99    // when parentVal & childVal are both present,100    // we need to return a function that returns the101    // merged result of both functions... no need to102    // check if parentVal is a function here because103    // it has to be a function to pass previous merges.104    return function mergedDataFn() {105      return mergeData(106        isFunction(childVal) ? childVal.call(this, this) : childVal,107        isFunction(parentVal) ? parentVal.call(this, this) : parentVal108      )109    }110  } else {111    return function mergedInstanceDataFn() {112      // instance merge113      const instanceData = isFunction(childVal)114        ? childVal.call(vm, vm)115        : childVal116      const defaultData = isFunction(parentVal)117        ? parentVal.call(vm, vm)118        : parentVal119      if (instanceData) {120        return mergeData(instanceData, defaultData)121      } else {122        return defaultData123      }124    }125  }126}127128strats.data = function (129  parentVal: any,130  childVal: any,131  vm?: Component132): Function | null {133  if (!vm) {134    if (childVal && typeof childVal !== 'function') {135      __DEV__ &&136        warn(137          'The "data" option should be a function ' +138            'that returns a per-instance value in component ' +139            'definitions.',140          vm141        )142143      return parentVal144    }145    return mergeDataOrFn(parentVal, childVal)146  }147148  return mergeDataOrFn(parentVal, childVal, vm)149}150151/**152 * Hooks and props are merged as arrays.153 */154export function mergeLifecycleHook(155  parentVal: Array<Function> | null,156  childVal: Function | Array<Function> | null157): Array<Function> | null {158  const res = childVal159    ? parentVal160      ? parentVal.concat(childVal)161      : isArray(childVal)162      ? childVal163      : [childVal]164    : parentVal165  return res ? dedupeHooks(res) : res166}167168function dedupeHooks(hooks: any) {169  const res: Array<any> = []170  for (let i = 0; i < hooks.length; i++) {171    if (res.indexOf(hooks[i]) === -1) {172      res.push(hooks[i])173    }174  }175  return res176}177178LIFECYCLE_HOOKS.forEach(hook => {179  strats[hook] = mergeLifecycleHook180})181182/**183 * Assets184 *185 * When a vm is present (instance creation), we need to do186 * a three-way merge between constructor options, instance187 * options and parent options.188 */189function mergeAssets(190  parentVal: Object | null,191  childVal: Object | null,192  vm: Component | null,193  key: string194): Object {195  const res = Object.create(parentVal || null)196  if (childVal) {197    __DEV__ && assertObjectType(key, childVal, vm)198    return extend(res, childVal)199  } else {200    return res201  }202}203204ASSET_TYPES.forEach(function (type) {205  strats[type + 's'] = mergeAssets206})207208/**209 * Watchers.210 *211 * Watchers hashes should not overwrite one212 * another, so we merge them as arrays.213 */214strats.watch = function (215  parentVal: Record<string, any> | null,216  childVal: Record<string, any> | null,217  vm: Component | null,218  key: string219): Object | null {220  // work around Firefox's Object.prototype.watch...221  //@ts-expect-error work around222  if (parentVal === nativeWatch) parentVal = undefined223  //@ts-expect-error work around224  if (childVal === nativeWatch) childVal = undefined225  /* istanbul ignore if */226  if (!childVal) return Object.create(parentVal || null)227  if (__DEV__) {228    assertObjectType(key, childVal, vm)229  }230  if (!parentVal) return childVal231  const ret: Record<string, any> = {}232  extend(ret, parentVal)233  for (const key in childVal) {234    let parent = ret[key]235    const child = childVal[key]236    if (parent && !isArray(parent)) {237      parent = [parent]238    }239    ret[key] = parent ? parent.concat(child) : isArray(child) ? child : [child]240  }241  return ret242}243244/**245 * Other object hashes.246 */247strats.props =248  strats.methods =249  strats.inject =250  strats.computed =251    function (252      parentVal: Object | null,253      childVal: Object | null,254      vm: Component | null,255      key: string256    ): Object | null {257      if (childVal && __DEV__) {258        assertObjectType(key, childVal, vm)259      }260      if (!parentVal) return childVal261      const ret = Object.create(null)262      extend(ret, parentVal)263      if (childVal) extend(ret, childVal)264      return ret265    }266267strats.provide = function (parentVal: Object | null, childVal: Object | null) {268  if (!parentVal) return childVal269  return function () {270    const ret = Object.create(null)271    mergeData(ret, isFunction(parentVal) ? parentVal.call(this) : parentVal)272    if (childVal) {273      mergeData(274        ret,275        isFunction(childVal) ? childVal.call(this) : childVal,276        false // non-recursive277      )278    }279    return ret280  }281}282283/**284 * Default strategy.285 */286const defaultStrat = function (parentVal: any, childVal: any): any {287  return childVal === undefined ? parentVal : childVal288}289290/**291 * Validate component names292 */293function checkComponents(options: Record<string, any>) {294  for (const key in options.components) {295    validateComponentName(key)296  }297}298299export function validateComponentName(name: string) {300  if (301    !new RegExp(`^[a-zA-Z][\\-\\.0-9_${unicodeRegExp.source}]*$`).test(name)302  ) {303    warn(304      'Invalid component name: "' +305        name +306        '". Component names ' +307        'should conform to valid custom element name in html5 specification.'308    )309  }310  if (isBuiltInTag(name) || config.isReservedTag(name)) {311    warn(312      'Do not use built-in or reserved HTML elements as component ' +313        'id: ' +314        name315    )316  }317}318319/**320 * Ensure all props option syntax are normalized into the321 * Object-based format.322 */323function normalizeProps(options: Record<string, any>, vm?: Component | null) {324  const props = options.props325  if (!props) return326  const res: Record<string, any> = {}327  let i, val, name328  if (isArray(props)) {329    i = props.length330    while (i--) {331      val = props[i]332      if (typeof val === 'string') {333        name = camelize(val)334        res[name] = { type: null }335      } else if (__DEV__) {336        warn('props must be strings when using array syntax.')337      }338    }339  } else if (isPlainObject(props)) {340    for (const key in props) {341      val = props[key]342      name = camelize(key)343      res[name] = isPlainObject(val) ? val : { type: val }344    }345  } else if (__DEV__) {346    warn(347      `Invalid value for option "props": expected an Array or an Object, ` +348        `but got ${toRawType(props)}.`,349      vm350    )351  }352  options.props = res353}354355/**356 * Normalize all injections into Object-based format357 */358function normalizeInject(options: Record<string, any>, vm?: Component | null) {359  const inject = options.inject360  if (!inject) return361  const normalized: Record<string, any> = (options.inject = {})362  if (isArray(inject)) {363    for (let i = 0; i < inject.length; i++) {364      normalized[inject[i]] = { from: inject[i] }365    }366  } else if (isPlainObject(inject)) {367    for (const key in inject) {368      const val = inject[key]369      normalized[key] = isPlainObject(val)370        ? extend({ from: key }, val)371        : { from: val }372    }373  } else if (__DEV__) {374    warn(375      `Invalid value for option "inject": expected an Array or an Object, ` +376        `but got ${toRawType(inject)}.`,377      vm378    )379  }380}381382/**383 * Normalize raw function directives into object format.384 */385function normalizeDirectives(options: Record<string, any>) {386  const dirs = options.directives387  if (dirs) {388    for (const key in dirs) {389      const def = dirs[key]390      if (isFunction(def)) {391        dirs[key] = { bind: def, update: def }392      }393    }394  }395}396397function assertObjectType(name: string, value: any, vm: Component | null) {398  if (!isPlainObject(value)) {399    warn(400      `Invalid value for option "${name}": expected an Object, ` +401        `but got ${toRawType(value)}.`,402      vm403    )404  }405}406407/**408 * Merge two option objects into a new one.409 * Core utility used in both instantiation and inheritance.410 */411export function mergeOptions(412  parent: Record<string, any>,413  child: Record<string, any>,414  vm?: Component | null415): ComponentOptions {416  if (__DEV__) {417    checkComponents(child)418  }419420  if (isFunction(child)) {421    // @ts-expect-error422    child = child.options423  }424425  normalizeProps(child, vm)426  normalizeInject(child, vm)427  normalizeDirectives(child)428429  // Apply extends and mixins on the child options,430  // but only if it is a raw options object that isn't431  // the result of another mergeOptions call.432  // Only merged options has the _base property.433  if (!child._base) {434    if (child.extends) {435      parent = mergeOptions(parent, child.extends, vm)436    }437    if (child.mixins) {438      for (let i = 0, l = child.mixins.length; i < l; i++) {439        parent = mergeOptions(parent, child.mixins[i], vm)440      }441    }442  }443444  const options: ComponentOptions = {} as any445  let key446  for (key in parent) {447    mergeField(key)448  }449  for (key in child) {450    if (!hasOwn(parent, key)) {451      mergeField(key)452    }453  }454  function mergeField(key: any) {455    const strat = strats[key] || defaultStrat456    options[key] = strat(parent[key], child[key], vm, key)457  }458  return options459}460461/**462 * Resolve an asset.463 * This function is used because child instances need access464 * to assets defined in its ancestor chain.465 */466export function resolveAsset(467  options: Record<string, any>,468  type: string,469  id: string,470  warnMissing?: boolean471): any {472  /* istanbul ignore if */473  if (typeof id !== 'string') {474    return475  }476  const assets = options[type]477  // check local registration variations first478  if (hasOwn(assets, id)) return assets[id]479  const camelizedId = camelize(id)480  if (hasOwn(assets, camelizedId)) return assets[camelizedId]481  const PascalCaseId = capitalize(camelizedId)482  if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]483  // fallback to prototype chain484  const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]485  if (__DEV__ && warnMissing && !res) {486    warn('Failed to resolve ' + type.slice(0, -1) + ': ' + id)487  }488  return res489}

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.