2025-10-17-【工程化】Monorepo(单仓库多项目)工具对比
前言
在 Monorepo(单仓库多项目)管理领域,Lerna 是曾经的“老大哥”,而 Turborepo 和 Nx 则是目前最主流的、追求“极速”体验的新一代构建工具。
以下是它们在核心逻辑、性能和适用场景上的深度对比。
1. 核心定位差异
| 工具 | 定位 | 核心哲学 |
|---|---|---|
| Lerna | 包发布/版本管理 | 早期定位是管理 packages 的依赖链接和发布流程,本身不擅长构建加速。 |
| Turborepo | 构建加速器 | 追求“零配置”和极致的缓存(Cache)。它是基于任务(Task)的,能极大地缩短重复构建时间。 |
| Nx | 全栈管理框架 | 功能最全,提供代码生成器、项目依赖分析图谱,适合超大型企业级项目的精细化管理。 |
2. 关键特性对比
Lerna:经典但已“换芯”
Lerna 曾经因为维护停滞几乎被放弃,但后来被 Nx 的母公司 Nrwl 接管并进行了现代化重构。
- 优势:发布(Publishing)流程非常成熟。它能自动处理版本号更新、生成 Changelog。
- 现状:现在的 Lerna 15+ 内部其实集成了 Nx 的部分引擎。如果你只需要管理版本并发布到 npm,Lerna 依然是直观的选择。
Turborepo:简单且快(Vercel 出品)
Turbo 的核心是 “不重复做已经做过的工作”。
- 远程缓存 (Remote Caching):如果你在加拿大节点构建了一次,你的同事在本地构建时可以直接拉取你的缓存结果,无需重新编译。
- 任务编排:通过简单的
turbo.json定义任务依赖(比如:只有当依赖包 build 完了,App 才能 build)。 - 语言优势:用 Rust 编写,启动速度极快。
Nx:强大的生态图谱
Nx 是目前 Monorepo 界的“天花板”,它不仅是构建工具,更是开发标准。
- 依赖分析 (Graph):运行
nx graph可以可视化看到项目中所有包的调用关系。 - 受影响测试 (Affected Commands):如果你只改了 A 包,Nx 会智能判断出哪些包依赖 A,并只运行相关的测试和构建,避免全量运行。
- 计算缓存:同样支持本地和云端缓存。
3. 性能深度对比
| 特性 | Lerna (传统版) | Turborepo | Nx |
|---|---|---|---|
| 执行方式 | 串行或简单并行 | 并行计算 + 任务编排 | 智能任务编排 |
| 本地缓存 | 无(除非结合 Nx) | 有 | 有 |
| 增量构建 | 弱 | 强 | 强 |
| 配置复杂度 | 低 | 低 (JSON) | 中/高 |
4. 底层实现原理
从底层实现来看,这三者的区别本质上是**“脚本执行器”与“计算引擎”**之间的代差。Lerna 代表了早期的拓扑排序逻辑,而 Turbo 和 Nx 则引入了现代编译器中的“控制流图”与“内容寻址缓存”概念。
4.1 Lerna:基于拓扑排序的串行/并行执行器
Lerna 的传统底层逻辑非常简单,它主要解决的是 Package 间的依赖顺序。
- 依赖图构建:读取每个
package.json中的dependencies,构建一个 DAG(有向无环图)。 - 拓扑排序(Topological Sort):根据图的结构确定执行顺序。例如 B 依赖 A,那么 Lerna 会先执行 A 的 build,完成后再启动 B。
- 执行瓶颈:传统的 Lerna 不具备“任务级”细粒度控制。即使你只改了 A 包的一个 README,Lerna 依然会重新执行 A 的完整构建逻辑,因为其底层没有 Input/Output 状态校验机制。
4.2 Turborepo:基于内容的哈希(Content-Aware Hashing)
Turborepo 的底层是由 Rust 编写的,它的核心是 指纹追踪(Fingerprinting)。
- 哈希计算(Hashing):在执行任务前,Turbo 会根据以下四个维度计算一个唯一的哈希值:
- 源码文件的内容(仅限该任务关联的文件)。
- 依赖包的哈希值。
- 环境变量(如
NODE_ENV)。 - 命令行参数。
- 干预执行(The “Dry Run”):如果计算出的哈希值在本地或远程缓存中已存在,Turbo 直接跳过执行,将缓存的
dist目录和日志直接“搬”到对应位置。 - 任务编排(Task Pipeline):它不以 Package 为最小单位,而是以 Task 为单位。比如,所有包的
lint任务可以跨包并行,而不必等待某个包完整 build 结束。
4.3 Nx:基于受影响分析与计算缓存(Computation Caching)
Nx 的底层比 Turbo 复杂得多,它更像是一个静态代码分析器。
- 静态分析(Static Analysis):Nx 不仅仅看
package.json,它会扫描源代码中的import和require语句,构建一个比 Lerna 更精确的源码级依赖图。 - Affected 算法:通过
git diff比对改动的文件,逆向推导出受影响的节点。 - 执行图解耦:Nx 将“做什么”(Task)和“怎么做”(Executor)解耦。它的计算缓存不仅缓存文件,还缓存内存状态。
- 分布式执行(DTE):这是 Nx 的杀手锏。它能将任务图拆分,分发到多台机器并行执行,并处理它们之间的产物依赖,这在 2 核 1G 的小机器上可能感知不强,但在大型 CI 集群中是质变。
4.4 底层差异深度对比表
| 特性 | Lerna (Legacy) | Turborepo | Nx |
|---|---|---|---|
| 底层语言 | JavaScript | Rust (核心引擎) | TypeScript / Rust |
| 缓存颗粒度 | 无 (默认) | 任务级文件缓存 | 源码级依赖 + 任务缓存 |
| 依赖识别 | 仅依赖 package.json |
仅依赖 package.json |
源码 AST 分析 |
| 执行模型 | 拓扑排序 (串行/简单并行) | 任务管线并行 (Pipeline) | 高级受影响分析并行 |
| 产物重用 | 重新生成 | 文件系统软链接/拷贝 | 文件系统镜像 |
如何选择
- 选择 Turborepo 的场景:如果你追求轻量、快、配置简单。对于 1G 内存的服务器,Turbo 的开销极小,且它的并行构建和缓存机制能显著降低构建时的 CPU 压力。
- 选择 Nx 的场景:如果你的项目非常庞大,包含几十个子项目,且你需要严格的架构约束(比如禁止 A 包引用 B 包),或者需要强大的 IDE 插件支持。
- 选择 Lerna 的场景:如果你只是开发几个 npm 插件,主要痛点在于如何方便地更新版本号和发布,且对构建速度没有极高要求。
- Turborepo 的优势:由于底层是 Rust,它的内存开销极小。在 1G 内存的机器上,Turbo 扫描数千个文件计算哈希的速度极快,不会导致 Node.js 进程因为内存压力频繁 GC。
- Nx 的考量:Nx 的静态分析插件(尤其是包含大量代码生成器时)对内存有一定要求。如果你的 Monorepo 非常大,在 1G 内存机器上跑
nx graph可能会比较吃力。 - 缓存的作用:对于你的境外节点,开启 Remote Cache。这样你在本地 M1 Mac 上构建好的
dist产物,在 Debian 服务器上部署时,底层引擎发现哈希一致,会直接从云端下载结果,而不需要在弱鸡的服务器 CPU 上进行编译。
2025-10-17-【工程化】Monorepo(单仓库多项目)工具对比
https://zhangyingxuan.github.io/2025-10-17-【工程化】Monorepo(单仓库多项目)工具对比/