I have a small transparent front end, if there is any wrong or insufficient understanding of VUE, please kindly understand and correct, thank you for your support. The corresponding vUE source code for this article is 2.6.0-release version, happy to read.

1. Vue engineering structure

This section has a lot of operations that just mount an API, with a partially parsed, partially skipped strategy, which will be bulk up in the next series

1.1 Pictures show the complete project catalog

1.2 Here are the files and directories we pay more attention to

| - vue2.6.0 - release | - flow / / about flow file directory It abandoned the poor | - scripts / / about the build script file directory | - SRC | - | compiler / / compile module - codegen / / Code generation | - directives / / instruction v - bind v - model v - on | - the parser / / ast syntax tree generation part of the core module | | - core / / components / / built-in component KeepAlive | - global - API / / the extend, assets, mixins, use), the observer, Util | - the instance / / render related and hook mount vue constructor defined life cycle Prototype chain instance methods on mount | - the realization of the observer / / response type | - util / / kit is mainly the debug, lang, Next-tick, options(merge strategy), props(props), Env running environment sniffer | - vdom / / virtual dom implementation | - platforms / / platform related web | | weex | - server / / server to render a single file to compile | - SFC / / | - Shared for js file / / hooks clear | - based toolkit and life cycle test / / test parts | - typesCopy the code

1.3 the config file

Various types of the output file configuration file, about the UMD, CommonJS, AMD can refer to this article You can also view the AMD, UMD, CMD, CommonJS, ES6module


const builds = {
  // Runtime+compiler CommonJS build (CommonJS)
  // This is the type of CJS nodeJS environment I will learn this time
  'web-full-cjs-dev': {
    entry: resolve('web/entry-runtime-with-compiler.js'), // The corresponding entry file
    dest: resolve('dist/vue.common.dev.js'),
    format: 'cjs'.env: 'development'.alias: { he: './entity-decoder' },
    banner
  },
  'web-full-cjs-prod': {
    entry: resolve('web/entry-runtime-with-compiler.js'), // The corresponding entry file
    dest: resolve('dist/vue.common.prod.js'),
    format: 'cjs'.env: 'production'.alias: { he: './entity-decoder' },
    banner
  }
}

Copy the code

1.3.1 Variable substitution during packaging


  // Replace the variable value of the same name in the package, such as version is dynamic and specified by us at packaging time
  // Get the version number in package.json, replacing vue.version = version in the code
  // Switch class and tag class

  // It is found after startup
  // 1. __WEEX__   
  // 2. __WEEX_VERSION__   
  // 3. __VERSION__   
  // 4. Process.env. NEW_SLOT_SYNTAX // Whether to use new slot syntax
  // 5.process.env.vbind_prop_dictate // Whether fast binding syntax is enabled
  // 6. process.env.node_env // Dev =development prod=production removes unnecessary alarms and prompts
  // built-in vars
  const vars = {
    __WEEX__:!!!!! opts.weex,__WEEX_VERSION__: weexVersion,
    __VERSION__: version
  }

Copy the code

1.3.2 Alias Settings


// Import... from 'core'
module.exports = {
  vue: resolve('src/platforms/web/entry-runtime-with-compiler'), // Vue full version entry
  compiler: resolve('src/compiler'), // Compiler entry
  core: resolve('src/core'), // Vue core file
  shared: resolve('src/shared'), / / toolkit
  web: resolve('src/platforms/web'), // Web platform entry
  weex: resolve('src/platforms/weex'), // WeeX platform entry
  server: resolve('src/server'), / / SSR entrance
  entries: resolve('src/entries'), //
  sfc: resolve('src/sfc') // 
}

Copy the code

2. Vue

The following uses occur when using VUE


Vue.mixins({})

new Vue({
  created () {
    // this}})Copy the code

So there must be a place to define Vue () {}, and we can start looking for Vue from the entry file in config


=> scripts/config web/entry-runtime-with-compiler      // Package entry web/entry-runtime-with-compiler
=> src/platforms/web/entry-runtime-with-compiler.js    // import Vue from './runtime/index'
=> src/platforms/web/runtime/index.js                  // import Vue from 'core/index'
=> src/core/index                                      // import Vue from './instance/index'
=> src/core/instance/index                             // function Vue

Copy the code

2.1 the Vue statement

The Vue constructor declares the definition


function Vue (options) {
  // In the test or development environment check if the instance is generated in the form of a new Vue, otherwise the alarm is generated because all subsequent operations are performed around the Vue instance
  / / new Vue ✔ ()
  / / the Vue (x)
  if(process.env.NODE_ENV ! = ='production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')}this._init(options) // Instance execution methods we will learn later
}

Copy the code

Prototype API & Vue API

Vue’s global API wrapper is divided into instance prototype object mount and constructor API mount

2.2.1 initMixin

// The initialization method mounts vue.prototype. _init =function () {}
Copy the code

2.2.2 stateMixin

Data, props Data agent Settings

  /** * Data hijacking this is also the core of Vue implementation principles. This is used for data proxy * All Vue instances such as this. XXX access data is called this._data.xxx * All Vue instances such as this. XXX are accessing this._props. XXX * hijack the data set to alert the operation to which this
  const dataDef = {}
  dataDef.get = function () { return this._data } 
  const propsDef = {}
  propsDef.get = function () { return this._props }
  if(process.env.NODE_ENV ! = ='production') {
    dataDef.set = function () {
      warn(
        'Avoid replacing instance root $data. ' +
        'Use nested data properties instead.'.this
      )
    }
    propsDef.set = function () {
      warn(`$props is readonly.`.this)}}Object.defineProperty(Vue.prototype, '$data', dataDef)
  Object.defineProperty(Vue.prototype, '$props', propsDef)
Copy the code

  // Observer set, delete method
  Vue.prototype.$set = set                  // Set the observation object
  Vue.prototype.$delete = del               // Delete the observed object
  Vue.prototype.$watch                      // $watch on instance

Copy the code

2.2.3 eventsMixin


  Vue.prototype.$on = function () {}      // Add a listener
  Vue.prototype.$once = function () {}    // Add a one-time listener
  Vue.prototype.$off = function () {}     // Uninstall the listener
  Vue.prototype.$emit = function () {}    // Launch event

Copy the code

2.2.4 lifecycleMixin


  Vue.prototype._update = function () {}         // View updates focus on view component updates
  Vue.prototype.$forceUpdate = function () {}    The point of forcing updates is to force the observer to update accordingly
  Vue.prototype.$destroy = function () {}        // Destroy the current instance

Copy the code

2.2.5 renderMixin

Render related processing and API mount


  / / https://chunmu.github.io/mylife/vue-2.6.0/api.html#_1-nexttick
  Vue.prototype.$nextTick = function () {}         // View updates focus on view component updates please refer to
  Vue.prototype._render = function () {}           The point of forcing updates is to force the observer to update accordingly

Copy the code

2.2.6 installRenderHelpers


// The mount operation was not executed
export function installRenderHelpers (target: any) {
  target._o = markOnce                  // Marks the once directive attribute
  target._n = toNumber
  target._s = toString
  target._l = renderList                // Render for loop
  target._t = renderSlot
  target._q = looseEqual
  target._i = looseIndexOf
  target._m = renderStatic              // Render static content
  target._f = resolveFilter
  target._k = checkKeyCodes
  target._b = bindObjectProps           // Dynamic property binding
  target._v = createTextVNode           // Create a Text VNode node
  target._e = createEmptyVNode          // Create an empty VNode
  target._u = resolveScopedSlots
  target._g = bindObjectListeners
  target._d = bindDynamicKeys           // 
      
target._p = prependModifier } Copy the code

2.3 initGlobalAPI

Note that the vue. prototype instance method is configured first, followed by the constructor API mount

2.3.1 agent config


  /** * hijacks the set method of config configuration. Read-only objects should not modify vue. config directly but configure the fields ** / as needed in the passed parameters
  const configDef = {}
  configDef.get = (a)= > config
  if(process.env.NODE_ENV ! = ='production') {
    configDef.set = (a)= > {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.')}}Object.defineProperty(Vue, 'config', configDef)

Copy the code

2.3.2 Global Config Parsing


// There are omissions in the field parsing of the config global configuration
export default ({
  optionMergeStrategies: Object.create(null),             // The configuration of the various merge strategies is best left unchanged unless you are familiar with the mechanism
                                                          // object.create (null) This creates relatively purer objects
  silent: false.// Whether to keep silent to disable console.warn outputproductionTip: process.env.NODE_ENV ! = ='production'.// A reminder to control the development modedevtools: process.env.NODE_ENV ! = ='production'.// DevTools switch
  performance: false.// Whether to record performance data such as vue rendering time and compile time records
  errorHandler: null.// You can customize error handling methods such as collecting vue error reports
  warnHandler: null.// You can customize warn handling methods such as collecting VUE WARN reports
  ignoredElements: [],                                    // Ignore compiled custom tags
  keyCodes: Object.create(null),                          // set of key values
  isReservedTag: no,                                      // Whether a tag is kept globally
  isReservedAttr: no,                                     // Whether attributes are kept globally
  isUnknownElement: no,                                   // Unknown element
  getTagNamespace: noop,                                  // Get the label namespace
  parsePlatformTagName: identity,
  mustUseProp: no,                                        // Whether a prop must be passed in such as the selct tag must receive the value attribute as a prop
  async: true._lifecycleHooks: LIFECYCLE_HOOKS                        // Lifecycle hooks beforeCreate, created, beforeMount, Mounted, beforeUpdate, updated, beforeDestory, destroyed, activated, deactivated, errorCaptured, serverPrefetch
}: Config)

Copy the code

2.3.3 Vue. Util


  Vue.util = {
    warn,                    // There is a post-processing detail for formatting the vue instance call stack
    extend,                  // Extend method for... In loop Value Set value traverses the prototype chain extension attribute Assign does not
    / / https://chunmu.github.io/mylife/vue-2.6.0/api.html#_1-mergeOptions
    mergeOptions,            // options merge policy new Vue(options)
    defineReactive           // Observer tool methods
  }

Copy the code

  Vue.prototype.set = function () {}                // set
  / / https://chunmu.github.io/mylife/vue-2.6.0/api.html#_1-nexttick
  Vue.prototype.delete = function () {}             // delete
  Vue.prototype.nextTick = function () {}           // nextTick
  Vue.prototype.observable = function () {}         // observable

Copy the code

2.3.4 Vue.options Initialization

Initializes options on the constructor as the ancestor of all subsequent options

  /** * Vue.options = { * components: {}, * directives: {}, * filters: {} * } * */
  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type= > {
    Vue.options[type + 's'] = Object.create(null)
  })
  
  Vue.options._base = Vue                               // _base => Vue
  
  extend(Vue.options.components, builtInComponents)     // The global built-in keep-alive component
Copy the code

2.3.5 initUse Vue. Use

The implementation of vue. use, which provides an aggregation of global or instance specific logic or APIS to operate Vue, standardizes plug-in installation

  • Use Definition section

Use (plugin1).use(plugin2)


  Vue.use = function (plugin: Function | Object) {
    // Check whether the same plug-in is registered twice
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    if (installedPlugins.indexOf(plugin) > - 1) {
      return this
    }

    // additional parameters
    const args = toArray(arguments.1)
    // The initial position argument to pass install is this = Vue
    args.unshift(this)
    // Trying to install plug-ins through Install can be considered as the standard format recommended by Vue for plug-in installation
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
  }
Copy the code
  • The official documentation will be copied again to make it very clear what it can do

MyPlugin.install = function (Vue, options) {
  // 1. Add a global method or attribute
  Vue.myGlobalMethod = function () {
    / / logic...
  }

  // 2. Add global resources
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      / / logic...}... })// 3. Inject component options
  Vue.mixin({
    created: function () {
      / / logic...}... })// 4. Add instance methods
  Vue.prototype.$myMethod = function (methodOptions) {
    / / logic...}}Copy the code

2.3.6 initMixin Vue. Mixins

  • Vue. Mixin definition

The essence is to call mergeOptions directly to merge mixins options, which involves a key point we need to pay attention to, options merge strategy

  Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin)
    return this
  }

Copy the code

2.3.7 initExtend core implementation


Vue.extend = function () {}              // Component extension core methods are then parsed in actual code

  /** * Each instance constructor, including Vue, has a unique * cid. This enables us to create wrapped "child * constructors" for prototypal inheritance and cache them. * /
  Vue.cid = 0
  let cid = 1

  /** * Class inheritance */
  Vue.extend = function (extendOptions: Object) :Function {
    extendOptions = extendOptions || {}
    const Super = this
    const SuperId = Super.cid
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    // options mount _Ctor for storage
    // If you want extended options = targetOptions, continue to extend with this option next time
    // There are already existing extension Vue class constructors that meet the criteria
    // Cache the extended constructor
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }
    // Extensions are used to build components
    const name = extendOptions.name || Super.options.name
    if(process.env.NODE_ENV ! = ='production' && name) {
      validateComponentName(name)
    }

    // VUe-like constructor
    const Sub = function VueComponent (options) {
      this._init(options) Call _init when the call constructor is son = new Sub()
    }
    // Rewrite Sub's prototype; constructor points to Vue, inheriting method properties on all Vue instance prototype chains
    Sub.prototype = Object.create(Super.prototype)
    // Call the constructor of the prototype chain to execute Sub or point to Vue
    Sub.prototype.constructor = Sub
    Sub.cid = cid++ // constructor ID skips 1... If you use statistics, you lose one day. The total is right
    // Call mergeOptions top-level vue here
    // super. options with _base will be inherited to Sub options
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    / / specify the super
    Sub['super'] = Super

    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids Object.defineProperty calls for each instance created.
    if (Sub.options.props) {
      initProps(Sub) // Set the props agent to access this.xxx = this._props
    }
    if (Sub.options.computed) {
      initComputed(Sub) // Design to immediate corresponding section follow up
    }

    // allow further extension/mixin/plugin usage
    // Inherit properties and methods from super
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use

    // create asset registers, so extended classes
    // can have their private assets too.
    // The conponents filters in Vue are cache inherited
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // enable recursive self-lookup
    // It can register itself as a component
    if (name) {
      Sub.options.components[name] = Sub
    }

    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    / / sealing options
    Sub.sealedOptions = extend({}, Sub.options)

    // cache constructor
    // Note that the superId is used to store the constructor after the super execution
    cachedCtors[SuperId] = Sub
    return Sub
  }

Copy the code

2.3.8 initAssetRegisters

This is the definition of vue.component, vue.directive, vue.filter global API

The three sections of registration logic are mixed together to add unnecessary type judgments, but it needs to be consistent with ASSET_TYPES. I can’t think of a better solution for now, but it’s not too expensive and it’s not a performance problem. This resource level processing can be broken up separately because even if a new resource is introduced it is likely to have logical changes so adding one registration method is acceptable


  ASSET_TYPES.forEach(type= > {
    Vue[type] = function (id: string, definition: Function | Object) :Function | Object | void {
      if(! definition) {// If the registered content exists, return the corresponding ID. After registration, it is stored in the {components: {}, filters: {}, directives: {}} collection in the corresponding location vue. options
        /** * Vue.component('my-component') * => * Vue.options = { * ... , * components: { * 'my-component': MyComponent * } * } * */
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if(process.env.NODE_ENV ! = ='production' && type === 'component') {
          // Verify the validity of the component name
          validateComponentName(id)
        }
        if (type === 'component' && isPlainObject(definition)) {
          // Use the name attribute defined inside the Component object to name components preferentially. If not, use the id used when registering components
          definition.name = definition.name || id
          // extend is used here, which we'll talk about later
          definition = this.options._base.extend(definition)             
        }
        // If it is a directive and the directive is configured as a method, the default binding and updating of the directive is to call that method
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })

Copy the code

2.3.10 validateComponentName

Verify component name validity

  // Unicode characters are generally supported, including Chinese mathematical symbols and emojis, but it is best not to play this way
  if (!new RegExp(`^[a-zA-Z][\\-\\.0-9_${unicodeRegExp.source}] * $`).test(name)) {
    warn(
      'Invalid component name: "' + name + '". Component names ' +
      'should conform to valid custom element name in html5 specification.')}// Whether to reserve words, reserve labels, or built-in slot components; these cannot be used; keep-alive, etc. (built-in components)
  if (isBuiltInTag(name) || config.isReservedTag(name)) {
    warn(
      'Do not use built-in or reserved HTML elements as component ' +
      'id: ' + name
    )
  }

Copy the code

3. Remaining mount

3.1 SSR server rendering is relevant

Let’s not focus on that for now


Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})

Object.defineProperty(Vue.prototype, '$ssrContext', {
  get () {
    /* istanbul ignore next */
    return this.$vnode && this.$vnode.ssrContext
  }
})

// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
  value: FunctionalRenderContext
})

Copy the code

3.2 Vue version implantation


Vue.version = '__VERSION__'    // Get the version number of the main project package.json

// Look for the code block in srcipts/config that sets the rullup variable to replace the corresponding string in the packaged project
// built-in vars
const vars = {
  __WEEX__:!!!!! opts.weex,__WEEX_VERSION__: weexVersion,
  __VERSION__: version
}

Copy the code

Utils mounts and presets platform-specific built-in components and platform-specific __patch__ methods


// install platform specific utils
Vue.config.mustUseProp = mustUseProp                            // Determine whether the props must be introduced forcibly
Vue.config.isReservedTag = isReservedTag                        // Determine whether to keep the label
Vue.config.isReservedAttr = isReservedAttr                      // Determine whether to keep attributes
Vue.config.getTagNamespace = getTagNamespace                    // Get the namespace
Vue.config.isUnknownElement = isUnknownElement                  // Unrecognized component name


// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)              // v-model&v-show
extend(Vue.options.components, platformComponents)              // Transition&TransitionGroup

// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop              // Distribute the core of render communication

Copy the code

3.4 Mount Method Mount API core methods


// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
) :Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

Copy the code

3.3.5 Warm hints related to development and Initialization of DevTools


// devtools global hook
/* istanbul ignore next */
if (inBrowser) {
  setTimeout((a)= > {
    if (config.devtools) {
      if (devtools) {
        devtools.emit('init', Vue)                        / / devtools use
      } else if( process.env.NODE_ENV ! = ='production'&& process.env.NODE_ENV ! = ='test'
      ) {
        console[console.info ? 'info' : 'log'] ('Download the Vue Devtools extension for a better development experience:\n' +
          'https://github.com/vuejs/vue-devtools')}}if(process.env.NODE_ENV ! = ='production'&& process.env.NODE_ENV ! = ='test'&& config.productionTip ! = =false &&                    // productionTip in config of new Vue({config})
      typeof console! = ='undefined'
    ) {
      console[console.info ? 'info' : 'log'] (`You are running Vue in development mode.\n` +
        `Make sure to turn on production mode when deploying for production.\n` +
        `See more tips at https://vuejs.org/guide/deployment.html`)}},0)}Copy the code

conclusion

The above is some initialization process done after the introduction of Vue, after such a process, the global API, instance API has a prototype, I have analyzed the above process has the execution process, as well as some simple method definition, but the core and key points are still not involved. In the next installment, we’ll look at the template parsing process for Vue