前言
很久以前就听过"Monorepo"这个词,也大致明白它是干嘛的。只是自己手上那些小项目还用不上这种多项目仓库的形式,就一直没试。
Monorepo 是什么
Monorepo,即单一代码库,就是把多个相关项目或模块的代码放进同一个仓库里管理。
想象一下,一个大型应用可能由多个子模块组成,传统做法会给每个子模块单独建一个仓库(MultiRepo)。 Monorepo 则反过来,把所有相关代码都集中到一个地方。
为什么要使用 Monorepo
代码复用
在 Monorepo 里,所有代码都在同一个仓库,不同项目或模块之间共享代码变得特别容易。 一个模块开发出通用的功能或组件,其他模块直接引用复用就行,不用重复造轮子,既省事又少冗余。
比如说,一个企业级应用里可能有多个项目要用同一套身份验证逻辑。在 Monorepo 下,写一次,各个项目直接引用即可。
版本管理
统一的版本管理是 Monorepo 的另一大优势。某个共享模块更新或修复后,所有依赖它的项目立刻就能用上,整个系统保持一致。
假设一个底层库做了性能优化或修了 bug,在 Monorepo 里,所有用到它的项目拉取最新代码时就自动拿到了改进,不用逐个项目更新部署。
开始实践
先确保装好了 pnpm,没有的话在终端跑一句:
npm install -g pnpm
创建一个新文件夹作为 Monorepo 的根目录,并pnpm init初始化仓库。
在根目录新建 pnpm-workspace.yaml,内容大致如下
packages:
- packages/**
- packages 是路径数组,用来指定包含工作区包的目录。
- 通常子项目会统一放在
root/packages/**目录下 - packages 可以指定多个目录
packages 通常作为多项目的存储路径,里面存放的一般是公共代码。在 pnpm 中,它会把 packages 下的子目录都看作一个项目。 另外,如果项目里有文档或者在线演示的项目(这些不属于核心库),放在 packages 里不是很合适,此时就可以按照下面的方式来配置 workspace。
packages:
- packages/**
- docs
- examples
安装依赖
有些依赖需要装到根目录,比如常用的编译依赖 rollup、typescript 等
pnpm add rollup typescript -Dw
其中 -w 表示装在 workspace 根目录,而不是当前目录
给子包装依赖,总不能傻乎乎地 cd 到每个目录一个个 pnpm install。pnpm 提供了过滤参数 --filter 或 -F 来指定命令作用范围。
假如要在 utils 包下装 dayjs,可以这样:
pnpm add dayjs -r --filter @monoremp/utils
假设现在写好了 utils 模块,@monorepo/web 准备使用 utils 模块,可以按照如下操作
pnpm -F @monorepo/web add @monorepo/utils@*
这个命令表示在 @monorepo/web 安装 @monorepo/utils,其中的 @* 表示默认同步最新版本。
之后,打开 packages/web/package.json 发现 dependencies 中多了一行
{
"name": "@monorepo/web",
"version": "1.0.0",
"description": "...",
"main": "index.tsx",
"scripts": {
"dev": "vite"
},
"keywords": [],
"author": "Hsinyau",
"license": "ISC",
"dependencies": {
"@monorepo/utils": "workspace:^1.0.0"
}
}
最终代码仓库的项目结构大致如下
monorepo
├── package.json
├── packages
│ ├── ui
│ │ ├── index.tsx
│ │ └── package.json
│ ├── web
│ │ ├── index.ts
│ │ ├── ...
│ │ └── package.json
│ ├── utils
│ │ ├── index.ts
│ │ ├── ...
│ │ └── package.json
├── docs
│ │ ├── index.ts
│ │ ├── index.md
│ │ └── package.json
├── examples
│ │ ├── astro
│ │ ├── react
│ │ └── vue
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── README.md
└── tsconfig.json
启动和打包项目
在根目录编写 npm scripts 如下所示
"scripts": {
"postinstall": "pnpm run build",
"build:ui": "pnpm -C packages/ui run build",
"build:utils": "pnpm -C packages/utils run build",
"build": "turbo run build",
"dev:web": "pnpm -C packages/web dev",
"...": "..."
}
其中 "-C path" 表示在指定路径下运行 npm 脚本,而不是当前工作路径。
例如在根目录执行 "pnpm dev:web",实际跑的就是 "packages/web/package.json" 里的 "dev" 脚本。