文章中使用到的是Vue源码的2.6.11版本,初次接触到源码,水平有限,着实有些吃力,写文章的目的也是为了帮助自己更清晰地理解。还望前辈们指教。 关于vue源码的获取,可以从github上clone至本地,点击下载源码
下载解压完毕后,先对源码目录结构初步分析,如下:
-
dist:经过打包构建之后的源码
-
flow:类型检查相关文件(当前版本的vue源码使用的是flow做类型检查)
-
scripts:与npm的scripts字段相关的一些文件
-
src:存放vue源码的文件夹(重点分析)
- compiler 编译相关
- core 核心代码
- platforms 对不同平台的支持
- server 服务端渲染
- sfc .vue文件解析
- shared 共享工具方法
对目录结构有了大致了解后,就开启了本次探索的旅程。
Vue.js 源码是基于Rollup构建的。因为vue是发布在npm上的,打开package.json文件,文件中记录了对npm包的详细描述,其中module字段指定npm包的ESM规范入口文件(main字段指定commonjs规范入口文件)。在进行项目构建时,我们执行npm run build命令实际上是执行scripts字段的build字段(此处为node scripts/build.js)
我们打开scripts文件夹下的build.js,代码如下:
let builds = require('./config').getAllBuilds()
可以看到代码中引入了config.js中的getAllBuilds方法,我们打开config.js,最后一句代码如下:
exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
在getAllBuilds方法中涉及的两个参数builds、genConfig又是什么呢?同样在config.js中我们查找builds的定义可以了解到,builds对象自定义了不同版本源码的entry、dest、format、env等配置,用于Rollup构建
const builds = {
'web-runtime-cjs-dev': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.common.dev.js'),
format: 'cjs',
env: 'development',
banner
},
//...此处省略多行代码
}
而genConfig函数返回的才是真正的Rollup配置对象
function genConfig (name) {
const opts = builds[name]
const config = {
input: opts.entry,
external: opts.external,
plugins: [
flow(),
alias(Object.assign({}, aliases, opts.alias))
].concat(opts.plugins || []),
output: {
file: opts.dest,
format: opts.format,
banner: opts.banner,
name: opts.moduleName || 'Vue'
},
onwarn: (msg, warn) => {
if (!/Circular/.test(msg)) {
warn(msg)
}
}
}
return config
}
至此,我们就知道上面build.js中做了什么(获取到所有构建所需的配置)。紧接着根据package.json中scripts字段的参数值进行一层过滤,把不需要打包的过滤掉。调用build方法进行逐个构建。
function build (builds) {
let built = 0
const total = builds.length
const next = () => {
buildEntry(builds[built]).then(() => {
built++
if (built < total) {
next()
}
}).catch(logError)
}
next()
}
以上就是Vue.js源码构建的大致过程。
紧接着我们讨论一下Runtime-Only和Runtime-Compiler的问题,相信部分同学在升级至Vue/CLI3后,遇到过下图所示报错。可以通过如下方式解决:

- 在根目录下添加配置文件
vue.config.js,书写如下配置
const path = require('path')
function resolve (dir) {
return path.join(__dirname,dir)
}
module.exports = {
configureWebpack: config => {
config.resolve = {
extensions: ['.js', '.vue', '.json',".css"],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
}
}
}
}
- 将template方式改为render函数
<!--render函数-->
new Vue({
render: h => h(App),
}).$mount('#app')
<!--template-->
new Vue({
el: '#app',
template: '<App/>',
components: { App }
})
那为什么会存在这样的问题呢,我们来一探究竟。
其实在使用Vue/CLI2创建项目时,也会询问我们使用Runtime-only版本还是Runtime+Compiler版本。这两个版本又有什么区别呢?
- Runtime-only版本字面意思是只包含运行时版本,是在构建时通过webpack的vue-loader工具将模板预编译成JavaScript,也就是进行了预编译,在最终打好的包里实际上是已经编译好的,在浏览器中可直接运行
- Runtime+Compiler字面意思为运行时+编译器,是不在打包时进行编译的,是在客户端(浏览器)运行时进行编译的,所以要使用带编译器的完整版本
因为在Vue.js 2.0中,最终渲染都是通过render函数,如果写template属性,则需要编译成render函数,那么这个编译过程会发生在运行时,所以需要带有编译器的版本。很显然,这个编译过程对性能会有一定损耗,所以通常我们更推荐使用 Runtime-Only 的 Vue.js。