Vue Implementation 02

Static Properties and Functions in Vue (Global API)

Till now, we went through core/instance/index.js. Now we are looking into core/index.js. This file introduced Vue from core/instance/index.js. We open core/index.js here is the code:

// import Vue from its born place
import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'

// pass Vue constructor as parameter, and pass it into initGlobalAPI. This is imported by ./global-api/index.js
initGlobalAPI(Vue)

// add $isServer property to Vue.prototype. This property proxies isServerRendering in core/util/env.js
Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})

// add $ssrContext property to Vue.prototype
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
})

// Vue.version stores the current version of Vue
Vue.version = '__VERSION__'

// export Vue
export default Vue

In code above, it imported Vue in instance/index.js, and imported three variables from three different files:

import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'

initGlobalAPI is a funcion, and use Vue as a parameter of constuctor.

initGlobalAPI(Vue)

Then two more readonly properties is added to Vue.prototype, which are $isServer and $ssrContent. Then it defined FunctionalRenderContext as a static property, and it is imported from FunctionalRenderContext in core/vdom/create-functional-component/js. These properties are designed to use in Server-side rendering.

Lastly. it added version to Vue.constructor and it storages the current version of Vue. But where do the __VERSION__ come from? Open scripts/config.js and find genConfig. There is a line: __VERSION__: version. This line is written in replace plugin in rollup. That is __VERSION__ will be replaced by version finally and version is the version of Vue.

Let's get back to this line:

initGlobalAPI(Vue)

We can assume that this added some global API into Vue. In fact, these global API is attached to Vue's constructor as static properties and functions. Open src/core/global-api/index.js and look what initGlobalAPI do.

Firstly, a piece of code:

  // config
  const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)

It defines config property to Vue.constructor. The apporach to add this property is similar to how to add $data and $props before, and it is readonly as well. When you try to set its value directly, you wiill get a friendly warning if you are not in production.

What is Vue.config? In src/core/global-api/index.js, we can find:

import config from '../config'

So what Vue.config proxying is the object exported in core/config.js.

And it is followed by this code:

// exposed util methods.
// NOTE: these are not considered part of the public API - avoid relying on
// them unless you are aware of the risk.
Vue.util = {
 warn,
 extend,
 mergeOptions,
 defineReactive
}

It defines util property to Vue, and it is an object. It contains four properties which are warn, extend, mergeOptions and defineReactive. They are imported by core/util/index.js.

There is a piece of comment that means Vue.util and the four functions in util is not defined as public API and we should avoid depending on these APIs. However, you can invoke them at your own risk. Besides, in official documentation, these APIs are not introduced as well, so we just use them as less as possible.

And it is followed by this code:

Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick

Vue.options = Object.create(null)

This part is quite simple. It defined four properties which is set, delete, nextTick and options. We should note that Vue.options which is an empty object right now, and it is created by Object.create(null).

But then, Vue.options is not an empty object anymore because of the following code:

ASSET_TYPES.forEach(type => {
 Vue.options[type + 's'] = Object.create(null)
})

// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
Vue.options._base = Vue

extend(Vue.options.components, builtInComponents)

In code above, ASSER_TYPES is imported by shared/constants.js. Open this file, we can know ASSET_TYPES is an array:

export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]

So after execution of this code:

ASSET_TYPES.forEach(type => {
 Vue.options[type + 's'] = Object.create(null)
})

// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
Vue.options._base = Vue

Vue.option become something like this:

Vue.options = {
 components: Object.create(null),
 directives: Object.create(null),
 filters: Object.create(null),
 _base: Vue
}

And it is followed by this line:

extend(Vue.options.components, builtInComponents)

extend is imported by shared/util.js. We can refer to //TODO link to find its effect. At all, this code is mixing properties from builtInComponents to Vue.options.components. builtInComponents is imported by core/components/index.js, coded as below:

import KeepAlive from './keep-alive'

export default {
  KeepAlive
}

Therefore, Vue.options.components became something like this:

Vue.options.components = {
 KeepAlive
}

Till now, Vue.options became:

Vue.options = {
 components: {
  KeepAlive
 },
 directives: Object.create(null),
 filters: Object.create(null),
 _base: Vue
}

Let's dive into code then. In the last part of initGlobalAPI, it invoked four init* functions by passing Vue as parameter.

initUse(Vue)
initMixin(Vue)
initExtend(Vue)
initAssetRegisters(Vue)

They are imported by global-api/use.js, global-api/mixin.js, global-api/extend.js and global-api/assets.js. Let's make them clear one by one. Open global-api/use.js and we found only a function called initUse:

/* @flow */

import { toArray } from '../util/index'

export function initUse (Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | Object) {
    // ...
  }
}

It defines use on the constructor of Vue, which also known as the global API called Vue.use. We are quite familiar with it that it is used to install plugins to Vue.

Open global-api/mixin.js as well, and we found it is simpler:

/* @flow */

import { mergeOptions } from '../util/index'

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

initMixin is used to add mixin to Vue as a global API.

Open global-api/extend.js and look into initExtend:

export function initExtend (Vue: GlobalAPI) {
  /**
   * 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 {
    // ...
  }
}

initExtend added a static property called Vue.cid to Vue. Then, it defined Vue.extend to Vue.

The last one is initAssetRegisters. Open global-api/assets.js and find initAssetRegisters:

export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      // ......
    }
  })
}

ASSET_TYPES which is included in shared/constants.js is what we once met before. So after initAssetRegisters, Vue have three more static functions:

Vue.component
Vue.directive
Vue.filter

These three static API is used to add globally registered component, directives and filter.

Therefore, we introduced initGlobalAPI which act as it named. It added three global API to Vue.constructor.

Now, we are clear about what core/index.js do.

  1. In this file, it imported a core Vue(a.k.a. Vue from core/instance/index.js, which is decorated by multiple properties and functions).
  2. Then, it uses initGlobalAPI to add static functions and properties to Vue.
  3. Besides, it modified prototype and defined two properties called $isServer and $ssrContext.
  4. Finally, it defined Vue.version and exported Vue.