# 什么是 Monorepo?
Monorepo 是一种(单仓多模块)的开发模式,可以在一个代码库中管理多个项目、组件或服务,提供更好的代码共享和重用性。
直至 2024 年,目前在前端界比较流行的 Monorepo 工具有 Pnpm Workspaces
、Yarn Workspaces
、npm Workspaces
、Rush
、Turborepo
、Lerna
、Yalc
、和 Nx
,它们提供了更好的管理、构建和部署多个项目的能力。
Pnpm 本身提供了 Monorepo 能力,不需要额外安装其他工具,所以强烈推荐使用Pnpm Workspaces
作为 Monorepo 项目的依赖管理工具😍😍😍。
# 优缺点
优点:
- 保留 multirepo 的主要优势
- 代码复用
- 模块独立管理
- 分工明确,业务场景独立
- 代码耦合度降低
- 管理所有项目的版本控制更加容易和一致,降低了不同项目之间的版本冲突。
- 可以统一项目的构建和部署流程,降低了配置和维护多个项目所需的工作量。
缺点:
- Monorepo 可能随着时间推移变得庞大和复杂,导致构建时间增长和管理困难,git clone、pull 的成本增加。
- 权限管理问题:项目粒度的权限管理较为困难,容易产生非owner管理者的改动风险。
# Monorepo 绝不是简单地将代码搬到一个仓库
在 Monorepo 中,每个子模块仍然是独立的,有独立的版本,可以独立发包,不受其他模块的限制,最重要的是 Monorepo 的 build、test 都是增量的,只有发生更改的子模块会进行构建和测试,而不需要重新构建和测试整个代码库。这可以大大加快持续集成(CI)的速度,提高开发效率。
与传统多仓库项目相比,Monorepo 中的子模块可以代码共享,可以最大程度复用依赖、复用工作流、复用基础配置。
但是单体仓库和多仓库两种方案能同时存在,一定是各有利弊的,不要将自己锁定到一种方案上,选择最合适的才是最好的。
# 为什么组件库项目会选用 Monorepo 模式
对于组件库项目,很自然的会涉及到划分以下模块
- components 包,作为组件库的主要代码,实现各个 UI 组件的核心逻辑。
- shared 包,主要存放各种杂七杂八的工具方法。
- theme 包,实现组件库的主题样式定制方案。
- cli 包,实现组件库模板脚手架的命令行工具。
- docs 包,组件库的示例 demo 与使用文档。
- playground 包,组件库的在线编辑、演示应用。
细化拆分不同模块的好处非常明显,一句话总结就是:模块划分的越清晰,复用时的灵活性、可操作性就越强,每个独立模块产物的体积也会越轻量。
# 基于 pnpm 包管理器的 monorepo 基本使用
# 创建 workspace
pnpm
支持 monorepo
模式的工作机制叫做 workspace(工作空间) (opens new window)。
它要求在代码仓的根目录下存有 pnpm-workspace.yaml
文件指定哪些目录作为独立的工作空间,这个工作空间可以理解为一个子模块或者 npm
包。
例如以下的 pnpm-workspace.yaml
文件定义:a
目录、b
目录、c
目录下的所有子目录,都会各自被视为独立的模块。
packages:
- a
- b
- c/*
2
3
4
📦my-project
┣ 📂a
┃ ┗ 📜package.json
┣ 📂b
┃ ┗ 📜package.json
┣ 📂c
┃ ┣ 📂c-1
┃ ┃ ┗ 📜package.json
┃ ┣ 📂c-2
┃ ┃ ┗ 📜package.json
┃ ┗ 📂c-3
┃ ┗ 📜package.json
┣ 📜package.json
┣ 📜pnpm-workspace.yaml
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 内部依赖管理
假如项目 a 依赖项目 b :
可以在项目 a 中的 package.json 中建立 workspace 依赖。
{
"name": "a",
// ...
"dependencies": {
"b": "workspace:*"
}
}
2
3
4
5
6
7
当然,你也可以直接在根目录中管理所有 workspace 依赖:
{
"name":"my-project",
"private": true,
// ...
"dependencies": {
"a": "workspace:*",
"b": "workspace:*",
"c-1": "workspace:*",
"c-2": "workspace:*",
"c-3": "workspace:*",
}
}
2
3
4
5
6
7
8
9
10
11
12
在实际发布 npm
包时,workspace:^
会被替换成内部模块 b
的对应版本号(对应 package.json
中的 version
字段)。替换规律如下所示:
{
"dependencies": {
"a": "workspace:*", // 固定版本依赖,被转换成 x.x.x
"b": "workspace:~", // minor 版本依赖,将被转换成 ~x.x.x
"c": "workspace:^" // major 版本依赖,将被转换成 ^x.x.x
}
}
2
3
4
5
6
7
# pnpm filter过滤器
如何在根目录下对指定包进行操作?
pnpm --filter a i -D lodash // 为 a 包安装 lodash
pnpm -F a build // a 包执行build命令(-F为简写)
2