Monorepo 在 Atom Video 项目中的实战体会
INFO
本文介绍我在Atom Video项目实践学习monorepo的感悟
一、为什么我们选择 Monorepo?
一开始,其实是因为听说 Monorepo 已经成为世界范围内主流的代码管理模式,不少大厂(像 Google、Facebook)也都在用。所以在 Atom Video 这个项目里,我们也想趁机尝试一下,顺便提升项目整体的工程化水平。 刚好,Atom Video 这个项目本身也非常适配 Monorepo:它既包含前端、后端、转码、数据模型,又需要前后端之间保持紧密联动。
不过,老实说,最开始我们对 Monorepo 的理解还比较粗浅——知道它可以「用单个仓库管理多个项目」,也大概知道像 pnpm workspace 这样的工具属于 Monorepo 范畴。但那时候,我们只是简单地用 pnpm workspace 把 frontend
和 backend
的依赖链接在一起,更多是为了方便依赖管理,还没有真正挖掘出 Monorepo 的潜力。
后来,随着项目开发不断深入,我们逐渐意识到:Monorepo 远远不止是「统一依赖管理」这么简单。 比如,因为我们前后端都用 TypeScript 编写,所以像 ESLint、Prettier 这样的静态代码规范其实是可以共享的。于是,我们把 ESLint、Prettier 配置和 Husky + Commitlint 的开源提交规范,全部集中放在项目根目录,一次定义,全局生效,前后端自动共享。这时,我第一次真正感受到了 Monorepo 在统一团队规范方面的价值。
接着,随着开发进一步推进,我们发现:数据模型复用成了另一个痛点。像前端的类型校验、后端的数据校验、以及 ORM(对象关系映射)到 PostgreSQL,这些地方其实本质上用的是同一套数据结构。如果每个模块都手动重复定义一遍,不仅效率低,而且容易造成数据模型不统一的问题。 于是,我们在 Monorepo 根目录下新建了一个 packages
共享包,专门用来统一定义数据模型。前端、后端模块再直接导入这个共享包,做到真正的模型复用。这样,前端的类型检查、后端的业务逻辑、以及数据库表结构,全都保持了一致性,也避免了重复脑力劳动。
最后,当我们前后端的 1.0 版本开发接近尾声,开始需要统一对项目进行构建与测试时,Monorepo 再次发挥了它的力量。这时候,我们引入了 Lerna,在根目录下就可以一键对所有模块统一进行打包、测试、格式化等操作,大大提升了批量操作的效率。
可以说,随着项目的发展,我们逐步从「只用来链接依赖」的小白阶段,进化到了真正发挥 Monorepo 体系价值的实战状态。
二、我们使用的 Monorepo 技术栈
在搭建 Monorepo 体系时,我们选择了一套既实用又轻量、适合小团队敏捷开发的技术栈:
- 🔗 pnpm workspace:负责全局依赖管理,天然支持依赖去重、跨模块软链接,安装速度快且稳定。
- 🔄 Lerna:用来批量执行命令,比如一键构建所有模块、运行全量测试、格式化代码等。
- 🌿 Git + GitHub Flow:结合 Monorepo,我们内部设定了「每人负责一个模块」「每日至少一次 commit」「每周模块测试」的协作节奏,保持团队高效对齐。
- 📦 模块划分:
packages/
目录下划分为frontend/
、backend/
、utils/
、types/
、docs/
五大模块,每个成员认领一块,形成了模块负责人制。
整个体系虽然看上去简单,但对于我们这种4人小团队来说,已经足够支撑起复杂的模块协作,保持了高效、独立、频繁联动的开发节奏。
三、实践过程中踩过的坑
实话实说,Monorepo 用起来确实舒服,但它也绝不是「无痛上手」。我们在实战中,也狠狠踩过几个坑:
🚧 Lerna 的缓存机制硬伤:一开始,Lerna 每次执行
lerna run build
,都会把所有包全量重跑——就算我只改了一个utils
模块,也得乖乖等待整个仓库重新构建。后来才发现,这是 Lerna 缺乏任务缓存和依赖追踪机制导致的短板。对比下来,像 TurboRepo 和 Nx 这样的现代 Monorepo 工具,原生支持缓存和追踪,可能会更适合后续升级迭代。🔄 依赖版本一致性问题:虽然 pnpm workspace 自带软链接,理论上依赖能自动对齐,但实际上如果某个模块偷偷装了个不匹配的版本,整个
node_modules
就可能直接「炸锅」。后来我们统一开启了 pnpm 的overrides
和strict-peer-dependencies
,才把依赖地基稳稳筑牢。🌀 模块间类型同步困扰:因为我们所有模块都用 TypeScript,类型统一成了生死大事。起初没做好同步机制,经常出现「前端后端类型不一致」的尴尬局面。后来专门抽出一个独立的
types/
包,所有模块强制引用同一套类型,才彻底理顺了类型共享这条链路。
这些坑,有些是工具层的限制,有些是团队协作中的细节疏忽。但好在,我们一一踩过、也一一填平了。
四、Monorepo 带来的实打实收益
等这些坑踩完、填完,Monorepo 的好处就开始肉眼可见地显现了:
🚀 跨模块联动效率飙升:我在
utils
包里改了一个视频转码函数,frontend
和backend
立刻就能拿到最新版本,不用等发布 npm 包、升级版本、来回测试,一切联动秒同步。👥 团队协作边界清晰:每个人认领自己模块,独立开发、独立测试,互不干扰;但需要统一构建、测试、发布时,又能一键批量操作,模块间协作边界清晰又高效,避免了「你影响我、我拖你」的扯皮状况。
📦 依赖管理成本极低:pnpm workspace 的依赖去重效果惊艳,整个
node_modules
比起传统的 yarn/npm 小了不止一圈,也更稳定,不容易出「幽灵依赖」和版本地狱。🔄 一体化 PR 流程丝滑:所有模块的改动,都走统一的 GitHub PR 流程,代码审查、CI 测试、自动化流程全闭环,让整个项目协作和质量控制更上一个台阶。
说白了,Monorepo 让我们的前后端协作和模块联动变得前所未有的丝滑,让一个 4 人小团队也能打出大项目的开发节奏
五、我的实战反思与建议
回头复盘这次 Atom Video 项目,我最大的感触就是一句话:
Monorepo 很香,但一定要配上对的工具链,它才真的飞起来。
Lerna 虽然帮我们撑起了第一版 Monorepo 架构,但随着项目规模一点点膨胀(比如未来我们可能要加上 App、小程序端),我已经强烈感受到:是时候考虑迁移到 TurboRepo 或 Nx 了。这些工具原生支持增量构建、任务缓存和依赖图管理,能让 Monorepo 在中大型团队里也跑得又快又稳。
也借这个机会,给想尝试 Monorepo 的朋友们提几条我亲踩出来的建议:
- 🔒 一开始就锁死依赖版本!别等到版本冲突堆成「火山」才开始灭火。pnpm 的
overrides
和严格 peer 依赖检查,建议直接开起来。 - 📚 模块边界划清楚。每个包的职责、依赖关系最好在项目初期就定好,避免 Monorepo 后期「越用越乱」变成一锅粥。
- 📝 团队内部要有 commit 规范 + 模块负责人制。多人协作时,秩序感比什么都重要,我们用了
commitlint + husky
,再加上模块认领制,效果很好。 - 🔄 TypeScript 项目一定要单独抽
types/
包。提前规划好类型同步机制,能省下你后期无数的修 bug 和对齐成本。
这些看似「小细节」,实际上正是决定 Monorepo 成败的关键点。
六、结语:Monorepo,让小团队打出大项目节奏
Monorepo 不是万能钥匙,也不是每个团队都非用不可。但对我们 Atom Video 这种模块多、前后端解耦但类型强耦合、团队小但节奏快的场景,它带来的价值是真正「肉眼可见」的。
它帮我们:
- 降低了协作成本
- 加快了开发节奏
- 提升了代码一致性和模块复用率
更重要的是,它让我个人对团队协作和模块化开发这件事,开始有了更系统、更多工程思维的理解。
接下来,我也已经在计划:
- 把这套 Monorepo 实践升级成一个更自动化
- 更缓存友好
- 更完整 CI/CD 一体化的体系
真正把 Atom Video 项目,推向一个专业化、工程化的新高度。
未来计划
后续将更加深入的学习monorepo管理模式,如:
- 深入解析pnpm等工具的原理
- 最大化packages的价值,完全统一数据模型
- 微前端架构实践