2

如何减少 monorepo

 2 years ago
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.
neoserver,ios ssh client
如何减少 monorepo 中 lib 的初始化时间

如何减少 monorepo_

2021年10月28日 下午

2.2k 字

7 分钟

本文最后更新于:2021年11月6日 晚上

在我们使用 monorepo 将所有的前端项目放到一个项目中后,会面临各种各样的问题,其中的多个通用模块的初始化也会是一个问题。

下面是目前实践过的一些解决方案

  • 捆绑依赖项
  • 基于 esbuild
  • 不构建 dts

每次修改 libs 中的内容,其他人通过 git 拉取时都需要重新 initialize,如果知道在哪个包还好,可以仅运行指定包的 initialize 命令。如果不知道的话,则需要运行根目录的 initialize 命令,这其实是非常缓慢的,因为它会重新运行所有包含 intialize 命令的 npm 包,而不管它们是否有变更。

  1. 减少 initialize 时间,提高协作的开发体验
  2. 支持 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

参考 VSCode 打包指南

基于 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
  • 增加依赖该模块的终端程序的打包时间

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK