Vue2 初始化入口

840 阅读3分钟

引子

《Vue2 源码构建》一文中,一步一步地讲解 Vue2 是如何打包构建的,最终构建出不同用途的 Vue 版本。当使用 vue-cli4+ 初始化项目时,默认使用的是 Runtime Only Vue 版本;如果项目需要使用 Runtime + Compiler 版本,则可在 vue.config.js 配置文件中设置属性:runtimeCompiletrue 即可。

那么当在 main.js 引入 Vue,即 import Vue from 'vue' 时,项目是如何找到 Vue 呢?

这得归功于 Webpack,使用命令 vue inspect 导出配置,可以看到:

  resolve: {
    alias: {
      vue$: 'vue/dist/vue.runtime.esm.js'
    }
  },

其实,Vue 只是一个别名,最终引入的是位于 vue/dist 目录下文件;那么如果是 Runtime + Compiler Vue 版本,其别名设置如下:

  resolve: {
    alias: {
      vue$: 'vue/dist/vue.esm.js'
    }
  },

对于 Vue2 源码的分析,是基于 Runtime + Compiler 版本来分析。因此,对于 vue/dist/vue.esm.js 产物,其实是由文件 src/platforms/web/entry-runtime-with-compiler.js 打包而生成的;也就是说,它就是引入 Vue 的入口,下面将详细分析该入口的具体实现。

对于入口文件,将其逻辑实现整理成一张流程图,如下:

entrance-init.png

下面将根据流程图一步一步地分析入口文件的具体实现,整体上以主线为主,对于细节稍微展开,更详细的后续文章会讲到。

逻辑分析

从流程图上可看出,实现 Vue 涉及到的主要文件有 4 个,先看入口文件:src/platforms/web/entry-runtime-with-compiler.js ,前几行代码是引入各种依赖,其中一行比较重要的代码:

import Vue from './runtime/index'

跳转到文件 src/platforms/web/runtime/index ,第一行代码:

import Vue from 'core/index'

也是导入 Vue,继续跳转到文件 src/core/index,第一行代码也是导入 Vue:

import Vue from './instance/index'

跳转到文件 src/core/instance/index ,具体逻辑实现如下:

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'function Vue (options) {
  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)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

至此,终于看到 Vue 的庐山真面目。实际上,Vue 是用 Function 实现的类,我们只能使用 new Vue 去实例化它。首先,定义函数 Vue,基于函数在 JavaScript 也是一种对象,在 Vue 的 prototype 上进行扩展。扩展的功能如下:

initMixin

在 Vue 原型上,即 Vue.prototype 定义方法:_init,在函数 Vue 的实现中会调用到。

stateMixin

在 Vue 原型上定义属性:$data$props$set$delete$watch

eventsMixin

在 Vue 原型上定义属性:$on$off$once$emit

lifecycleMixin

在 Vue 原型上定义属性:_update$forceUpdate$destroy

renderMixin

在 Vue 原型上定义属性:$nextTick_render

最终将 Vue 导出,那么该文件就分析完了,回到文件:src/core/index,所实现的功能:

  • 初始化全局 API(initGlobalAPI(Vue)
  • 在 Vue 原型上定义属性:$isServer$ssrContext
  • 在 Vue 上定义属性:FunctionalRenderContextversion
  • 导出 Vue

来看下 initGlobalAPI(Vue) 具体实现:

export function initGlobalAPI (Vue: GlobalAPI) {
  // 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)
​
  // 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
  }
​
  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick
​
  // 2.6 explicit observable API
  Vue.observable = <T>(obj: T): T => {
    observe(obj)
    return obj
  }
​
  Vue.options = Object.create(null)
  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)
​
  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)
}
​

主要在 Vue 定义全局属性,比如:configutil(官方不考虑将其作为公共 API 的一部分,不推荐使用)、setdeletenextTickobservableoptions ;同时还定义几个方法来定义全局属性,如下:

initUse

定义全局属性:use

initMixin

定义全局属性:mixin

initExtend

定义全局属性:extend

initAssetRegisters

定义全局属性:componentdirectivefilter

最终也导出 Vue,至此,主要逻辑分析完了,回到文件:src/platforms/web/runtime/index

基于上一步在 Vue 定义全局属性:config 基础上,config 其数据类型是 Object,在其定义属性:

// install platform specific utils
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isReservedAttr = isReservedAttr
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement

然后初始化 Runtime Only 环境下 directives & components,具体代码如下:

// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)

接着在 Vue 原型上定义:__patch__$mount,最后将 Vue 导出。

最后,回到入口文件:`src/platforms/web/entry-runtime-with-compiler.js ,其实现逻辑也很简单,将上一步在 Vue 原型定义的属性 $mount 保存到变量 mount,重新在 Vue 原型上定义属性 $mount,其值是一个函数,与 mount 的区别是具有编译功能,将 template 编译成 render 函数;最终,其内部在最后也是通过调用 mount保存的 $mount 来实现挂载的。

至此,Vue 初始化入口也就分析完了,我们也清晰地知道 import Vue from 'vue' 这行代码具体是怎么回事了。

参考链接