monorepo(多层级)项目中的依赖管理
多层级(monorepo)项目可不可以在根目录引入多个子项目使用的依赖,然后子项目就不引入,来达到根目录的package统一管理的效果?
monorepo中的依赖管理
首先为了节省空间或者说复用依赖,减少代码和维护,会想到 可不可以在根目录引入多个子项目使用的依赖,然后子项目就不用引入了。
-
技术上可行,代码能跑。 Node.js 的模块查找机制是递归向上的。当你
import X时,Node 会按以下顺序查找: 1../node_modules(当前包) 2.../node_modules(上一级) 3.../../node_modules(根目录) 这意味着: 如果在根目录安装了axios,即使apps/web/package.json里没写axios,你在apps/web/src/index.ts里写import axios from 'axios',代码是可以运行的(因为 Node 会一路向上找到根目录的node_modules)。 -
工程层面:隐式依赖陷阱 (The Trap) 虽然上面的“冒泡查找”能让代码跑起来,但这在工程上被称为 “幽灵依赖” (Phantom Dependency) 或 “隐式依赖”。
结论:强烈不建议复用运行时依赖(Runtime Deps)。
为什么要禁止这样做?
-
版本不可控:
- 如果你在
apps/web里用的是根目录的axios。 - 某天你为了
apps/api的需求,升级了根目录的axios(有破坏性更新)。 - 后果:
apps/web莫名其妙挂了,因为你根本不知道它依赖了根目录的版本。
- 如果你在
-
破坏可移植性:
- 如果你想把
apps/web拆分出来单独部署,或者移动到另一个仓库。 - 因为它的
package.json里没有声明axios,一旦脱离了这个 Monorepo 环境,它就跑不起来了。
- 如果你想把
-
pnpm 的严格模式:
- 默认情况下,
pnpm试图阻止你访问未声明的依赖。虽然 Node.js 允许向上查找,但pnpm的node_modules结构设计就是为了让这种“非法访问”变得困难(尽管在根目录的依赖通常还是能被访问到的,但这属于“漏网之鱼”而非特性)。
- 默认情况下,
✅ 最佳实践策略:该怎么放?
在 Monorepo 中,我们通常遵循 “工具归根,应用归私” 的原则。
A. 放在根目录 (Root) 的依赖 🌳
适合:开发工具、构建工具、规范类库
这些依赖通常是整个仓库通用的,不需要打进生产环境包里。
- 例子:
typescript(保证全仓库语言标准一致)eslint,prettier(代码风格一致)husky,lint-staged(Git 钩子)@types/node(基础类型定义,如前文讨论)turbo,nx(构建编排工具)
B. 放在子项目 (Package) 的依赖 📦
适合:运行时依赖、UI 框架、特定业务库
这些依赖直接影响代码的运行逻辑,必须在各自的 package.json 中明确声明。
- 例子:
react,vue(前端框架)nestjs,express(后端框架)lodash,axios,dayjs(工具库)- 内部 UI 库 (如
@my-org/ui)
C. 如何高效操作?
你不需要手动去改每个 json 文件,使用 pnpm 过滤器命令:
-
给所有包安装 lodash (不推荐,除非真的都要用):
pnpm add lodash -r # -r 表示 recursive (递归所有包) -
只给 api 项目安装 nestjs:
pnpm --filter api add @nestjs/core -
在根目录安装 eslint (开发依赖):
pnpm add -w -D eslint # -w 表示 workspace-root (仅根目录)
总结
- 省空间吗? 省,pnpm 会自动去重。
- 能直接用吗? 根目录的包能被子项目通过
import读取到。 - 应该直接用吗?
- 如果是 开发工具 (DevDeps) -> 是,放根目录大家一起用。
- 如果是 生产依赖 (Deps) -> 否,请在每个子项目中显式安装,哪怕版本号一样。这叫“显式依赖”,是健壮工程的基石。
| 类别 | 描述 | 放置位置 | 典型例子 |
|---|---|---|---|
| 基建工具 (Infrastructure) | 只是在开发、构建、测试阶段用到,不会被打包进最终产物里。 | 根目录 (workspace root) | typescripteslint 相关全家桶prettierhusky / lint-stagedvitest / jestturbo / nx |
| 共享配置 (Shared Config) | 全局通用的配置文件。 | 根目录 | @types/node@tsconfig/node-lts |
| 业务依赖 (App Logic) | 代码运行时确实需要它,会被打包进最终产物 (bundle)。 | 各子项目 (packages/apps) | react / vue@nestjs/coreaxioslodashantd / element-plus |
| 特殊工具 (Specific Tools) | 只有这一个子项目特有的构建需求,别的项目完全用不上。 | 各子项目 | 比如 electron 只在 desktop 用:electronelectron-builder |