skip to content
usubeni fantasy logo Usubeni Fantasy

前端 Monorepo 实践指南:从选择到实现

/ 9 min read

当你想用 monorepo 时,必须要搞清楚一件事,为什么要用 monorepo?

一般来说,只有体量很大的公司会考虑 monorepo,这与组织架构和组织间的协作有关。一个组织的沟通结构、层级关系和协作方式,会在它设计或开发的产品、系统甚至软件架构中被“投射”或“复刻”出来。这就是康威定律,使用 monorepo 很重要的一个原因。

公司内部不同组织可以通过一个大仓库配置所有项目的规范,这个规范不是限制前端框架而是代码格式和 eslint 的统一。不同项目间的依赖也能通过 monorepo 框架生成,一目了然。

另一种情况是开源项目Vue3 的代码库就是一个 monorepo 的例子。Vue 虽不是出自大公司之手,但参与的人数众多,分出独立的不同模块也是一个不错的选择。这样单独的模块可以独立开发,也可以被单独测试,这确保了多人合作下软件质量的稳定性。

选择 monorepo 是有代价的,所以没有充分的理由请不要无脑上 monorepo,哪怕这个理由是干中学,问题也不大,总之不要为了装逼用 monorepo

如果你的公司部门多且联系紧密,monorepo 就显得很必要,但是相反,就一个部门单干,又或者各个部门间都不会互相调用包,那么 monorepo 就很可能不适合你。

monorepo 还有这么一系列缺点:

  • CICD 就变得有点麻烦,不再那么直观
  • 跨包的方法调用虽然不麻烦但肯定也不如单仓库简单
  • 一个大仓库拉下来都是个费时费硬盘的动作
  • monorepo 内的权限不好控制

以上的这一套逻辑,无论优缺点,同样适合微前端,甚至可以说微前端能派上用场的机会比 monorepo 更小。

当你深思熟虑决定要用 monorepo 了,那就进入到下一步,要用什么工具。

workspaces

首先要让你的包管理器适配 monorepo,这里以 pnpm 为例,npm 和 yarn 配置不同,但逻辑类似。

使用 pnpm 的 Workspace 首先要在根目录建一个 pnpm-workspace.yaml 文件:

packages:
# specify a package in a direct subdir of the root
- "my-app"
# all packages in direct subdirs of packages/
- "packages/*"
# all packages in subdirs of components/
- "components/**"
# exclude packages that are inside test directories
- "!**/test/**"
catalog:
chalk: ^4.1.2
catalogs:
react16:
react: ^16.7.0
react-dom: ^16.7.0
react17:
react: ^17.10.0
react-dom: ^17.10.0

这时候你需要运行其中一个包,可以进包目录正常运行,也可以在根目录用 -F 过滤运行:

pnpm -F package-name run dev

如果你依赖了仓库内的包,这次运行就多半会出现问题了。

pnpm workspaces 确实有效解决了外部依赖管理的基本需求。

但是仓库内的包怎么办?仓库内的 A 包依赖 B,你要记得,monorepo 里的可是源码,它不会因为被依赖就自动 build,怎么直接依赖?那难道每次更新都要整体 build 一遍?

确实是可以的,pnpm 可以让你简单粗暴地把所有包都 build 一遍:

pnpm recursive run build

这并非长远之计,尤其是项目非常大的时候(又凑巧,monorepo 一般都很大)。我们需要一个能帮我们自动构建内部依赖的工具,并且最好是只构建当前包依赖的包而不是整个 monorepo。

turborepo

turborepo 就是上一个问题的答案,它可以用于处理任务执行调度、缓存等更高层的构建效率问题。

现有仓库接入 turborepo 不麻烦,可以参考官方文档,简单来说就是先安装 turborepo:

pnpm add turbo --save-dev --ignore-workspace-root-check

然后在根目录创建 turbo.json 文件:

{
"$schema": "https://turborepo.com/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"check-types": {
"dependsOn": ["^check-types"]
},
"dev": {
"dependsOn": ["^build"],
"persistent": true,
"cache": false
}
}
}

关键看 "dependsOn": ["^build"] 这一行,加上之后 build 或者 dev 的时候就能自动构建内部依赖。

最后仓库的结构就类似这样:

my-monorepo/
├── packages/
│ ├── ui-components/
│ ├── utils/
│ └── shared-types/
├── apps/
│ ├── web-app/
│ └── admin-dashboard/
├── pnpm-workspace.yaml
├── turbo.json
└── package.json

也可以参考官方提供的 Example,各种框架各种脚手架的例子都有。

在独立仓库迁移到 monorepo 的过程中,turborepo 基本没有什么强制要修改的地方。现在的 monorepo 迁移要比前 AI 时代简单多了,借助 AI 可以轻松把项目依赖改为内部包名,还能快速把多个仓库的共有依赖抽取为 catalog

turborepo 还支持远程缓存,在多人协作时对于构建时间很长的任务可以直接拉取远程缓存。不过这是收费的,turborepo 毕竟是 vercel 开发的,当然要推销自家产品了。

类似的工具还有 Nx,功能更全面但学习成本更高,还能支持其他语言的多仓库管理。

changesets

changesets 是一个写 changelog 的好帮手,并且它无需额外配置直接支持 monorepo,在一个包更新时,它能帮你顺便更新它的依赖者的 changelog。

pnpm add @changesets/cli --save-dev --ignore-workspace-root-check

初始化后会自动生成一份泛用的配置:

npx changeset init

正式生成更新文档:

npx changeset
# 输出
🦋 Which packages would you like to include? · @mind-elixir/import-freemind
🦋 Which packages should have a major bump? · @mind-elixir/import-freemind
🦋 Please enter a summary for this change (this will be in the changelogs).
🦋 (submit empty line to open external editor)
🦋 Summary · 1.0.0 Released
🦋
🦋 === Summary of changesets ===
🦋 major: @mind-elixir/import-freemind
🦋
🦋 Note: All dependents of these packages that will be incompatible with the new version will be patch bumped when this changeset is applied.
🦋
🦋 Is this your desired changeset? (Y/n) · true
🦋 Changeset added! - you can now commit it
🦋
🦋 warn This Changeset includes a major change and we STRONGLY recommend adding more information to the changeset:
🦋 warn WHAT the breaking change is
🦋 warn WHY the change was made
🦋 warn HOW a consumer should update their code

应用更新文档:

npx changeset version
# 输出
🦋 All files have been updated. Review them and commit at your leisure

对于其他团队协作规范,monorepo 跟单仓库差距不大,可以参考前端代码质量与团队协作终极指南

总结

前端 monorepo 不是银弹,选择它需要基于实际的业务需求和团队规模。记住康威定律:你的架构会反映你的组织结构。

适合使用 monorepo 的场景:

  • 多部门协作且联系紧密
  • 需要统一代码规范和工具链
  • 包之间存在复杂的依赖关系
  • 开源项目需要模块化管理

核心工具链组合:

  • pnpm workspaces:解决依赖管理和包隔离
  • turborepo:处理构建调度和缓存优化
  • changesets:自动化版本管理和 changelog 生成

这套工具链已经足够应对大部分 monorepo 场景,重点是理解每个工具解决的具体问题,而不是盲目追求技术栈的复杂度。

最后,如果你的项目规模还不足以支撑 monorepo 的复杂性,那就继续用单仓库吧。技术选型的核心是解决问题,而不是炫技。

评论组件加载中……