此文章适宜筹备第一次浏览vuejs源码的童鞋,因为vuejs的源码十分多,而且散布在各个文件夹中,因而想要读懂源码,须要理清整个框架的脉络。此文章就是从编译入口登程,找到源码中的关键点。

筹备工作

打包源码

浏览器调试比单纯的浏览源码更有效率,那么如何为vuejs增加sourceMap?

  1. fork vue源码仓库到本人的github仓库中,这样,能够轻易增加正文和批改。
  2. 下载我的项目,关上package.json文件,找到文件中:

  1. vuejs应用rollup打包,在dev命令最初增加--sourcemap
  2. 执行npm run dev进行打包,生成带sourcemap的vue文件。
  3. 找到examples文件夹,轻易找一个用例,将vue文件的援用地址改为新打包生成的文件。

  1. 用浏览器关上html文件,关上控制台,就能够看到源码。

理解打包文件

在命令行中执行npm run build,会打包所有版本的vue文件,打包后果如下:

其中:

  • common 示意合乎commonjs标准的文件。
  • ems 示意合乎ES Module标准的文件。
  • dev 示意文件内容未压缩,是可读的。
  • prod 示意文件内容是压缩过的。
  • runtime

示意运行时版本,不蕴含模版编译性能。也就是在申明组件的时候无奈编译template模版,只能应用render函数。 vue-cli构建的我的项目中,因为打包的时候会将template模版编译成render函数,所以其打包后援用的vue版本为运行时版本。

在vue-cli创立的我的项目中执行vue inspect > out.js。能够将所有的webpack配置输入到out.js文件中,查看其中的resolve配置能够看到其打包的vue版本:

resolve: {    alias: {      vue$: 'vue/dist/vue.runtime.esm.js'    }}
  • 未加common,es

示意是umd标准文件,此文件能够反对commonjs,ES Module,AMD,或者间接通过window.Vue形式援用。

  • 未加rentime

示意残缺版本,蕴含运行时和编译器。整体代码比运行时版本多。

入口文件

vuejs我的项目的打包入口文件,能够当作是源码浏览的入口文件。

打包的时候执行的是如下命令:

rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap

其中scripts/config.js为rollup配置文件所在门路,rollup配置文件要求导出一个对象,其中input属性指定打包入口文件。

scripts/config.js

在该文件的最初:

if (process.env.TARGET) {  module.exports = genConfig(process.env.TARGET)} else {  exports.getBuild = genConfig  exports.getAllBuilds = () => Object.keys(builds).map(genConfig)}

因为执行的打包命令中蕴含--environment TARGET:web-full-dev,所以此时process.env.TARGET值为web-full-dev,也就是通过
genConfig获取配置并导出。

在getConfig办法中,通过const opts = builds[name]获取内置的配置,其中name为web-full-dev。通过opts.entry指定input属性值,其最终值为platforms/web/entry-runtime-with-compiler.js

platforms文件加中寄存的是和平台相干的代码,其中web文件夹是和web相干的代码,weex文件夹是和weex相干的代码。

platforms/web/entry-runtime-with-compiler.js

此文件的性能并不简单,只是批改了Vue原型上的$mount办法和增加静态方法compile

Vue.prototype.$mount = function (  el?: string | Element,  hydrating?: boolean): Component {   // 具体逻辑}// 静态方法compileVue.compile = compileToFunctions

在原有$mount办法的根底上增加判断,当vue组件没有定义render时,判断是否传入了templete,如果传了,就将其编译成render。具体逻辑可精简为:

const options = this.$options// 如果没有传入renderif (!options.render) {    // 获取template    let template = options.template    // .... 此处蕴含template的各种状况判断    if (template) {        // 编译template为render函数        const { render, staticRenderFns } = compileToFunctions(template, {            outputSourceRange: process.env.NODE_ENV !== 'production',            shouldDecodeNewlines,            shouldDecodeNewlinesForHref,            delimiters: options.delimiters,            comments: options.comments        }, this)        // 在options下面增加render函数        options.render = render        // 动态render函数,用于优化Dom渲染过程        options.staticRenderFns = staticRenderFns    }}// 调用原有的mount办法return mount.call(this, el, hydrating)
此处compileToFunctions是模版编译的入口,等到前面编译局部再持续。

platforms/web/runtime/index.js

entry-runtime-with-compiler.js文件中的Vue类引入自platforms/web/runtime/index.js这个文件,此文件为Vue类增加了web平台特有性能,如Dom操作。

此文件能够分为三大块:

  • 扩大config
// 增加web平台特有的一些辅助办法Vue.config.mustUseProp = mustUsePropVue.config.isReservedTag = isReservedTag // 判断是否是保留tag,如inputVue.config.isReservedAttr = isReservedAttr // 是否是保留属性Vue.config.getTagNamespace = getTagNamespace // 获取元素的命名空间Vue.config.isUnknownElement = isUnknownElement

此处增加的办法大部分是Vue外部应用,平时工作上简直用不到,所以不再详解。

  • 增加全局内置组件和指令
// 增加web平台相干的全局内置组件和指令extend(Vue.options.directives, platformDirectives)extend(Vue.options.components, platformComponents)

此处增加的组件有: transitiontransition-group
此处增加的指令有: v-modelv-show

  • 增加原型办法

增加了两个要害办法:Dom挂载和Dom更新。

// 增加虚构Dom更新操作Vue.prototype.__patch__ = inBrowser ? patch : noop// 增加挂载办法Vue.prototype.$mount = function (  el?: string | Element,  hydrating?: boolean): Component {  el = el && inBrowser ? query(el) : undefined  // 渲染虚构Dom  return mountComponent(this, el, hydrating)}
mountComponent是Vnodes渲染的入口办法,后续会具体降到Vnodes渲染过程。

core/index.js

platforms/web/runtime/index.js中的Vue类引入自core/index.js文件。

core文件夹蕴含了vue外围代码,其与平台没有任何关系。

此文件只蕴含一个要害代码:

// 为Vue类增加全局静态方法如Vue.extend, Vue.component等。initGlobalAPI(Vue)// ... 残余的是和ssr服务端渲染相干的全局属性,此处省略。

initGlobalAPI

定义在core/global-api/index.js中:

export function initGlobalAPI(Vue: GlobalAPI) {    // 定义config    Object.defineProperty(Vue, 'config', configDef)    // 帮忙函数,不要间接应用,vuejs不保障会正确执行    Vue.util = {        warn,        extend,        mergeOptions,        defineReactive    }    // 设置全局的set,delete和nextTick    Vue.set = set    Vue.delete = del    Vue.nextTick = nextTick    // 增加observable办法    Vue.observable = <T>(obj: T): T => {        observe(obj)      return obj    }    // 创立全局的options对象    Vue.options = Object.create(null)    // 初始化options中的components,directives,filters三个属性。    ASSET_TYPES.forEach(type => {            Vue.options[type + 's'] = Object.create(null)        })    // Vue.use    initUse(Vue)    // Vue.mixin    initMixin(Vue)    // Vue.extend    initExtend(Vue)    // Vue.component, Vue.directive, Vue.filter    initAssetRegisters(Vue)  }

core/instance/index.js

core/index.js中的Vue类引入自core/instance/index.js文件,此文件定义了Vue的构造函数和实例办法。

定义构造函数

function Vue (options) {  // 确保Vue不会被当作函数调用  if (process.env.NODE_ENV !== 'production' &&    !(this instanceof Vue)  ) {    warn('Vue is a constructor and should be called with the `new` keyword')  }  // 执行_init办法,此办法在initMixin中定义  this._init(options)}

申明实例属性办法

// 增加实例办法属性initMixin(Vue)stateMixin(Vue)eventsMixin(Vue)lifecycleMixin(Vue)renderMixin(Vue)
此处体现了vuejs针对代码逻辑文件的划分,将不同的性能划分到不同的文件中。
  • initMixin

定义在core/instance/init.js文件中。

export function initMixin(Vue: Class<Component>) {    Vue.prototype._init = function (options?: Object) {        // ... 省略    }}

该文件次要为Vue实例增加_init办法,当创立Vue实例的时候,此办法会被立刻调用。

下一部分Vue初始化过程的入口就是此办法。

  • stateMixin

定义在core/instance/state.js中:

export function stateMixin(Vue: Class<Component>) {    // 定义$data属性    Object.defineProperty(Vue.prototype, '$data', dataDef)    // 定义$props属性    Object.defineProperty(Vue.prototype, '$props', propsDef)    // 定义get,set办法    Vue.prototype.$set = set    Vue.prototype.$delete = del    // 定义watch办法    Vue.prototype.$watch = function (        expOrFn: string | Function,        cb: any,        options?: Object    ): Function {        // ... 省略,在响应式源码局部详解    }}
  • eventsMixin

定义在core/instance/events.js中:

export function eventsMixin(Vue: Class<Component>) {    // 定义$on    Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {        // ... 省略    }    // 定义$once    Vue.prototype.$once = function (event: string, fn: Function): Component {        // ... 省略    }    // 定义$off    Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {        // ... 省略    }    // 定义$emit    Vue.prototype.$emit = function (event: string): Component {        // ... 省略    }}

这里定义的事件注册办法逻辑很近似,都是将注册的办法存储在Vue实例的_events属性中。

_events属性是在_init办法执行的过程中初始化的。

  • lifecycleMixin

定义在core/instance/lifecycle.js中:

export function lifecycleMixin(Vue: Class<Component>) {    // 定义_update办法    Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {        // 调用__patch__执行更新渲染        if (!prevVnode) {            // initial render            vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)        } else {            // updates            vm.$el = vm.__patch__(prevVnode, vnode)        }    }    // 定义强制更新办法    Vue.prototype.$forceUpdate = function () {    }    // 定义销毁办法    Vue.prototype.$destroy = function () {    }}

此文件中定义的_update办法是响应式过程中的要害一环,作为观察者Watcher的回调函数,当vm的数据放生变动的时候,会被调用。

  • renderMixin

定义在core/instance/render.js中:

export function renderMixin(Vue: Class<Component>) {    // 定义nextTick办法    Vue.prototype.$nextTick = function (fn: Function) {        // 。。。省略    }    Vue.prototype._render = function (): VNode {        // 外部调用options中的render办法,生成虚构Dom    }}

_render办法将配合_update,_update更新时比照的是虚构Dom,而_render办法就是用于生成虚构Dom。

总结

至此,vuejs整个我的项目的web平台文件关系理顺,如下:

core/instance/index.js: 申明Vue构造函数和实例办法属性。
core/index.js:为Vue增加静态方法。
platforms/web/runtime/index.js:针对web平台,增加Dom渲染和加载办法。
platforms/web/entry-runtime-with-compiler.js: 扩大Vue模版编译能力,如果是不带编译的运行时版本就无需针对template进行解决。