Skip to content

Monorepo 在 Atom Video 项目中的实战体会

INFO

本文介绍我在Atom Video项目实践学习monorepo的感悟

一、为什么我们选择 Monorepo?

一开始,其实是因为听说 Monorepo 已经成为世界范围内主流的代码管理模式,不少大厂(像 Google、Facebook)也都在用。所以在 Atom Video 这个项目里,我们也想趁机尝试一下,顺便提升项目整体的工程化水平。 刚好,Atom Video 这个项目本身也非常适配 Monorepo:它既包含前端、后端、转码、数据模型,又需要前后端之间保持紧密联动。

不过,老实说,最开始我们对 Monorepo 的理解还比较粗浅——知道它可以「用单个仓库管理多个项目」,也大概知道像 pnpm workspace 这样的工具属于 Monorepo 范畴。但那时候,我们只是简单地用 pnpm workspacefrontendbackend 的依赖链接在一起,更多是为了方便依赖管理,还没有真正挖掘出 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 缺乏任务缓存依赖追踪机制导致的短板。对比下来,像 TurboRepoNx 这样的现代 Monorepo 工具,原生支持缓存和追踪,可能会更适合后续升级迭代。

  • 🔄 依赖版本一致性问题:虽然 pnpm workspace 自带软链接,理论上依赖能自动对齐,但实际上如果某个模块偷偷装了个不匹配的版本,整个 node_modules 就可能直接「炸锅」。后来我们统一开启了 pnpm 的 overridesstrict-peer-dependencies,才把依赖地基稳稳筑牢

  • 🌀 模块间类型同步困扰:因为我们所有模块都用 TypeScript,类型统一成了生死大事。起初没做好同步机制,经常出现「前端后端类型不一致」的尴尬局面。后来专门抽出一个独立的 types/ 包,所有模块强制引用同一套类型,才彻底理顺了类型共享这条链路。

这些坑,有些是工具层的限制,有些是团队协作中的细节疏忽。但好在,我们一一踩过、也一一填平了。

四、Monorepo 带来的实打实收益

等这些坑踩完、填完,Monorepo 的好处就开始肉眼可见地显现了:

  • 🚀 跨模块联动效率飙升:我在 utils 包里改了一个视频转码函数,frontendbackend 立刻就能拿到最新版本,不用等发布 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的价值,完全统一数据模型
  • 微前端架构实践

One Day We Will All Be Famous!