Monorepo 是什么?
Monorepo 就是将多个相关项目的代码,放在同一个代码仓库中进行管理。通过牺牲一定的仓库隔离性,换来了极高的开发协同效率和一致性。
优缺点
-
优点
代码复用性高:公共组件、工具函数、配置可以轻松在项目间共享。
简化依赖管理:特别是处理内部互相依赖的包时,可以避免“依赖地狱”。
统一的工具链:ESLint, Prettier, 测试框架,构建工具等可以全局统一配置,保证一致性。
原子更改:修改一个底层库时,可以同时更新所有依赖它的项目,并确保一次提交就能完成,易于回滚和追溯。
更简单的 CI/CD:理论上可以设置统一的流水线,但需要精心设计以优化构建速度。 -
缺点
仓库体积巨大:随着时间推移,仓库会变得非常庞大,对 Git 操作和 IDE 性能是挑战。
权限控制复杂:如何确保团队只能访问其负责的模块,需要额外的工具和策略。
构建和测试优化:需要智能的工具(如 Turborepo, Nx, Lage)来支持增量构建和缓存,避免每次全量构建。
对工具链要求高:需要搭配专业的 Monorepo 管理工具才能发挥其优势。
常用的 Monorepo 管理工具
- 基于 NPM/Yarn/PNPM 的工作区:
PNPM Workspace:目前非常流行,利用硬链接节省磁盘空间,依赖管理高效。
Yarn Workspaces:Yarn 内置的 Monorepo 支持。
NPM Workspaces:NPM v7+ 开始内置。 - 高级构建系统:
Turborepo:Vercel 出品,主打极快的增量构建和智能缓存。
Nx:功能非常强大,提供代码生成、依赖图可视化、分布式任务执行等。
Rush:Microsoft 出品,专注于大规模 Monorepo 管理。
如何使用?
- 初始化项目
mkdir my-monorepo-demo
cd my-monorepo-demo
echo "packages:
- 'packages/*'
- 'apps/*'" > pnpm-workspace.yaml
- 创建子包(共享包)
mkdir packages
cd packages
mkdir ui
cd ui
pnpm init
//手动将包名改为 @my-repo/ui
- 创建应用
cd ../.. // 回到根目录
mkdir apps
pnpm create next-app@latest apps/web-app --typescript --tailwind --app --no-eslint
- 安装 Turborepo(构建系统)
pnpm add -Dw turbo
echo>turbo.json // 创建文件
编辑 turbo.json
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
// 一个包(包A)的构建任务,依赖于其依赖包(包B)的 `build` 任务先完成
"dependsOn": ["^build"],
// 此任务的输出(如 dist 目录)应该被缓存
"outputs": ["dist/**", ".next/**"]
},
"dev": {
// dev 任务默认不缓存
"cache": false
},
"lint": {
// lint 任务可以并行执行,无特殊依赖
"outputs": []
},
"test": {
// test 任务依赖于 `build` 任务
"dependsOn": ["build"],
"outputs": []
}
}
}
更新根目录的 package.json
{
"name": "my-monorepo-demo",
"private": true,
"packageManager": "pnpm@9.0.0",
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"lint": "turbo run lint",
"test": "turbo run test"
},
"devDependencies": {
"turbo": "latest"
}
}
- 建立包之间的依赖关系
编辑 apps/web-app/package.json
("workspace:*"是 PNPM Workspace 的协议,表示直接链接工作区内的本地包,而不是从 npm 仓库下载。)
{
"dependencies": {
"@my-repo/ui-button": "workspace:*",
"@my-repo/utils": "workspace:*"
}
}
- 统一安装所有依赖并运行
# 在项目根目录执行,这将安装所有 packages 和 apps 下的依赖
pnpm install
# 运行开发命令,Turborepo 会并行启动所有包的 `dev` 脚本(如果定义了的话)
pnpm dev
# 或者,运行构建命令。Turborepo 会智能地分析依赖图:
# 1. 先构建没有任何内部依赖的包(如 utils)。
# 2. 再构建依赖了 utils 的包(如 ui)。
# 3. 最后构建应用(web-app),因为它依赖了前两个包。
# 并且会缓存结果,下次构建时跳过未变更的部分。
pnpm build
monorepo 和 npm link
-
区别
Monorepo 为了系统性解决代码共享和项目管理
npm link 是临时的开发调试工具。
-
何时使用哪个?
a. 使用 npm link 的情况:
快速测试一个第三方库的本地修改
在独立仓库之间进行临时代码调试
你没有权限或不需要重构项目结构
一次性、短期的测试需求
b. 使用 Monorepo 的情况:
长期维护多个相互依赖的项目
需要跨项目统一代码规范、工具链
团队协作,需要原子提交和统一的 CI/CD
项目间共享大量代码和配置
有长期规划的项目群
项目权限管理
因为所有代码都在同一个仓库里,如何确保团队成员只能访问和修改他们负责的部分,是 Monorepo 落地企业级场景必须解决的问题
- 解决方案 A:基于路径的代码所有权
GitHub Code Owners 示例:
# .github/CODEOWNERS
# 前端团队拥有前端代码
/apps/web-app/* @org/frontend-team
/apps/admin/* @org/frontend-team
# 后端团队拥有后端代码
/apps/api/* @org/backend-team
/packages/api-sdk/* @org/backend-team
# 基础架构团队拥有共享配置
/packages/config/* @org/platform-team
/tooling/* @org/platform-team
# 核心包需要多个团队审批
/packages/ui/* @org/frontend-team @org/design-system-team
创建 Pull Request
自动要求 @org/frontend-team 审批
- 解决方案 B:分支保护规则
# GitHub 分支保护规则示例
# 保护 main 分支:
rules:
- branch: "main"
# 必须通过 CI 检查
checks: ["ci/build", "ci/test"]
# 必须经过 review
reviews: 1
# 不同路径需要不同团队的 review
path_rules:
- paths: ["apps/frontend/**"]
required_approving_review_count: 1
required_reviewers: ["frontend-team"]
- paths: ["apps/backend/**"]
required_approving_review_count: 1
required_reviewers: ["backend-team"]
- paths: ["packages/shared/**"]
required_approving_review_count: 2
required_reviewers: ["frontend-team", "backend-team"]
评论区