前言
这是一个对Vue.js
源码解析的系列,会持续更新,欢迎关注;话不多说,下面我们就从怎么读Vue.js
源码开始。
一. 源码目录
首先我们先看看Vue.js
源码的项目结构:Vue.js源码GitHub
我们先了解一下src
这个目录的各模块分工:
src
├── compiler # 编译相关
├── core # 核心代码
├── platforms # 不同平台的支持
├── server # 服务端渲染
├── sfc # .vue 文件解析
├── shared # 共享代码
复制代码
1. compiler
compiler
模块包含Vue.js
了所有编译相关的代码。它包括把模板解析成AST
语法树,AST
语法树优化,代码生成等功能。
2. core
core
目录包含了Vue.js
的核心代码,包括内置组件、全局 API 封装,Vue 实例化、观察者、虚拟 DOM、工具函数等。
3. platform
Vue.js
是一个跨平台的MVVM
框架,它可以跑在 web
上,也可以配合weex
跑在native
客户端上。platform
是Vue.js
的入口,会分别打包成运行在 web
上和weex
上的Vue.js
。
4. server
这是与服务端渲染相关的部分,这部分代码是跑在服务端的Node.js
。
5. sfc
通常我们开发Vue.js
都会借助webpack
构建,然后通过.vue
单文件来编写组件,这个模块的功能就是将.vue
文件内容解析成一个 JavaScript
的对象。
6. shared
这里是Vue.js
定义的一些共享工具方法,会供以上模块所共享。
大概了解了以上模块功能后,我们就知道了对于web
端的源码,我们主要分析的就是core
模块。
二. 源码的构建入口
想想平常我们使用vue
的时候是通过npm
来安装使用的,那说明Vue.js
其实就是一个 node
包,但它是基于Rollup
构建的,但我们也可以用webpack
的一些打包思路去理解它,如果对webpack
和node
包还不太了解的同学可以看看我之前写的webpack4.x最详细入门讲解和不会发布node包?进来看看
简单理解的话就是Vue.js
通过构建工具将其打包,这个包会导出一个Vue
构造函数供我们使用。
所以我们从Vue.js
源码中的package.json
文件入手,因为其包含了打包的一些配置记录,主要了解两个地方:
1. 导出口
"module": "dist/vue.runtime.esm.js"
复制代码
这个配置可以理解为出口或者入口,理解为出口时就是指它会导出一个Vue
构造函数供我们使用;理解为入口的话就从这个vue.runtime.esm.js
开始集成Vue.js
做需要的代码。
2. 打包入口
"scripts": {"build": "node scripts/build.js"}
复制代码
可以理解为打包入口,会通过build.js
找到Vue.js
所需要的依赖代码,然后对其进行打包,所有我们可以从这里入手,去scripts/build.js
路径下的build.js
文件中看看:
// scripts/build.js
let builds = require('./config').getAllBuilds()
...
build(builds)
复制代码
// scripts/config.js
const builds = {
// Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify
'web-runtime-cjs': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.common.js'),
format: 'cjs',
banner
},
// Runtime+compiler CommonJS build (CommonJS)
'web-full-cjs': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.common.js'),
format: 'cjs',
alias: { he: './entity-decoder' },
banner
},
// Runtime only (ES Modules). Used by bundlers that support ES Modules,
// e.g. Rollup & Webpack 2
'web-runtime-esm': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.esm.js'),
format: 'es',
banner
},
// Runtime+compiler CommonJS build (ES Modules)
'web-full-esm': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.esm.js'),
format: 'es',
alias: { he: './entity-decoder' },
banner
},
// runtime-only build (Browser)
'web-runtime-dev': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.js'),
format: 'umd',
env: 'development',
banner
},
// ...
}
exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
复制代码
以上逻辑其实就是从配置文件config.js
中读取配置,再对构建配置做过滤,进而根据不同配置构建出不同用途的Vue.js
,其中就有web
使用的两个版本:Runtime Only
和 Runtime + Compiler
:
3. Runtime Only
在使用Runtime Only
版本的Vue.js
的时候,通常需要借助如webpack
的vue-loader
工具把.vue
文件编译成JavaScript
,因为是在编译阶段做的,所以它只包含运行时的Vue.js
代码,因此代码体积也会更轻量。
4. Runtime + Compiler
如果没有对代码做预编译,但又使用了Vue
的template
属性并传入一个字符串,则需要在客户端编译模板,所以需要带有编译器的版本,即Runtime + Compiler
。
因为后续系列我们会将到编译模块,所有我们就从带编译器的版本入手,即以上入口是entry: resolve('web/entry-runtime-with-compiler.js'),
的文件,根据源码的路径解析我们得到最终的文件路径是src/platforms/web/entry-runtime-with-compiler.js
。
// src/platforms/web/entry-runtime-with-compiler.js
import Vue from './runtime/index'
// ...
export default Vue
复制代码
可以看到这个文件不是定义Vue
构造函数的地方,也是从其他文件引入,然后再加工导出,那我们从./runtime/index
这个文件继续找:
// src/platforms/web/runtime/index
import Vue from 'core/index'
// ...
export default Vue
复制代码
依旧如此……,继续往上找,最终经过几次查找,在src/core/instance/index.js
中可以看到Vue
的真身:
// src/core/instance/index.js
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) {
// 判断是否是开发环境且必须是new调用
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
// new一个实例时会调用_init方法,该方法在下面的initMixin(Vue)中有定义
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
复制代码
所以到此我们终于看到了Vue
的庐山真面目,可以看到Vue
是一个构造函数,且经过了一系列的Mixin
,进而在Vue
的原型上拓展方法。
最后
通过以上梳理,我们大概了解到了我们平时使用的Vue
是怎么来的,后续系列会继续对源码进行梳理,如果对你的有帮助的话欢迎来波关注!
相关参考:Vue.js源码全方位深入解析