Vue.js源码是基于Rollup构建的,它的构建相关配置在scripts目录下。
通常一个基于NPM托管的项目都会有一个package.json文件,它是对项目的描述文件,它的内容实际上是一个标准的JSON对象。
我们通常会配置script
字段作为NPM的执行脚本,Vue.js源码构建的脚本如下:
{
"script": {
"build": "node scripts/build.js",
"build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
"build:weex": "npm run build --weex"
}
}
这里面总共有3条命令,作用都是构建Vue.js,后面2条是在第一条命令的基础上,添加一些环境参数。
当在命令运行npm run build
的时候,实际上就会执行node scripts/build.js
,接下来我们来看看它实际是怎么构建的。
我们对于构建过程分析是基于源码的,先打开构建的入口JS文件,在script/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)
这段代码逻辑非常简单,先从配置文件读取配置,再通过命令行参数对构建配置做过滤,这样就可以构建出不同用途的Vue.js了。接下来我们看一下配置文件,在script/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
},
// runtime-only production build (Browser)
'web-runtime-prod': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.min.js'),
format: 'umd',
env: 'production',
banner
},
// Runtime+compiler development build (Browser)
'web-full-dev': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.js'),
format: 'umd',
env: 'development',
alias: { he: './entity-decoder' },
banner
},
// Runtime+compiler production build (Browser)
'web-full-prod': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.min.js'),
format: 'umd',
env: 'production',
alias: { he: './entity-decoder' },
banner
},
// ...
}
这里列举了一些Vue.js构建的配置,关于还有一些服务端渲染webpack插件以及weex的打包配置就不列举了。
对于单个配置,它是遵循Rollup的构建规则的。其中entry
属性表示构建的入口JS文件地址,dest
属性表示构建后的JS文件地址。format
属性表示构建的格式,cjs
表示构建出来的文件遵循CommonJS规划,es
表示构建出来的文件遵循ES Module规范。umd
表示构建出来的文件遵循UMD规范。
'web-runtime-cjs': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.common.js'),
format: 'cjs',
banner
}
以web-runtime-cjs
配置为例,它的entry
是resolve('web/entry-runtime.js')
,先来看一下resolve
函数的定义。
源码目录:script/config.js
const aliases = require('./alias')
const resolve = p => {
//获取以/分割的前一部分,比如web
const base = p.split('/')[0]
if (aliases[base]) {
//p.slice(base.length + 1)表示获取/之后的字符串,比如entry-runtime.js
return path.resolve(aliases[base], p.slice(base.length + 1))
} else {
//如果aliases中不存在,比如dist/vue.runtime.common.js
return path.resolve(__dirname, '../', p)
}
}
这里的resolve
函数实现的非常简单,它先把resolve
函数传入的参数p
通过/
做了分割成数组,然后取数组第一个元素设置为base
。在我们这个例子中,参数p
是web/entry-runtime.js
,那么base
则为web
。base
并不是实际的路径,它的真实路径借助了别名的配置,我们来看一下别名配置的代码,在script/alias
中:
const path = require('path')
module.exports = {
vue: path.resolve(__dirname, '../src/platforms/web/entry-runtime-with-compiler'),
compiler: path.resolve(__dirname, '../src/compiler'),
core: path.resolve(__dirname, '../src/core'),
shared: path.resolve(__dirname, '../src/shared'),
web: path.resolve(__dirname, '../src/platforms/web'),
weex: path.resolve(__dirname, '../src/platforms/weex'),
server: path.resolve(__dirname, '../src/server'),
entries: path.resolve(__dirname, '../src/entries'),
sfc: path.resolve(__dirname, '../src/sfc')
}
很显然,这里web
对应的真实路径是path.resolve(__dirname, '../src/platforms/web')
,这个路径就找到了Vue.js源码的web目录。然后resolve
函数通过path.resolve(aliases[base], p.slice(base.length + 1))
找到了最终路径,他就是Vue.js源码web目录下的entry-runtime.js
。因此,web-runtime-cjs
配置对应的入口文件就找到了。
它经过Rollup的构建打包后,最终会在dist目录下生成vue.runtime.common.js
。
通常我们利用vue-cli去初始化我们的vue.js项目的时候会询问我们用Runtime Only版本的还是Runtime+Compiler版本。下面我们来对比这两个版本。
// 需要编译器的版本
new Vue({
template: '{{ hi }}'
})
// 这种情况不需要
new Vue({
render (h) {
return h('div', this.hi)
}
})
因为在Vue.js 2.0中,最终渲染都是通过render
函数,如果写template
属性,则需要编译成render
函数,那么这个编译过程会发生运行时,所以需要带有编译器的版本。
很显然,这个编译过程对性能会有一定损耗,所以通常我们更推荐使用Runtime-Only的Vue.js。
通过这一节的分析,我们可以了解到Vue.js的构建打包过程,也知道了不同作用和功能的Vue.js,他们对应的入口以及最终编译生成的JS文件。尽管在实际开发过程中我们会用Runtime Only版本开发比较多,但为了分析Vue的编译过程,我们这门课重点分析的源码是Runtime+Compiler的Vue.js。