源码目录
Vue.js 的源码都在 src 目录下,结构如下:
src
├── compiler # 编译相关 (模板解析成 ast 语法树及优化)可以构建时也可以运行时
├── core # 核心代码 (内置组件、全局 API 封装,Vue 实例化、观察者、虚拟 DOM、工具函数等)
├── platforms # 不同平台的支持(入口:web和weex)
├── server # 服务端渲染(跑在服务端的 Node.js)
├── sfc # .vue 文件解析
├── shared # 共享代码(浏览器端和服务端的共享工具方法)
源码构建
Vue.js 源码是基于 Rollup 构建的,它的构建相关配置都在 scripts 目录下。
构建脚本
通常一个npm托管项目都会有个package.json文件,它是对项目的描述文件,内容为一个标准JSON对象。
通常会配置 script
字段作为 NPM 的执行脚本:
{
"script": {
"build": "node scripts/build.js",
"build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
"build:weex": "npm run build -- weex"
}
}
可以看到这三条命令都是通过script/build.js
构建 vue.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)
首先读取config.js
中的配置,然后通过命令行参数做配置过滤。
然后看一下script/confit.js
,以web-runtime-cjs
为例:
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
}
}
构建格式分为:
格式名称 | 遵循规范 |
---|---|
cjs | CommonJS |
es | ES Module |
umd | UMD |
从entry
看起,它的entry
是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)
}
}
它将传入参数通过/
分割为数组,取第一个元素设置为base
,此时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')
}
这样就可以通过对应目录找到真实的入口路径。
为../src/platforms/web/entry-runtime.js
,经过 Rollup 的构建打包,最终在dist目录下生成vue.runtime.common.js
Runtime Only 和 Runtime+Compiler
当用 vue-cli 去初始化Vue项目时会询问用Runtime Only 版本的还是 Runtime + Compiler 版本,对比一下:
- Runtime Only
需要使用 webpack 的 vue-loader 工具将
.vue
文件编译为JavaScript。由于做了预编译,所以它只包含运行代码,因此代码体积更轻量。
- Runtime + Compiler
如果没有对代码做预编译,但又使用了 Vue 的 template 属性并传入一个字符串,则需要在客户端编译模板。
可以看下两种版本:
// 需要编译器的版本
new Vue({
template: '{{ hi }}'
})
// 这种情况不需要
new Vue({
render (h) {
return h('div', this.hi)
}
})
由于最终渲染都是通过render
函数,因此当写了templete
属性时就需要编译。
由于编译的性能损耗,因此更推荐使用Runtime-Only 的 Vue.js。