单体仓库与 JavaScript:为可扩展项目组织代码
随着 JavaScript 项目(尤其是 Node.js 项目)复杂性的增长,开发者面临一个问题:如何组织代码,使其保持可维护、可复用且易于扩展?传统方法是多仓库(polyrepo),即每个服务或库都存在于自己的 Git 仓库中。然而,对于许多团队来说,单体仓库(monorepo)——一个包含多个项目(包)的单一仓库——更加方便和高效。
在本文中,我们将探讨什么是单体仓库,JavaScript/Node.js 有哪些工具可用,以及如何正确组织代码结构。
什么是单体仓库,为什么需要它?
单体仓库是一种代码管理策略,其中系统的所有组件(前端、后端、公共库、配置)都存储在一个仓库中。在 JavaScript 的上下文中,这通常意味着一组包(packages),每个包可以单独发布或作为内部依赖使用。
单体仓库的优势
- 统一的依赖版本:所有包共享一个
node_modules(得益于 hoisting),减少了重复和版本冲突。 - 简单的重构:对公共库的更改在同一个 PR 中立即对所有使用者可见。
- 端到端的原子性:一个提交可以包含多个包的更改,简化了回滚和 CI/CD。
- 代码复用:公共工具、类型和配置可以轻松地在项目之间共享。
- 统一的工具:整个仓库使用同一个 linter、同一个测试运行器、同一个格式化工具。
缺点(以及如何缓解)
- 仓库大小增长:Git 可能会变慢。解决方案:使用
git sparse-checkout或浅克隆。 - CI 配置复杂:需要智能缓存和仅运行更改的包(Nx 或 Turborepo 等工具有帮助)。
- 对纪律要求更高:必须制定明确的提交规范和版本控制规则。
JavaScript 中组织单体仓库的工具
工具的选择取决于团队规模和项目复杂性。我们来看看三种最流行的选项。
1. Yarn / npm Workspaces(基础级别)
这是包管理器的内置功能。它允许在单个仓库中管理多个包,使用统一的 node_modules。
结构示例:
my-monorepo/├── package.json # 根 package.json├── packages/│ ├── utils/ # 公共库│ │ └── package.json│ ├── server/ # 后端应用│ │ └── package.json│ └── client/ # 前端(如果需要)│ └── package.json└── node_modules/根 package.json:
{ "name": "my-monorepo", "private": true, "workspaces": [ "packages/*" ]}包 packages/server/package.json:
{ "name": "@myorg/server", "version": "1.0.0", "dependencies": { "@myorg/utils": "*" // 引用本地包 }}优点:简单,不需要额外依赖。缺点:没有智能缓存和并行任务执行。
2. Lerna(单体仓库的经典选择)
Lerna 多年来一直是标准。它运行在 Yarn/npm Workspaces 之上,并增加了包发布和脚本执行的功能。
安装:
npm
```