源码下载地址:https://github.com/vuejs/vue
Vue源码位于src目录下,其目录结构如下:
src
├── compiler # 编译相关
├── core # 核心代码
├── platforms # 不同平台的支持
├── server # 服务端渲染
├── sfc # .vue 文件解析
├── shared # 共享代码
compiler
compiler目录中包含Vue中所有与编译相关的代码,主要功能是把模板解析成ast语法树,ast语法树优化,代码生成等。编译工作可以在构建时借助webpack、vue-loader等插件进行离线编译,也可以在运行时直接使用包含构建功能的Vue。由于编译工作非常耗费性能,所以更推荐使用前者的离线编译模式。
core
core目录中包含了Vue的核心代码,包括内置组件、全局API封装、Vue实例化、观察者、虚拟DOM、工具函数等,是学习Vue源码的重点。
platform
platform中包含两个次级目录,代表两个入口。Vue是一个跨平台的MVVM框架,可以在web上运行,也可以配合weex在native客户端上运行。通过两个入口,分别打包运行在web上和weex上的Vue。
server
server目录中包含与服务端渲染相关的逻辑,这部分代码在服务端的Node.js中运行,和在浏览器端运行的Vue不一样。
sfc
在开发过程中,常常会通过webpack构建.vue单文件来编写组件。sfc目录中的代码会将.vue文件解析成一个JS对象。
shared
shared目录中包含的是一些对工具方法定义,这些方法会被浏览器端的Vue和服务端的Vue所共享。
在package.json
中,Vue源码的构建脚本为
{
"script": {
"build": "node scripts/build.js",
"build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
"build:weex": "npm run build -- weex",
}
}
当运行脚本npm run build
时,实际上时执行node scripts/build.js
。Vue源码是基于Rollup构建的,与构建相关的配置均位于scripts
目录下。后两条命令是在第一条命令的基础上,添加了环境参数。
以第一条命令为例,在scripts/build.js
中,
let builds = require('./config').getAllBuilds()
// filter builds via command line arg
if (process.argv[2]) {
const filters = process.argv[2].split(',')
builds = builds.filter(b => {
return filters.some(f => b.output.file.indexOf(f) > -1 || b._name.indexOf(f) > -1)
})
} else {
// filter out weex builds by default
builds = builds.filter(b => {
return b.output.file.indexOf('weex') === -1
})
}
build(builds)
上述代码中,builds
是配置文件scripts/config.js
中的所有配置选项,如果构建时有命令行参数process.argv[2]
,则会将输入的参数分割成一个数组filters
,然后在builds
中过滤出所有涉及到filters
中各项的配置。若没有命令行参数,则默认采用weex。
在scripts/config.js
中的配置选项很多,提取出其中一部分进行分析
const builds = {
// Runtime+compiler CommonJS build (CommonJS)
'web-full-cjs-dev': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.common.dev.js'),
format: 'cjs',
env: 'development',
alias: { he: './entity-decoder' },
banner
},
// Runtime only ES modules build (for bundlers)
'web-runtime-esm': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.esm.js'),
format: 'es',
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
},
}
在单个配置中,entry
表示构建入口js文件地址;dest
表示构建出口js文件地址;format
表示构建出来的文件所遵循的规范,其中cjs
表示CommonJS规范,es
表示ES Module规范,umd
表示UMD规范。
以web-runtime-esm
配置为例,其入口文件的地址为resolve('web/entry-runtime.js')
,其中resolve
函数定义如下
const aliases = require('./alias')
const resolve = p => {
const base = p.split('/')[0]
if (aliases[base]) {
return path.resolve(aliases[base], p.slice(base.length + 1))
} else {
return path.resolve(__dirname, '../', p)
}
}
在上述代码中,首先获取在scripts/alias
中定义的路径别名(先将相对路径转换成绝对路径,然后再取一个简短的路径别名),然后将传入的参数进行分割,取数组第一项,在这里被取出的base
为web
,如果路径别名中存在web
,则将web
对应的路径与参数数组的剩余项进行拼接,找到最终路径。这里找到的是在web目录下的entry-runtime.js
文件,也就是web-runtime-esm
配置的入口文件。经过Rollup构建打包后,最终在dist目录下生成vue.runtime.esm.js
文件。
使用vue-cli脚手架初始化Vue项目时,可以选择使用Runtime Only版本,还是Runtime + Compiler版本。这两个版本的区别在于编译工作时离线时完成 还是在线运行时进行。
Runtime Only
若选择Runtime Only版本,则通常需要借助webpack、Vue-loader等工具将.vue文件编译成js文件。这一工作是在编译阶段完成的,所以在运行时只需要执行Vue.js代码,所以代码体积会更轻量。
Runtime + Compiler
若选择Runtime + Compiler版本,则在运行时直接使用包含构建功能的Vue。在Vue2.0中,最终的渲染都是通过render
函数进行的,如果定义了template
属性,则需要先编译成render
函数,再渲染。
// 需要编译器编译成render函数
new Vue({
templete: '<div>{{ 编译 }}<div>'
})
// 不需要编译
new Vue({
render (h) {
return h('div',this.hi)
}
})
由于编译工作非常耗费性能,所以更推荐使用Runtime Only的离线编译模式。