如何减少 monorepo
source link: https://blog.rxliuli.com/p/f35319c382cf488082a1df13dad35005/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
如何减少 monorepo_
2.2k 字
7 分钟
本文最后更新于:2021年11月6日 晚上
在我们使用 monorepo 将所有的前端项目放到一个项目中后,会面临各种各样的问题,其中的多个通用模块的初始化也会是一个问题。
下面是目前实践过的一些解决方案
- 捆绑依赖项
- 基于 esbuild
- 不构建 dts
每次修改 libs 中的内容,其他人通过 git 拉取时都需要重新 initialize
,如果知道在哪个包还好,可以仅运行指定包的 initialize
命令。如果不知道的话,则需要运行根目录的 initialize
命令,这其实是非常缓慢的,因为它会重新运行所有包含 intialize
命令的 npm 包,而不管它们是否有变更。
- 减少 initialize 时间,提高协作的开发体验
- 支持 ci/cd 缓存已构建的 libs,加快构建时间
- 尽可能地按照依赖图并发执行命令,并且基于 git 变更实现缓存
- 在指定模块依赖的模块执行命令
- 在所有子模块中执行命令
真实项目构建时间
lerna run
- 新项目首次初始化 198.54s
- 非新项目再次初始化 90.17s
吾辈在 yarn-plugin-changed 模块中基于 yarn2 实现了这个功能。
捆绑依赖项
加载一个文件总比加载 100 个小文件要快,这也是为什么 webpack 等工具会将开发的代码打包,yarn2 推动 pnp 的主要原因之一。对于 cli 而言,也是一样的道理,将依赖尽可能地打包到 bundle 中就好了。虽然打包本身会增加一点时间,但在其他模块使用 cli 时就会快一些。
real 0m44.142s
user 0m0.122s
sys 0m0.214s
44.142s => 30s 438ms
基于 esbuild
rollup 使用 js 编写,它在使用必须的插件之后打包非常缓慢(可能部分要归结于 tsc 本身也非常慢),而 esbuild 在官方性能测试中要快 10-100 倍,这为性能优化提供了一种思路:将 CPU 密集型的功能使用高性能的语言构建。在使用 esbuild 命令行时,基本上,lib/cli 都能在数百毫秒内完成构建,而其中实际运行构建代码的时间大约只有几十毫秒,大部分时间是在等待 cli 启动。
下面是一个在单模块的性能测试
esbuild cli
time esbuild src/bin.ts --bundle --external:esbuild --external:@yarnpkg/cli --platform=node --outfile=dist/bin.js --sourcemap && \
time esbuild src/index.ts --bundle --external:esbuild --external:@yarnpkg/cli --external:fs-extra --platform=node --format=cjs --outfile=dist/index.js --sourcemap && \
time esbuild src/index.ts --bundle --external:esbuild --external:@yarnpkg/cli --external:fs-extra --platform=node --format=esm --outfile=dist/index.esm.js --sourcemap
# 0m0.244s # 15ms
# 0m0.211s # 4ms
# 0m0.212s # 4ms
esbuild base nodejs cli
time liuli-cli build cli # 0m2.276s # 1988ms
不构建 dts
为什么不构建 dts?
构建 dts 很慢,至于多慢呢?下面是一个构建 cli 的时间分析
不生成类型定义
$ time yarn build
√ 构建 cli: 141ms
√ 构建 esm: 18ms
√ 构建 cjs: 17ms
构建完成: 142ms
real 0m4.000s
user 0m0.075s
sys 0m0.138s
生成类型定义
$ time yarn build
√ 构建 cli: 3598ms
√ 构建 esm: 50ms
√ 构建 cjs: 3587ms
√ 生成类型定义: 3602ms
构建完成: 3614ms
real 0m7.763s
user 0m0.000s
sys 0m0.197s
可以看到,生成类型定义耗费了大量的时间,与使用 esbuild 构建完全不在一个时间量级上。但同时,我们也必须注意到,nodejs 花费的真实时间很多,甚至远超构建本身,也就意味着,nodejs 确实存在性能极限。实际项目中,初始化 15 个模块需要 30s 438ms,而生成 dts 则需要 44s 343ms,单点优化对整体已经很难产生数量级的影响了。
不做任何打包
虽然看起来不可思议,但在 monorepo 中,许多通用模块可能并不需要在 monorepo 之外使用到,所以对于有些不需要发布的模块,可以不做打包,而是直接在 package.json 中指向未打包的入口文件。
下面是一个 lib 的不打包配置
{
"main": "src/index.ts",
"module": "src/index.ts",
"types": "src/index.ts"
}
当然,它也会带来一些副作用,例如
- 使用该模块必须支持 ts
- 增加依赖该模块的终端程序的打包时间
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK