monorepo 的优势在 repo 之间的共享复用、规范统一管理等方面,而随着项目的规模增长,repo 的依赖处理逻辑也会随着迭代而复杂
那 monorepo 中如何管理依赖呢?是将 repo 中的共同依赖安装至根目录的 package.json 中?还是将依赖安装至各 repo 的 package.json 中?
在需要的 repo 中安装依赖
对于大部分 monorepo 项目来说,直接在需要依赖项的 repo 中的 package.json 显示声明,无论是外部依赖还是内部的 repo 之间的依赖,即便 repo 之间的依赖是相同的。
在多个 repo 中安装相同依赖,可以使用以下命令:
npm install jest --workspace=web --workspace=mobile --save-devyarn@v1:
yarn workspace web add jest --dev
yarn workspace mobile add jest --dev
yarn@v2
yarn workspaces foreach -R --from '{web,@repo/ui}' add jest --devpnpm install jest --save-dev --recursive --filter=web --filter=mobile需要注意的是:不同的
包管理器在选择依赖安装的node_modules的位置不同(依赖提升)
优势
- 可维护性: 每个
repo中的package.json都会声明需要的依赖,开发者可以更容易地理解和处理依赖。 - 灵活性: 在大型、复杂的
monorepo中,保持相同版本的依赖是比较困难的。不同的repo的迭代优先级不一致,比如web和ui需要升级react版本,而web可能仍在功能迭代中,ui则可以提前发布相关变更。
简洁的根目录依赖
按上述策略安装依赖时,将会减少 workspace 根目录中的依赖。根目录中需要的依赖项是用于管理项目的工具,而用于构建和库的依赖项则安装在各自的包中。
一些适合安装在 workspace 很目录中的依赖:turbo、husky、lint-staged 等
依赖管理参考
https://github.com/vercel/turborepo/tree/main
https://github.com/vuejs/core/tree/main
幽灵依赖
如果你使用的是 npm 或 yarn@v1 时,安装依赖会默认提升至根目录中的 node_modules 中,此时若 mobile 依赖了 lodash 且未声明时,lodash 将成为 mobile 的 幽灵依赖,若后续迭代中,web 取消了对 lodash 的依赖,那么在开发或运行 mobile 时将会遇到错误。
├── apps
│ ├── mobile (dependencies: {axios: '^1.7.7'})
│ └── web (dependencies: {lodash: '4.17.21'})
└── package.json
如何统一依赖版本?
- 使用 syncpack 、 manypkg 和 sherif 等工具
- 使用 pnpm Catalogs 协议
Catalogs
Catalogs 起初是在 Vite Conf 2023 中提出,于今年
7.8号发布: pnpm@9.5,所以使用Catalogs协议请保持你的pnpm版本 >=9.5.0
Catalogs 工作于 workspace,用于将依赖版本的范围定义为可重用的变量,在 pnpm-workspace.yaml 中定义,package.json 中使用。
优势
在 workspace 中,不同的包之间会使用相同的依赖项,使用 Catalogs 协议可有效减少重复工作
- 统一版本: 在一个
workspace中,通常希望包之间的依赖可以使用同一个版本,使用Catalogs可以更方便的维护版本统一性 - 减少升级工作量和代码合并冲突: 在升级或降级依赖项时,只需要操作
pnpm-workspace.yaml而不是每个包的package.json,可有效减少冲突的发生。
使用
Catalogs 在 pnpm-workspace.yaml 中定义,有两种定义 Catalogs 的方法。通过 catalog:name 在 package.json 中进行引用。
支持协议的字段
dependenciesdevDependenciesoptionalDependenciespnpm.overrides
默认 Catalog
对于默认 Catalog 可以通过 catalog:default 进行引用,也可以简写为 catalog:。
catalog: 协议可理解为直接编写版本范围 ^18.3.1,
pnpm-workspace.yaml
packages: - packages/* # Define a catalog of version ranges. catalog: react: ^18.3.1 react-dom: ^18.3.1
package.json
{ "name": "@example/app", "dependencies": { "react": "catalog:", // 或者 "react-dom": "catalog:default" } }
可命名的 Catalogs
pnpm-workspace.yaml 顶层中不仅可以定义默认的 catalog 也可以定义具名的 catalogs
pnpm-workspace.yaml
catalog: react: ^16.14.0 react-dom: ^16.14.0 catalogs: # Can be referenced through "catalog:react17" react17: react: ^17.0.2 react-dom: ^17.0.2 # Can be referenced through "catalog:react18" react18: react: ^18.3.1 react-dom: ^18.3.1
package.json
{ "name": "@example/components", "dependencies": { // 使用 默认配置 "react": "catalog:", // 使用 具名配置 "react-dom": "catalog:react18" } }
发布后将变为以下内容:
package.json
{ "name": "@example/components", "dependencies": { "react": "^16.14.0", "react-dom": "^18.3.1" } }
使用 codemod 快速重构项目为 Catalogs 协议
在 workspace 的根目录下运行此命令,可快速将项目中 package.json 的版本协议替换为 默认 catalog:,并在 pnpm-workspace.yaml 修改或添加 catalog
pnpx codemod pnpm/catalog
需要注意!该命令会将 workspace 中的所有依赖都转换为 catalog 的默认协议(同依赖不同版本除外),请谨慎执行
A package.json
{ "name": "@example/A", "dependencies": { "react": "^16.14.0", "react-dom": "^18.3.1" } }
b package.json
{ "name": "@example/B", "dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1" } }
执行完命令后:
pnpm-workspace.yaml
catalog: react-dom: ^18.3.1
A package.json
{ "name": "@example/A", "dependencies": { "react": "^16.14.0", "react-dom": "catalog:" } }
参考链接
- pnpm Catalogs
- Turbo - Best practices for dependency installation
- vue 是如何使用 catalogs 协议
- ViteConf 2023 | pnpm Catalogs — A New Tool to Manage Dependencies in monorepos
























