🎬开篇
在前端开发中,我们通常会把每个项目放在单独的仓库里(Polyrepo
),比如一个网站、一个后台管理系统、一个组件库各自独立管理。随着团队和项目数量增加,这种方式会带来几个问题:依赖版本不一致、组件复用困难、改动跨项目调试麻烦。
Monorepo
(单仓库管理)的理念就是:把多个相关项目放在同一个仓库里统一管理。简单来说,就是把所有前端子项目、组件库甚至工具脚本都放进一个"大仓库",通过合理的目录结构和工具支持,实现共享代码、统一依赖、跨项目协作更高效。
🛠️ Monorepo vs Polyrepo:工具箱的故事
理解 Monorepo 的最佳方式,就是把它想象成"工具箱"的管理方式:
Polyrepo :每个工人都有自己的一套工具箱。
谁要是螺丝刀坏了,就得自己修或买新的。结果就是,大家手里的工具版本不一,质量参差不齐,还容易重复劳动。
Monorepo :所有工人共享一个大型工具房。
工具集中管理,更新一次,大家都能用上最新的工具。这样既省心又高效,还能保证整个团队的协作一致。
特点 | Polyrepo(多仓模式) | Monorepo(单仓模式) |
---|---|---|
代码仓库 | 每个项目/包都有独立仓库 | 所有项目/包放在一个仓库 |
依赖管理 | 共享依赖需要发布 + 升级版本 | 直接引用本地包即可,实时共享 |
协作方式 | 修改多个仓库要分别提PR,流程繁琐 | 一次提交就能修改多个包,保持一致 |
一致性 | 各仓库可能配置/规范不同 | 中央化配置,规范统一 |
适用场景 | 项目独立度高,生命周期差异大 | 多个项目/包高度相关,常常需要联动 |
学习/维护成本 | 入门简单,维护多个仓库比较麻烦 | 入门稍复杂,但长期维护更高效 |
❓ 大家共用一套工具,会不会发生抢占?
不会的!这里的"共用"并不是大家同时去抢一个螺丝刀,而是像这样:
- 工具集中存放:所有工具都放在统一的工具间(Monorepo 仓库)。
- 各自领取副本:每个人需要时,都会拷贝一套到自己工作台(本地开发环境)。
- 互不干扰:你拧你的螺丝,我敲我的钉子,大家各用各的副本。
- 集中升级:如果工具升级了(依赖更新),只要工具间统一更新,大家下次领到的就是最新版。
👉 所以,Monorepo 的优势在于 集中管理但独立使用 ,不会发生"抢占工具"的情况。Monorepo 的核心价值就是:共享、统一维护、减少重复劳动。
🏗 Monorepo 的实现原理与必要条件
很多同学会好奇:Monorepo 到底是怎么实现的?是不是只是把所有项目丢进一个仓库就行?
其实远不止于此。Monorepo 的核心在于 「统一管理 + 独立构建」。既能集中在一起,又要保持彼此的边界清晰。
实现原理
目录规范化
通常会有 packages/
文件夹,每个子项目或子包都是一个独立目录,每个子包依然有自己的 package.json
,保持边界。
依赖管理统一化
根目录维护一个总的依赖(package.json
+ lock 文件),子包的依赖通过工作区(workspace)机制或工具链接,避免重复安装。
工具支撑
借助工具(如 pnpm workspaces
、yarn workspaces
、lerna
、nx
、turborepo
)实现:本地包互相引用(不用发布 npm 就能共享);自动化构建、测试、发布流程。
隔离与共享的平衡
每个子包仍然是独立单元,可以单独构建/测试/发布;公共依赖、配置文件、CI/CD 脚本在根目录共享。
必要条件
要跑通一个 Monorepo,有几个前提条件,否则它就会变成一锅粥:
必要条件 | 实现方式 | 工具/示例 |
---|---|---|
依赖管理机制(Workspaces) | 使用包管理工具支持 Workspaces,把各个子包的依赖统一管理,支持跨包引用 | npm Workspaces , Yarn Workspaces , pnpm Workspaces |
独立构建能力 | 每个子包保留自己的 build 脚本,支持单独运行,同时可组合构建整个仓库 | 各子包 package.json 的 build 脚本;配合 tsc --build , vite ,webpack ,rollup |
任务调度/缓存机制 | 通过任务调度工具按依赖顺序执行构建/测试,并缓存中间产物,避免重复执行 | Nx , Turborepo , Bazel |
统一规范(Lint、TS、测试框架等) | 在仓库根目录统一配置 ESLint、Prettier、TSConfig、测试框架,并让子包继承 | 根目录 .eslintrc.js , tsconfig.base.json , jest.config.js |
版本管理/发布机制 | 使用版本管理工具自动生成 changelog、管理依赖包版本、统一发布 | Lerna , Changesets |
pnpm Workspaces 解析
原理
pnpm Workspaces 通过统一的 pnpm-workspace.yaml
文件,把多个子包(package)纳入一个工作区(workspace)管理。
每个子包仍然保留自己的 node_modules
结构,但实际依赖是通过 硬链接(symlink) 指向工作区根目录的统一存储位置(store
),避免重复安装。
跨包引用时,pnpm 会在本地创建symlink
,不需要重新发布到npm
,就可以直接引用本地子包。
配置示例
yaml
# pnpm-workspace.yaml
packages:
- 'packages/*'
- 'libs/*'
使用方式
bash
# 安装工作区所有依赖
pnpm install
# 在子包中添加依赖
pnpm add lodash -w # -w 表示添加到根 workspace
pnpm add lodash --filter @myorg/pkg1 # 只给 pkg1 添加依赖
任务调度 / 缓存机制(Task Runner & Cache)
在 Monorepo 中,随着项目子包数量增加,单纯依赖 pnpm run build
或 npm run test
会变得低效:
问题1:重复执行:每次构建或测试,所有子包都会重新执行,即便它们的源代码没有变化。
问题2:依赖顺序 :子包之间存在依赖关系(比如 ui-components
被 admin-a
,admin-b
引用),如果不按依赖顺序执行,可能导致构建失败。
任务调度工具(Task Runner)+ 缓存机制正是为了解决这两个问题。
原理
Turborepo
是一个高性能的 Monorepo 工具,用于管理多包项目(packages/apps),支持增量构建、任务缓存和并行执行。 Turborepo 会根据 包之间的依赖关系(package.json
的dependencies
)生成 DAG(有向无环图),并按拓扑顺序执行任务。
假设依赖关系是 @myorg/ui → admin-a → admin-b
在再次构建时,如果
@myorg/ui
没有改动,admin-a
也没改动,admin-b
依赖admin-a
,那么Turborepo
会检查缓存:如果缓存存在,则跳过构建;否则按顺序执行任务。
特性 | 说明 | 示例/作用 |
---|---|---|
多项目管理 | 支持管理多个子项目(packages/apps),统一执行任务 | admin-a、admin-b、@myorg/ui |
增量构建 | 只重新构建受影响的子项目,未改动的子项目复用缓存 | 修改 @myorg/ui,只重建依赖它的 admin-a |
本地缓存 | 将构建产物保存在本地,重复运行相同任务可直接复用 | 本地 turbo run build 后再次运行无需重新构建 |
远程缓存 | 将构建产物上传到远程仓库/服务,团队成员可共享缓存 | CI/CD 环境下无需重复构建相同任务 |
并行执行 | 按依赖顺序并行执行任务,提升流水线效率 | build、test 可以同时执行多个互不依赖的包 |
依赖感知 | 自动分析包之间依赖关系,按依赖顺序执行任务 | admin-a 依赖 @myorg/ui,先构建 @myorg/ui 再构建 admin-a |
输出追踪 | 指定 outputs(构建产物路径),Turbo 判断是否缓存命中 | "outputs": ["dist/**"] |
环境变量控制 | 通过 env 或命令行控制 NODE_ENV 等环境,确保构建一致性 | NODE_ENV=production turbo run build |
使用方式
bash
# 安装工作区所有依赖
pnpm add -Dw turbo
json
# turbo.json
{
"$schema": "https://turborepo.org/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"lint": {},
"test": {
"dependsOn": ["build"]
}
}
}

版本管理/发布机制
功能模块 | 实现原理 | 工具/配置 |
---|---|---|
版本管理 | 扫描 Monorepo 中子包的 package.json ,识别变更包;根据变更类型生成语义化版本号(SemVer);自动更新子包版本号并管理依赖关系 |
Lerna, Changesets |
Changelog自动生成 | 根据 commit 信息(通常遵循 Conventional Commits 规范)生成可读更新日志;提供每个子包的变更摘要 | Changesets, Conventional Commits |
统一发布 | 将需要发布的包统一打包并发布到 npm 或私有 registry;支持依赖顺序发布,保证基础包先发布,依赖包后发布 | Lerna, Changesets, npm / 私有 registry |
使用
pnpm
+Changesets
可以实现版本管理和日志自动生成。如果有npm包发布,可能需要依赖Lerna,需要根据具体情况使用工具和配置。
💡 实际项目基本结构和使用方法
bash
monorepo-root/
├─ apps/
│ ├─ admin-a/ # Vue3 项目A
│ ├─ admin-b/ # Vue3 项目B
│ └─ ... # 更多项目
├─ packages/
│ ├─ ui/ # 共享UI组件库
│ ├─ request/ # request请求工具库
│ └─ ... # 更多工具或UI库
├─ package.json
└─ pnpm-workspace.yaml
apps:业务应用,每个项目独立运行、独立打包
packages:共享模块/组件库,比如 UI 库、工具库
根目录:管理依赖、统一构建、统一版本
packages项目(ui / request)
模块 | 功能 | 说明 |
---|---|---|
UI 组件库 | 封装 Element Plus 常用组件 | 提供统一接口,简化项目开发流程 |
集成 TailwindCSS 实现主题定制 | 保持风格统一,便于快速构建 UI | |
提供基础组件(Button、Form、Table 等) | 可直接复用,减少重复开发 | |
请求工具库 | 封装网络请求方法 | 提供统一请求接口,方便调用和管理 |
支持拦截器、统一错误处理 | 提高代码可维护性与稳定性 | |
支持不同环境配置 | 可灵活适配开发、测试和生产环境 |
apps项目(admin-a / admin-b)
模块 | 内容 | 说明 |
---|---|---|
依赖 UI 库 | import { MyTable } from 'ui' | 组件库改动 → 自动在 Apps 中生效 |
项目独立性 | 独立运行 | admin-a/admin-b 各自独立,互不影响 |
自定义配置 | 可自定义主题、路由、业务逻辑 | |
共享依赖管理 | 统一依赖 | 通过 pnpm-workspace.yaml 管理根目录依赖版本,避免冲突 |
在 Monorepo 项目里,通用的 UI 组件库和工具包一般放在 packages 目录,方便多个项目复用和统一维护;业务应用则放在 apps 目录。各项目整合这些组件和工具包的方式可以根据实际情况灵活选择:可以直接在项目中引用本地子包实现即时更新,也可以通过打包发布后安装依赖来使用。这样既保证了开发效率,又能保持版本和管理的规范性。