在当今软件开发领域,随着 AI 智能体的崛起和项目规模的不断扩大,代码仓库的组织方式正在经历一场深刻的变革。传统的多仓库(Polyrepo)模式虽然为团队提供了独立性,但也带来了跨仓库协调的巨大成本。Monorepo(单体仓库)作为一种替代方案,正在被越来越多的组织采用——从 Google、Microsoft 到 Facebook,科技巨头们都在使用 Monorepo 来管理庞大的代码库。
本文将全面介绍 Monorepo 的核心概念、优势与挑战,以及如何选择合适的工具来构建高效的 Monorepo。特别值得关注的是,在 AI 驱动的开发时代,Monorepo 正展现出前所未有的价值:它为 AI 智能体提供了统一的上下文视图,使得跨项目的自动化变更、即时反馈循环成为可能。无论你是正在考虑迁移到 Monorepo,还是想优化现有的 Monorepo 设置,这篇文章都将为你提供实用的指导和深入的分析。
Monorepo 是一个包含多个独立项目的单一仓库,这些项目之间有着明确定义的关系。
这个定义强调两个关键要素:
我们认为这是所有成熟的 Monorepo 工具中对 Monorepo 最一致、最准确的定义。
Monorepo ≠ Monolith(单体应用)。一个良好的 Monorepo 实际上是单体应用的反面——它通过模块化和封装,将代码组织成离散的、可独立工作的部分,而不是一个庞大且难以分割的整体。事实上,这样的仓库是过度单体化的,这往往是人们想到 Monorepo 时首先想到的东西。
需要注意的是,仅仅将多个项目放在同一个仓库中(代码托管)并不构成 Monorepo。如果没有明确定义的项目关系和边界,那只是一个"大仓库"。同样,如果一个仓库包含一个没有划分和封装离散部分的大型应用程序,它只是一个大仓库,而非真正的 Monorepo。真正的 Monorepo 需要工具支持来管理项目间的依赖关系、任务编排和代码共享。
多仓库模式通过隔离为团队提供了自主性,但这种自主性有着复合成本。
什么是 Polyrepo? Polyrepo 的对立面通常被称为"Polyrepo":每个团队或应用程序都有自己的仓库,有自己的依赖、工具、构建产物和 CI 管道。组织采用 Polyrepo 是为了给团队自主性:对库、发布节奏和贡献规则的独立选择。但这种自主性是通过隔离实现的,而隔离并不能消除集成的需求。它只是推迟了集成。共享契约仍然需要对齐。破坏性变更仍然需要协调。反馈只是在开发周期的后期才到达,那时更难、更昂贵地采取行动。
主要挑战对比:
| 挑战 | Polyrepo 的问题 | Monorepo 的解决方案 |
|---|---|---|
| 代码共享 | 需要创建独立仓库、CI、包发布和版本管理,开销 discourages 共享 | 直接创建文件夹即可共享,无需版本化包 |
| 代码重复 | 团队在各仓库重新实现通用服务和组件 | 通用代码集中一处,修复一次即惠及所有消费者 |
| 跨仓库变更 | 需要跨多个仓库的 PR、顺序合并、兼容性处理 | 单个 PR 即可完成跨项目变更,原子提交 |
| 规范执行 | 各仓库独立选择工具、依赖、结构,漂移是常态 | 组织规则集中一处,一致性是默认状态 |
具体来说:
代码共享:在 Polyrepo 中,跨仓库共享代码意味着设置专用仓库、CI、包发布和版本管理。消费者必须协调共享依赖的不兼容版本。而在 Monorepo 中,当所有消费者都在同一个仓库时,不需要版本化的包。共享一个新库就像创建一个文件夹一样简单。
代码重复:当共享成本太高时,团队在每个仓库重新实现通用服务和组件。这使维护、安全补丁和质量控制在每个副本中倍增。而在 Monorepo 中,通用服务和组件集中一处。修复一次,每个消费者都获得修复。
跨仓库变更:共享库中的 bug 意味着跨不连续历史的多个 PR、顺序合并和兼容性体操:beta 发布、消费者升级、稳定发布、重复。而在 Monorepo 中,每次提交时一切都协同工作。共享库中的破坏性变更和每个消费者中的修复都在同一个 PR 中落地。
规范执行:每个仓库对工具、依赖、代码结构和文档做出自己的选择。执行组织标准意味着维护每个仓库的单独配置和审查流程。而在 Monorepo 中,组织规则集中一处并随处应用:代码风格、依赖策略、仓库结构、文档标准。工具可以自动执行约束。
AI 时代的加剧效应:这些成本在 AI 时代变得更加严重——仓库边界成为 AI 智能体无法跨越的墙,它们只能依赖文档和规格说明,而非实际的实现代码。仓库边界对人类和 AI 助手都是墙。AI 智能体无法看到仓库边界之外,必须依赖规格和文档而不是实际实现。
AI 智能体的效果取决于它们能访问的上下文。在 Monorepo 中,AI 智能体获得了四大关键优势:
1. 完全可见性
2. 上下文自由流动
3. 跨项目工作
4. 即时反馈循环
总结:完全可见性、自主可发现的上下文和即时反馈循环:充分利用 AI 智能体的要素。
当将所有仓库合并为单一 Monorepo 不可行时,合成 Monorepo 提供了一个折中方案:它将独立的仓库连接成统一的依赖图,而不移动任何代码。
一个合成 Monorepo将独立仓库连接成统一的依赖图而不移动任何代码。哪个仓库依赖哪个、变更影响什么下游、项目如何跨团队关联:所有这些都自动变得可见。
核心价值:
平台工程师获得跨边界的合规检查和影响分析。AI 智能体获得仓库如何关联的地图,因此它们可以推理和协调跨整个组织的变更,而不是一次一个仓库。
核心洞察:合成 Monorepo 不拆除仓库之间的墙。它在墙中创建隧道,给人类和 AI 智能体有效跨边界工作的可见性。
在合成 Monorepo 中,一个协调智能体可以跨越仓库边界工作。
工作流程:
本地优化导致全局吞吐量下降:这是一个制造业的著名原则。当管道的一部分运行得快,工作在边界堆积。同样适用于跨 Polyrepo 工作的 AI 智能体。每个智能体在其自己的仓库内加速工作,但对边界之外存在什么零可见性。在这些快速、隔离的智能体之间坐着人类传递上下文。这破坏了智能体自主性。
有了合成 Monorepo,一个协调智能体读取跨仓库图,生成每个仓库的智能体,并自动在它们之间传递上下文。它遍历上游和下游依赖以确定哪些仓库实际上受变更影响,并只在需要操作的地方生成智能体。这解锁了跨仓库的完整 PR 生命周期:提交变更作为协调的 PR,监控每个仓库的 CI,并以完全自主运行直到一切变绿。这是你能得到的最接近真正 Monorepo 的原子提交,而不移动任何代码。
大多数组织不会将所有代码合并到单一 Monorepo,他们也不必这样做。每当你不能或不想合并仓库,但仍需要跨它们的统一视图时,合成 Monorepo 是正确的选择。
主要适用场景:
渐进迁移路径:从合成开始,逐步将相关仓库合并为真正的 Monorepo。按自己的节奏移动,而不是规划一次性迁移。
外部供应商和第三方:当系统的部分由外部团队或供应商构建,由于可见性或法律原因不能在你的 Monorepo 内时,合成 Monorepo 仍然给你跨边界的图和协调。
开源与闭源并存:保持你的开源项目作为公共仓库和内部代码私有,同时仍然通过单一图推理依赖、影响和跨仓库变更。
市面上有多款优秀的 Monorepo 工具,各有不同的设计哲学和适用场景。我们尽力客观地代表每个工具,如果我们弄错了什么,欢迎提交 PR!
| 工具 | 开发者 | 核心定位 |
|---|---|---|
| Bazel | 快速、可扩展、多语言构建系统 | |
| Gradle | Gradle Inc | 灵活的多语言构建系统 |
| Lage | Microsoft | JS Monorepo 任务运行器 |
| Lerna | Nx 团队维护 | JavaScript 多包管理工具 |
| moon | moonrepo | Web 生态的任务运行器和管理工具 |
| Nx | Nx | 为开发者和 AI 智能体优化的构建系统 |
| Pants | Pants Build | 用户友好的多语言构建系统 |
| Rush | Microsoft | 大型 Monorepo 的企业级解决方案 |
| Turborepo | Vercel | JS/TS 高性能构建系统 |
各工具简介:
| 功能 | Bazel | Gradle | Lage | Lerna | moon | Nx | Pants | Rush | Turborepo |
|---|---|---|---|---|---|---|---|---|---|
| 本地计算缓存 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| 本地任务编排 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| 分布式计算缓存 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| 分布式任务执行 | ✅ | ⚠️ | ❌ | ✅ | ❌ | ✅ | ✅ | ⚠️ | ❌ |
| 透明远程执行 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
| 受影响项目检测 | ⚠️ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| 任务拆分 | ⚠️ | ✅ | ❌ | ✅ | ❌ | ✅ | ⚠️ | ❌ | ❌ |
| 测试去抖动 | ⚠️ | ✅ | ❌ | ✅ | ❌ | ✅ | ⚠️ | ❌ | ❌ |
✅ = 原生支持 | ⚠️ = 需自行实现或有限支持 | ❌ = 不支持
功能说明:
| 功能 | Bazel | Gradle | Lage | Lerna | moon | Nx | Pants | Rush | Turborepo |
|---|---|---|---|---|---|---|---|---|---|
| MCP Server | 🌐 | ✅ | ❌ | ❌ | ✅ | ✅ | 🌐 | ✅ | 🌐 |
| AI Skills | ❌ | ❌ | ❌ | ❌ | 🌐 | ✅ | ❌ | ❌ | ✅ |
| 工作区分析 | 🌐 | 🌐 | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ |
| AI 任务执行 | 🌐 | 🌐 | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ | ❌ |
| 智能体 CI | ❌ | ⚠️ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
✅ = 原生支持 | 🌐 = 社区提供 | ⚠️ = 需自行实现 | ❌ = 不支持
功能说明:
| 功能 | Bazel | Gradle | Lage | Lerna | moon | Nx | Pants | Rush | Turborepo |
|---|---|---|---|---|---|---|---|---|---|
| 工作区分析 | ⚠️ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| 项目图可视化 | ✅ | ⚠️ | ⚠️ | ✅ | ✅ | ✅ | ⚠️ | ⚠️ | ✅ |
| 功能 | Bazel | Gradle | Lage | Lerna | moon | Nx | Pants | Rush | Turborepo |
|---|---|---|---|---|---|---|---|---|---|
| 源代码共享 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| 多语言支持 | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
| 代码生成 | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ✅ | ✅ | ✅ | ⚠️ | ⚠️ |
| 项目约束和可见性 | ✅ | ⚠️ | ⚠️ | ⚠️ | ✅ | ✅ | ⚠️ | ✅ | ⚠️ |
根据不同需求选择合适的工具:
按规模选择:
按技术栈选择:
按功能需求选择:
TypeScript 已成为前端和后端开发的标准,但 TypeScript Monorepo 有其独特的挑战。随着 TypeScript 项目越来越成为开发前端和后端应用程序的标准方式,标准化项目设置的需求也增加了。实现这一点的一种方法是在你的组织中采用 Monorepo。然而,对于 TypeScript 项目,有独特的挑战可能会显著影响你的开发体验。
从代码托管到模块化 Monorepo,需要经历三个阶段:路径别名、工作区和项目引用。通过了解这些概念如何与你的 TypeScript Monorepo 相关,你可以更好地组织代码库并保持构建快速。
路径别名允许用简短的关键词替代长导入路径:
typescript// 传统导入
import { something } from '../../../libs/lib-a/src/index';
// 路径别名
import { something } from '@my-org/lib-a';
路径别名是 TypeScript 的一个构造,允许你指向代码库中某处的目录。当你从 @my-org/lib-a 导入时,使用路径别名,你实际上并没有将该包视为模块。
局限与注意事项:
工作区是大多数 JavaScript 包管理器的内置功能,允许你告诉包管理器某个目录包含子项目。像 npm、yarn 和 pnpm 这样的包管理器都提供设置工作区的方式。
工作区的优势:
使用要求: 工作区确实需要在包可以在你的 Monorepo 中使用之前有一个额外步骤。由于大多数包在能够消费代码之前需要某种构建步骤,你选择的 Monorepo 工具在这里会有很大影响。例如,有了 Nx,你可以在启动主进程之前执行依赖项目的构建步骤。或者,你必须在启动任何其他进程之前手动构建包。
两种代码共享方式:
任一选项都可以工作,主要因素是你是否将库发布给 Monorepo 外的其他人使用。
随着转向工作区,一件事被遗留:我们的项目可以提供的详细类型信息。路径别名也有这个问题,因为深度嵌套的导入可能不会总是从 TypeScript 获得正确的类型信息。我们可以通过利用项目引用来帮助 TypeScript。
项目引用是对工作区中嵌套 tsconfig.json 的引用。通过提供它们,你可以通知 TypeScript 编译器关于任何嵌套项目和其中存在的类型。现在 TypeScript 将能够将每个项目视为其自己的隔离代码片段,并可以更好地优化它为你的编辑器返回任何类型信息的方式。
项目引用的优势:
除了类型好处,TypeScript 现可以在更好的隔离中编译代码库的片段。在最宽松的意义上,项目引用将 TypeScript 编译器转变为 Monorepo 工具。
潜在问题: 项目引用在包含大量类型的 Monorepo 中可能有一些缺点。例如,trpc 可以为 API 中的每个路由生成类型信息。通常这很好,项目引用可以确保该类型信息对你可用。但如果你有大型 API,当尝试触发自动完成或获取符号类型时,你可能在编辑器中面临显著延迟。这不仅限于 trpc,而是任何有大型和复杂类型设置的 Monorepo。
你如何设置 TypeScript Monorepo 不仅影响开发体验,还可能影响仓库中的变更构建和发布到世界的速度。有了包链接和项目引用,Monorepo 的整体构建时间可以显著更快。有了更快的 CI 时间,你可以更快地发货,同时有更好的开发体验。
构建时间对比:
| 配置方式 | 冷构建时间 | 热构建时间 |
|---|---|---|
| 路径别名 | 186.53s | - |
| 引用 + 工作区 | 175.52s | 25.33s |
| 引用 + 工作区(增量更新) | 36.33s(1包)→ 80.69s(100包) | - |
此示例使用 Nx,但其他 Monorepo 工具可能有类似结果。
增量更新的关键优势: 值得指出的是处理增量更新时的时间差异。有了路径别名,TypeScript 需要对每个包执行完整重建。然而,有了项目引用,TypeScript 可以理解哪些包已更改并跳过重建如果可能,减少重建所需的时间。
AI 和 Monorepo 相互提升:Monorepo 为强大的智能体工作流提供统一上下文,而 AI 帮助导航和自动化 Monorepo 复杂性。
Monorepo 为 AI 智能体提供:
Polyrepo vs Monorepo 对比:
Monorepo 有复杂性成本,使人们犹豫是否采用它们。AI 帮助抵消这种复杂性,使 Monorepo 采用更容易。
AI 在 Monorepo 中的应用场景:
| 任务 | AI 的作用 |
|---|---|
| 跨项目变更 | 更新被 40 个项目使用的 API,AI 自动适配每个项目 |
| 导航未知代码库 | AI 探索、解释、连接你从未接触的领域 |
| 理解影响范围 | AI 查询项目图,找到受影响项目,分析失败原因 |
| 跨项目调试 | AI 沿实际依赖链追踪,找到根本原因 |
| 技术债务升级 | AI 处理确定性迁移无法处理的边缘情况 |
| 项目合并 | AI 自动对齐构建配置、解决依赖冲突 |
具体说明:
Monorepo 可能很大。与其让 AI 智能体 grep 数百个项目,Monorepo 工具可以直接暴露工作区结构:哪些项目存在、它们如何关联、可以运行什么任务。结果是更快的发现、更好的理解,以及显著减少浪费在探索上的 token。
项目图:大多数 Monorepo 工具维护项目图:工作区中每个项目及其如何相互依赖的结构化地图。当暴露给 AI 智能体时,它给它们即时架构理解而不读取单个文件。智能体可以查询此图以发现哪些项目存在、追踪依赖链并理解影响范围:如果 lib-api 变更,什么下游应用和库受影响?智能体不是 grep 导入语句,而是在单个结构化调用中获得答案。
项目级知识:AI 智能体不应该解析 Vite 配置、Go Makefiles 和 Python pyproject.toml 以理解项目能做什么。像 Nx 这样的 Monorepo 工具暴露每个项目的结构化元数据:可用任务、缓存输入/输出和配置,都通过像 nx show project 这样的专用命令。一个接口,无论语言或框架。Monorepo 工具规范化复杂性。
架构地图:开发者以领域区域如"shop"、"auth"或"shared infrastructure"推理他们的代码库,但 AI 智能体只看到文件。像 Nx 这样的工具提供标签系统,将项目组分类到这些领域区域,给智能体相同的高级架构地图。有了这个,智能体可以渐进式探索:从领域级别开始以识别相关区域,使用项目图缩小到正确项目,然后只在需要时进入文件系统。
AI 智能体通过迭代工作:变更、检查、调整。结果质量取决于紧密反馈循环、适当的护栏和智能体能迭代多快。Monorepo 工具提供所有三个。
紧密迭代周期:智能体工作更快、产生更好结果并在获得即时反馈时更自主地操作每个变更。没有它,步骤 2 中的错误假设会破坏步骤 3 到 10。Monorepo 工具紧密此循环:只运行受影响任务、未变更工作被缓存,架构约束早期捕获违规。
可预测的代码脚手架:创建新项目不应该浪费 token 从头生成每个文件。使用模板并一次性 stamp 下来,有可预测结果匹配你的代码库约定。像 codemods 和 Nx generators 这样的工具产生一致、约定匹配的输出每次。像 Nx 这样的工具还让你创建自定义本地生成器,为你的工作区定制。你的 AI 智能体可以直接调用这些,获得确定性脚手架而不在样板代码上花费 token。
自主智能体提交更多 PR,更快更频繁。在 Monorepo 中,每个可以触发跨数百个项目的任务。CI 成为瓶颈,侵蚀智能体工程承诺的生产力增益。
分布式任务执行:基于项目图、历史运行数据和任务依赖,自动跨机器分发任务。没有手动 CI 配置。系统决定什么在哪里运行以及以什么顺序,随着智能体驱动的 CI 负载增加最大化并行性。参见 Nx Agents 作为实现示例。
自愈 PR:AI 可以在 CI 层面集成以检测破坏任务并自动修复它们。例如,Nx 的 Self-Healing CI 分类失败、利用关于错误和过去运行的完全上下文提出修复,然后重跑失败任务以验证它实际工作。高置信度的验证修复甚至可以自动应用,无需人工干预。
本地和远程智能体协作:有两个不同的 AI 系统在起作用:一个专门的远程 CI 智能体诊断和修复任务失败,以及本地开发智能体。有了正确的集成层,它们直接通信,所以本地智能体看到 CI 上在任务级别发生什么,不只是 pass/fail 状态检查。
自主解决流程:
随着 AI 智能体变得更强大,Monorepo 工具不能成为瓶颈。它需要为智能体自主设计:一个给结构化、可解析信息的 CLI,由智能体像人类一样自然地可驾驶,并让智能体查询项目、运行任务并对失败采取行动而不卡住。
智能体体验(Ax):AI 智能体应该能够像人类一样有效地导航和操作 CLI。这通常意味着根据调用者适配输出:人类获得可读列表,智能体获得它们可以立即解析和行动的结构化 JSON。但这也意味着清晰的错误消息、可组合命令和智能体可以推理的可预测行为,无需人工指导。
技能和 MCP:
工具对比:Monorepo 工具在 AI 支持方面差异很大,从完全没有集成到完整 MCP 服务器、智能体技能、工作区分析和自愈 CI。参见第四部分 4.2 AI 支持功能对比表。Nx 检查所有框。
无法拥有 Monorepo 的替代方案:一个合成 Monorepo 将相同的项目图和 AI 智能体能力带到该设置,而不移动任何代码。参见第三部分合成 Monorepo 的详细说明。
AI 智能体只有在其能访问的上下文范围内才有效。在单个仓库内工作、没有相关项目可见性、没有依赖图、没有方式运行跨项目任务的智能体将产生隔离的、通常是错误的结果。它很快达到上限。
复合效应:差异复合。每个功能、重构和迁移从该结构优势受益。如果你的团队正在采用 AI 工具但你的代码库没有为它设置,你将大部分价值留在桌上。
Monorepo 移除上限:一个 Monorepo 移除该上限。如果你还没准备好完整合并,一个合成 Monorepo 可以是增量起点。
从哪里开始:Nx 帮助你让你的代码库 AI-ready,无论你是从 Monorepo、Polyrepo或介于两者之间的某处开始。
Monorepo 不是银弹,但在正确的工具支持下,它能显著提升开发效率、代码质量和团队协作。特别是在 AI 驱动的开发时代,Monorepo 的价值更加凸显——它为 AI 智能体提供了必要的上下文和可见性,使得真正的自动化开发成为可能。
如果你的组织正在考虑 Monorepo,建议:
代码库的结构决定了 AI 的上限。一个设计良好的 Monorepo,将为你释放 AI 智能体的全部潜力。
参考资料:


本文作者:蒋固金
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!