Vue 3.0 打包学习
一个优秀项目,是一个不断打磨的过程,Vue 3.0(Vue-Next)从2018年9月19日尤雨溪老师第一次提交原型代码至今,已经历时2年以上,此刻(rc.1版本刚发出)master分支上已经包含2354次提交。
“预知大道,必先为史”,寻着Vue 3.0的每一次提交,理解其细节,观摩其演化,从Vue 3.0这个优秀的项目上学习/参悟软件工程的“大道”。
代码打包是现代前端项目中重要的一环,这篇文字会记录Vue 3.0在打包上的迭代过程(先暂且叫打包吧,期望后续扩展到构建)。
std-001-369997bb
尤雨溪老师于2018年9月19日上午11点35分,在Github第一次提交代码(3401f6b4
),当天最后一次提交是夜里2点(369997bb
),让整个原型项目顺利运行起来(npm run build
),也以这次提交作为整个学习的开始。
一天工作了15个小时,第二天上午11点43分又有了新的提交,尤雨溪老师优秀并且努力,在自己爱的工作义无反顾的投入——代码要学习,努力和勤奋也要学习。
源代码目录结构
打包的依据是源代码库的设计,Vue设计为多包存储库,是典型的 Monorepo(集中管理)风格,便于一同部署上线,共享构建以及配置脚本等核心流程。
Lerna 是一个优化使用 git 和 npm 管理多包存储库的工作流工具,用于管理具有多个包的 JavaScript 项目。Vue中也使用了这个工具。
|--packages
| |--vue
| | |--index.ts(Vue项目入口文件)
| |--renderer-dom
| | |--index.ts
| | |--modules/attrs.ts
| | |--modules/class.ts
| | |--modules/events.ts
| | |--modules/props.ts
| | |--modules/style.ts
| | |--nodeOps.ts
| | |--patchData.ts
| | |--teardownVNode.ts
| |--renderer-server(占位留空)
| | |--index.ts
| |--core
| | |--index.ts
| | |--component.ts
| | |--componentComputed.ts
| | |--componentOptions.ts
| | |--componentProps.ts
| | |--componentProxy.ts
| | |--componentState.ts
| | |--componentUtils.ts
| | |--componentWatch.ts
| | |--createRenderer.ts
| | |--errorHandling.ts
| | |--flags.ts
| | |--h.ts
| | |--utils.ts
| | |--vdom.ts
| |--observer
| | |--index.ts
| | |--autorun.ts
| | |--baseHandlers.ts
| | |--collectionHandlers.ts
| | |--computed.ts
| | |--lock.ts
| | |--operations.ts
| | |--state.ts
| |--scheduler
| | |--index.ts
| |--compiler(占位留空)
| | |--index.ts
完整的文件依赖关系如下图所示:
打包
项目的打包命令,依据传统定义在package.json的scripts中,包含开发环境("dev": "node scripts/dev.js")
和生产环境("build": "node scripts/build.js"
)两条命令。
Vue 3.0的构建基于rollup。Rollup 是一个 JavaScript 模块打包器,使用 ES6 的标准 ES 模块方案(不是以前的特殊解决方案,如 CommonJS 和 AMD),以自由、无缝地使用 library 中最有用独立函数(Tree-shaking),而不必携带其他未使用的代码。
Rollup 对代码中的 import 进行静态分析,排除任何未实际使用(和没有效果 no effect side)的代码。从而实现像 tree-shaking 的优化,并提供诸如循环引用和动态绑定等高级功能。减轻项目的大小膨胀。
Tree-shaking, 也被称为 "live code inclusion," 它是清除实际上并没有在给定项目中使用的代码的过程,但是它可以更加高效。
Rollup 支持 esm、cjs(CommonJS)、umd、iife 等格式发布。
开发环境的构建过程定义在scripts/dev.js
文件中,核心代码如下,借助execa创建命令行应用来执行rollup:
execa(
'rollup',
['-wc', '--environment', `TARGET:${target},FORMATS:${formats || 'global'}`],
{
stdio: 'inherit'
}
)
rollup.config.js
文件中主要是不同环境(开发环境和生产环境)、不同文件不同配置参数的生成,在开发环境的默认构建中,其最终使用的配置如下:
- TypeScript的插件定义;
- 内部子包别名的定义;
- 全局占位变量的定义。
可以在命令行中执行rollup -c rollup.config.dev.js --environment TARGET:renderer-dom,FORMATS:global
引用下面的配置生成基本相同的结果(备注:和“[‘-c’, ‘./rollup.config.dev.js’, ‘–environment’, TARGET:${target},FORMATS:${formats || 'global'}
]”略有差别),开发环境默认生成renderer-dom.global.js
一个文件。
const ts = require('rollup-plugin-typescript2')
const alias = require('rollup-plugin-alias')
const replace = require('rollup-plugin-replace')
module.exports = [
{
input: './packages/renderer-dom/src/index.ts',
external: [],
plugins: [
ts({
check: false,
tsconfig: './tsconfig.json',
cacheRoot: './node_modules/.rts2_cache',
tsconfigOverride: {
compilerOptions: {
declaration: false
}
}
}),
alias({
'@vue/compiler': './packages/compiler',
'@vue/core': './packages/core',
'@vue/observer': './packages/observer',
'@vue/renderer-dom': './packages/renderer-dom',
'@vue/renderer-server': './packages/renderer-server',
'@vue/scheduler': './packages/scheduler'
}),
replace({
__DEV__: true,
__COMPAT__: false
})
],
output: {
file: './packages/renderer-dom/dist/renderer-dom.global.js',
name: 'VueDOMRenderer',
format: 'iife'
},
onwarn: (msg, warn) => {
if (!/Circular/.test(msg)) {
warn(msg)
}
}
}
]
生产环境的构建,每个子包输出了esm、cjs(CommonJS)和iife 3种格式,每种格式包含压缩和非压缩两种文件(compiler、core、renderer-server、scheduler 4个字报只包含esm和cjs两种),执行过程和开发环境基本一致,不再赘述。
其他
scripts中有个bootstrap.js文件,用来生成分包中的package.json, README.md,.npmignore,index.js,index.ts等文件。虽然此时只有6个分包,而且项目的后续迭代中,分包增加的数量是有限的,但依然用代码(node scripts/bootstrap.js
)代替了ctrl + c
和ctrl + v
,这是优秀的工程师对代码的信仰吧。
说明
上面的文字中,可能是错的,伙伴们以源代码为准,斟酌/甄别着看。如果伙伴们发现错误,请回复留言,我会及时修改。