vue源码分析(二) 源码构建
# 1. 概述
vue
源码选择了rollup
(opens new window) 进行构建,rollup
相比于 webpack
,更加轻量,编译后的代码更加干净,更适合javascript
库的构建,除了vue
以外,像React
,Ember
,D3
,Three.js
以及其他很多开源库也选择了Rollup
进行构建。
# 2. 版本输出
# 2.1 版本
根据 format
构建格式可分为三个版本;再根据有无 compiler
,每个版本中又可分出两个版本。
类型 | 说明 |
---|---|
cjs | 表示构建出来的文件遵循 CommonJS 规范 |
es | 构建出来的文件遵循 ES Module 规范 |
umd | 构建出来的文件遵循 UMD 规范 |
# 2.2 区别
类型 | 区别 |
---|---|
Runtime Only | 通常需要借助如 webpack 的 vue-loader 工具把 .vue 文件编译成JavaScript,因为是在编译阶段做的,所以它只包含运行时的 Vue.js 代码,因此代码体积也会更轻量 |
Runtime + Compiler | 我们如果没有对代码做预编译,但又使用了 Vue 的 template 属性并传入一个字符串,则需要在客户端编译模板;Vue.js 2.0 中,最终渲染都是通过 render 函数,如果写 template 属性,则需要编译成 render 函数,那么这个编译过程会发生运行时,所以需要带有编译器的版本 |
通过上面的说明,我们对 Vue
三种不同的构建输出的概念有了一个了解,对每种模块形式又分别输出了 Runtime Only(运行时版)
以及 完整版(Runtime + Compiler)
的概念也有了一个了解,接下来我们从源码的角度进行分析。
# 3. package.json
我们都知道 rollup
或 webpack
中 scripts
是中配置的节点是执行 npm
脚本命令简写,比如 "start": "react-scripts start"
, 执行 npm start
就是运行 "react-scripts start"
,其中 build
节点为构建vue的脚本代码。具体配置如下:
源码目录: package.json
{
// ...
"scripts": {
// 构建完整版 umd 模块的 Vue
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",
"dev:cjs": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-cjs-dev",
"dev:esm": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-esm",
"dev:test": "karma start test/unit/karma.dev.config.js",
"dev:ssr": "rollup -w -c scripts/config.js --environment TARGET:web-server-renderer",
"dev:compiler": "rollup -w -c scripts/config.js --environment TARGET:web-compiler ",
"dev:weex": "rollup -w -c scripts/config.js --environment TARGET:weex-framework",
"dev:weex:factory": "rollup -w -c scripts/config.js --environment TARGET:weex-factory",
"dev:weex:compiler": "rollup -w -c scripts/config.js --environment TARGET:weex-compiler ",
"build": "node scripts/build.js",
"build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
"build:weex": "npm run build -- weex",
"test": "npm run lint && flow check && npm run test:types && npm run test:cover && npm run test:e2e -- --env phantomjs && npm run test:ssr && npm run test:weex",
"test:unit": "karma start test/unit/karma.unit.config.js",
"test:cover": "karma start test/unit/karma.cover.config.js",
"test:e2e": "npm run build -- web-full-prod,web-server-basic-renderer && node test/e2e/runner.js",
"test:weex": "npm run build:weex && jasmine JASMINE_CONFIG_PATH=test/weex/jasmine.js",
"test:ssr": "npm run build:ssr && jasmine JASMINE_CONFIG_PATH=test/ssr/jasmine.js",
"test:sauce": "npm run sauce -- 0 && npm run sauce -- 1 && npm run sauce -- 2",
"test:types": "tsc -p ./types/test/tsconfig.json",
"lint": "eslint src scripts test",
"flow": "flow check",
"sauce": "karma start test/unit/karma.sauce.config.js",
"bench:ssr": "npm run build:ssr && node benchmarks/ssr/renderToString.js && node benchmarks/ssr/renderToStream.js",
"release": "bash scripts/release.sh",
"release:weex": "bash scripts/release-weex.sh",
"release:note": "node scripts/gen-release-note.js",
"commit": "git-cz"
},
// ...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 4. 构建过程
# 4.1 入口文件
从上面的 package.json
中的配置我们可以看出 vue
构建输出最终执行的 "build": "node scripts/build.js"
,所以执行的 js
文件是 scripts/build.js
,如下:
源码目录: scripts/build.js
// ...
// 获取构建需要的配置
let builds = require('./config').getAllBuilds()
build(builds)
// ...
2
3
4
5
6
7
我们从 build.js
中的源码可以看出,首先从配置文件 ./config.js
中读取配置信息,然后进行过滤,最后通过 build(builds)
方法构建 vue.js
,接下来我们分析一下 config.js
# 4.2 配置文件
源码目录: scripts/config.js
const builds = {
// Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify
'web-runtime-cjs-dev': { /* ...*/ }
'web-runtime-cjs-prod': { /* ...*/ },
// Runtime+compiler CommonJS build (CommonJS)
'web-full-cjs-dev': { /* ...*/ }
'web-full-cjs-prod': { /* ...*/ },
// Runtime only ES modules build (for bundlers)
'web-runtime-esm': { /*...*/ },
// Runtime+compiler ES modules build (for bundlers)
'web-full-esm': { /*...*/ },
// Runtime+compiler ES modules build (for direct import in browser)
'web-full-esm-browser-dev': { /*...*/ },
// Runtime+compiler ES modules build (for direct import in browser)
'web-full-esm-browser-prod': { /*...*/ },
// runtime-only umd build (Browser)
'web-runtime-dev': { /*...*/ },
// runtime-only umd production build (Browser)
'web-runtime-prod': { /*...*/ },
// Runtime+compiler umd 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 umd 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
},
// Web compiler (CommonJS).
'web-compiler': { /*...*/ },
// Web compiler (UMD for in-browser use).
'web-compiler-browser': { /*...*/ },
// Web server renderer (CommonJS).
'web-server-renderer-dev': { /*...*/ },
'web-server-renderer-prod': { /*...*/ },
'web-server-renderer-basic': { /*...*/ },
'web-server-renderer-webpack-server-plugin': { /*...*/ },
'web-server-renderer-webpack-client-plugin': { /*...*/ },
// Weex runtime factory
'weex-factory': { /*...*/ },
// Weex runtime framework (CommonJS).
'weex-framework': { /*...*/ },
// Weex compiler (CommonJS). Used by Weex's Webpack loader.
'weex-compiler': { /*...*/ }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
配置文件遵循 Rollup
的构建规则的。其中:
entry
:属性表示构建的入口JS
文件地址dest
: 属性表示构建后的JS
文件地址format
:属性表示构建的格式cjs
: 表示构建出来的 文件遵循CommonJS
规范es
: 表示构建出来的文件遵循ES Module
规范umd
:表示构建出来的文 件遵循UMD
规范
以 web-full-dev
配置为例,它的 entry
是 resolve('web/entry-runtime-with-compiler.js')
,先来看一下函数的定义。
源码目录: scripts/config.js
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)
}
}
2
3
4
5
6
7
8
9
resolve
函数接受一个参数 p
,以 web-full-dev
为例。
此处的参数是 web/entry-runtime-with-compiler.js
和 dist/vue.js
,resolve
函数中以 /
分割获取到一个数组,此例子中分别为 ['web', 'entry-runtime-with-compiler.js']
和 ['dist', 'vue.js']
,拿到数组的第一项 web
和 dist
然后判断第一项对应的 key
能否在 aliases
中找到,最终拼接获得路径字符串: src/platforms/web/entry-runtime-with-compiler.js
和 ../dist/vue.js
。
其中 aliases
定义在文件 aliases.js
中:
源码目录: scripts/aliases.js
const path = require('path')
const resolve = p => path.resolve(__dirname, '../', p)
module.exports = {
vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
compiler: resolve('src/compiler'),
core: resolve('src/core'),
shared: resolve('src/shared'),
web: resolve('src/platforms/web'),
weex: resolve('src/platforms/weex'),
server: resolve('src/server'),
sfc: resolve('src/sfc')
}
2
3
4
5
6
7
8
9
10
11
12
这里定义了一些项目的正式路径所对应的键值对,上面例子对应的键值为:web:resolve('src/platforms/web')
。
最终,通过函数 genConfig(name)
生成 rollup
所需的配置项,经过 Rollup
的构建打包后,最终会在 dist
目录下生成不同版本的 vue.js
。 genConfig
函数的定义如下:
源码目录: scripts/config.js
// npm run dev ===> TARGET:web-full-dev
function genConfig (name) {
const opts = builds[name] // // builds 里面 web-full-dev ===> 对应的配置
const config = {
sourceMap: true,
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)
}
}
}
// built-in vars
const vars = {
__WEEX__: !!opts.weex,
__WEEX_VERSION__: weexVersion,
__VERSION__: version
}
// feature flags
Object.keys(featureFlags).forEach(key => {
vars[`process.env.${key}`] = featureFlags[key]
})
// build-specific env
if (opts.env) {
vars['process.env.NODE_ENV'] = JSON.stringify(opts.env)
}
config.plugins.push(replace(vars))
if (opts.transpile !== false) {
config.plugins.push(buble())
}
Object.defineProperty(config, '_name', {
enumerable: false,
value: name
})
return config
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 5. 构建结果
|── dist
│ ├── README.md
│ ├── vue.common.dev.js
│ ├── vue.common.js
│ ├── vue.common.prod.js
│ ├── vue.esm.browser.js
│ ├── vue.esm.browser.min.js
│ ├── vue.esm.js
│ ├── vue.js
│ ├── vue.min.js
│ ├── vue.runtime.common.dev.js
│ ├── vue.runtime.common.js
│ ├── vue.runtime.common.prod.js
│ ├── vue.runtime.esm.js
│ ├── vue.runtime.js
│ └── vue.runtime.min.js
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 6. 总结
UMD | CommonJS | ES Module (基于构建工具使用) | ES Module (直接用于浏览器) | |
---|---|---|---|---|
完整版 | vue.js | vue.common.js | vue.esm.js | vue.esm.browser.js |
只包含运行时版 | vue.runtime.js | vue.runtime.common.js | vue.runtime.esm.js | - |
完整版 (生产环境) | vue.min.js | - | - | vue.esm.browser.min.js |
只包含运行时版 (生产环境) | vue.runtime.min.js | - | - | - |