JavaScript 单体仓库:实践中的代码组织
在现代 JavaScript (Node.js) 网页开发中,一个问题越来越常见:当你有多个相互依赖的包、库或微服务时,如何有效地组织代码?传统的独立仓库 (multirepo) 方法可能导致代码重复、版本同步困难和 CI/CD 耗时过长。解决方案是——单体仓库 (monorepo)。在本文中,我们将探讨什么是单体仓库,为什么需要它,以及如何使用 JavaScript 在实践中组织它。
什么是单体仓库,为什么需要它?
单体仓库是一种方法,将多个项目(包、库、服务)的代码存储在一个仓库中。这不仅仅是“把所有东西放在一个文件夹里”,而是一种经过深思熟虑的架构,它解决了许多问题:
- 统一的依赖版本: 你避免了不同包中使用同一库的不同版本的情况。
- 轻松重构: 对公共库的更改会立即在使用它的所有项目中可见。
- 简化的 CI/CD: 一个仓库——一个构建和测试配置。
- 代码共享: 工具函数、类型和配置可供所有包使用,无需发布到 npm。
然而,单体仓库并不适合所有人。它需要团队纪律和良好的工具。对于 JavaScript (Node.js),主要工具是 npm workspaces(内置于 npm 7+)和 Lerna(更高级的工具)。
使用 npm workspaces 设置基本单体仓库
从一个简单的例子开始。假设我们有一个项目,包含两个包:@myapp/core(包含业务逻辑的核心)和 @myapp/server(基于 Express 的服务器)。
创建文件夹结构:
my-monorepo/├── packages/│ ├── core/│ │ ├── src/│ │ │ └── index.js│ │ ├── package.json│ │ └── ...│ └── server/│ ├── src/│ │ └── index.js│ ├── package.json│ └── ...├── package.json└── ...根目录的 package.json 必须包含 workspaces 字段:
{ "name": "my-monorepo", "private": true, "workspaces": [ "packages/*" ], "scripts": { "start": "node packages/server/src/index.js", "test": "npm run test --workspaces" }}现在,在 packages/core/package.json 中:
{ "name": "@myapp/core", "version": "1.0.0", "main": "src/index.js"}而在 packages/server/package.json 中,添加对 core 的依赖:
{ "name": "@myapp/server", "version": "1.0.0", "dependencies": { "@myapp/core": "*", "express": "^4.18.0" }}在根目录执行 npm install 后,npm 会在包之间创建符号链接。现在,在服务器代码中,我们可以直接导入 core:
// packages/server/src/index.jsconst express = require('express');const { calculate } = require('@myapp/core');
const app = express();app.get('/', (req, res) => { res.send(calculate(2, 3));});app.listen(3000);这是一个基本示例,但它已经展示了主要优势:对 @myapp/core 的更改会立即在 @myapp/server 中可见,无需发布到注册表。
使用 Lerna 进行高级组织
当包数量超过 5-10 个时,npm workspaces 可能不够用。这时 Lerna 就派上用场了——这是一个添加了强大功能的工具:
- 在所有或选定的包中运行命令。
- 自动版本管理(fixed 或 independent 模式)。
- 发