monorepo(多层级)项目中的依赖管理

多层级(monorepo)项目可不可以在根目录引入多个子项目使用的依赖,然后子项目就不引入,来达到根目录的package统一管理的效果?

#status / growing #tech / dev / frontend / eng #type / howto

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)。

为什么要禁止这样做?

  1. 版本不可控

    • 如果你在 apps/web 里用的是根目录的 axios
    • 某天你为了 apps/api 的需求,升级了根目录的 axios(有破坏性更新)。
    • 后果apps/web 莫名其妙挂了,因为你根本不知道它依赖了根目录的版本。
  2. 破坏可移植性

    • 如果你想把 apps/web 拆分出来单独部署,或者移动到另一个仓库。
    • 因为它的 package.json 里没有声明 axios,一旦脱离了这个 Monorepo 环境,它就跑不起来了。
  3. pnpm 的严格模式

    • 默认情况下,pnpm 试图阻止你访问未声明的依赖。虽然 Node.js 允许向上查找,但 pnpmnode_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)
typescript



eslint 相关全家桶



prettier



husky / lint-staged



vitest / jest



turbo / nx
共享配置



(Shared Config)
全局通用的配置文件。根目录@types/node



@tsconfig/node-lts
业务依赖



(App Logic)
代码运行时确实需要它,会被打包进最终产物 (bundle)。各子项目



(packages/apps)
react / vue



@nestjs/core



axios



lodash



antd / element-plus
特殊工具



(Specific Tools)
只有这一个子项目特有的构建需求,别的项目完全用不上。各子项目比如 electron 只在 desktop 用:



electron



electron-builder
创建于 2025/1/1 更新于 2026/5/27