前言
随着业务复杂度提升,前端项目从"单兵作战"走向"集团军作战"。代码仓库管理方式也从 Multi-repo (多仓)逐渐转向 Monorepo(单仓多包)。
本文将带你深入理解 Monorepo 的核心理念,并通过实战演示如何用现代工具链搭建企业级 Monorepo 架构。
一、为什么需要 Monorepo?
1.1 Multi-repo 的痛点
项目 A(组件库) ──┐
├── 版本不一致 → 依赖冲突
项目 B(业务线) ──┘
项目 C(工具库) ──┐
├── 重复配置 → CI/CD 维护噩梦
项目 D(业务线) ──┘
典型问题:
- 🔴 组件库更新后,各业务线升级周期不一致
- 🔴 公共配置(eslint、tsconfig)散落在各仓库
- 🔴 跨项目重构需要提交 N 个 PR
- 🔴 无法原子化提交跨项目改动
1.2 Monorepo 的优势
| 维度 | Multi-repo | Monorepo |
|---|---|---|
| 代码共享 | npm 包发布,版本滞后 | 源码引用,实时同步 |
| 原子提交 | 多个 PR,难以保证一致性 | 单 Commit,全量原子化 |
| 重构成本 | 高(跨仓库改动能改哭) | 低(IDE 全局重构) |
| CI/CD | 多套配置,维护困难 | 统一配置,批量构建 |
| 依赖管理 | 版本分散,冲突频发 | 统一调度,自动去重 |
适合 Monorepo 的场景:
- ✅ 大型前端应用(多业务线、多团队)
- ✅ 组件库 + 文档 + 示例项目一体化
- ✅ 工具链集合(eslint、cli、utils 等)
二、Monorepo 工具选型
| 工具 | 特点 | 适用场景 |
|---|---|---|
| Turborepo | Vercel 出品,极速构建,远程缓存 | 现代前端项目首选 |
| Nx | 功能全面,生态丰富,插件多 | 大型企业级项目 |
| Rush Stack | 微软出品,企业级规范 | 超大规模仓库 |
| Lerna | 老牌工具,维护 mode 活跃 | 简单场景或配合其他工具 |
| pnpm workspace | 轻量,依赖管理优秀 | 小型项目或作为基础 |
2025 年推荐组合:
- 🥇 Turborepo + pnpm workspace(现代项目首选)
- 🥈 Nx(需要丰富插件生态时)
三、实战:搭建企业级 Monorepo
3.1 项目结构设计
my-monorepo/
├── apps/ # 应用层
│ ├── web-admin/ # 管理后台
│ ├── web-portal/ # 门户站点
│ └── mobile-h5/ # H5 页面
├── packages/ # 共享包
│ ├── ui/ # 组件库
│ ├── utils/ # 工具函数
│ ├── hooks/ # 共享 Hooks
│ ├── eslint-config/ # ESLint 配置
│ ├── tsconfig/ # TS 配置
│ └── tailwind-config/ # Tailwind 配置
├── tooling/ # 工程化脚本
│ ├── scripts/
│ └── github-actions/
├── turbo.json # Turborepo 配置
├── pnpm-workspace.yaml # pnpm 工作区
└── package.json
3.2 初始化配置
package.json:
{
"name": "my-monorepo",
"private": true,
"packageManager": "pnpm@9.0.0",
"scripts": {
"dev": "turbo run dev",
"build": "turbo run build",
"lint": "turbo run lint",
"test": "turbo run test",
"clean": "turbo run clean && rm -rf node_modules",
"changeset": "changeset",
"version-packages": "changeset version",
"release": "changeset publish"
},
"devDependencies": {
"@changesets/cli": "^2.27.0",
"turbo": "^2.0.0",
"typescript": "^5.4.0"
}
}
pnpm-workspace.yaml:
packages:
- 'apps/*'
- 'packages/*'
- 'tooling/*'
turbo.json(Pipeline 配置):
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"lint": {
"dependsOn": ["^lint"]
},
"test": {
"dependsOn": ["^test"]
},
"dev": {
"cache": false,
"persistent": true
},
"clean": {
"cache": false
}
}
}
关键配置解析:
^build:依赖项需要先构建(^ 表示上游依赖)dependsOn:任务依赖关系outputs:缓存目录,命中缓存可提速 90%+
3.3 共享包开发
packages/ui/package.json:
{
"name": "@myrepo/ui",
"version": "0.0.1",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": ["dist"],
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts",
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
"lint": "eslint src/",
"clean": "rm -rf dist"
},
"devDependencies": {
"@myrepo/eslint-config": "workspace:*",
"@myrepo/tsconfig": "workspace:*",
"tsup": "^8.0.0",
"typescript": "^5.4.0"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
}
注意: workspace:* 表示使用工作区内的包版本,pnpm 会自动创建软链接。
packages/ui/src/components/Button.tsx:
import { forwardRef } from 'react';
import { cn } from '@myrepo/utils'; // 引用另一个 workspace 包
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant = 'primary', size = 'md', ...props }, ref) => {
return (
<button
ref={ref}
className={cn(
'inline-flex items-center justify-center rounded-md font-medium',
'transition-colors focus-visible:outline-none',
{
'bg-blue-600 text-white hover:bg-blue-700': variant === 'primary',
'bg-gray-200 text-gray-900 hover:bg-gray-300': variant === 'secondary',
'bg-red-600 text-white hover:bg-red-700': variant === 'danger',
'h-8 px-3 text-sm': size === 'sm',
'h-10 px-4 text-base': size === 'md',
'h-12 px-6 text-lg': size === 'lg',
},
className
)}
{...props}
/>
);
}
);
Button.displayName = 'Button';
3.4 应用层引用
apps/web-admin/package.json:
{
"name": "@myrepo/web-admin",
"dependencies": {
"@myrepo/ui": "workspace:*",
"@myrepo/utils": "workspace:*",
"@myrepo/hooks": "workspace:*"
}
}
apps/web-admin/src/pages/index.tsx:
import { Button } from '@myrepo/ui';
export default function Home() {
return (
<div>
<h1>管理后台</h1>
<Button variant="primary" size="lg">
提交
</Button>
</div>
);
}
四、进阶技巧
4.1 依赖自动同步
使用 manypkg 检查和修复 workspace 依赖版本不一致:
pnpm add -D @manypkg/cli
# package.json 添加脚本
"check-packages": "manypkg check",
"fix-packages": "manypkg fix"
4.2 版本管理与发布
使用 Changesets 实现自动化版本管理和 Changelog 生成:
pnpm add -D @changesets/cli
pnpm changeset init
发布流程:
# 1. 添加变更集
pnpm changeset
# 2. 提升版本(自动更新 package.json 和 CHANGELOG)
pnpm version-packages
# 3. 发布到 npm
pnpm release
4.3 远程缓存配置
Turborepo 支持远程缓存,团队成员共享构建结果:
// turbo.json
{
"remoteCache": {
"signature": true
}
}
Vercel 远程缓存(免费):
# 登录 Vercel 账号
npx turbo login
# 链接远程缓存
npx turbo link
效果: 只要团队有人构建过,其他人直接下载缓存,秒级构建。
4.4 按需构建优化
大型 Monorepo 中,不是所有包都需要重新构建:
// turbo.json
{
"pipeline": {
"build": {
"inputs": ["src/**/*", "tsup.config.ts"],
"outputs": ["dist/**"]
}
}
}
Turborepo 会自动:
- 检测文件变动
- 仅重新构建受影响的包
- 利用缓存跳过未变更的包
五、常见问题与解决方案
Q1: 安装依赖变慢?
# 使用 pnpm 的 hoisted 模式
echo 'node-linker=hoisted' >> .npmrc
# 或使用按需安装
pnpm install --frozen-lockfile
Q2: TypeScript 类型不识别?
// apps/web-admin/tsconfig.json
{
"extends": "@myrepo/tsconfig/base.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@myrepo/*": ["../../packages/*/src"]
}
}
}
Q3: 循环依赖检测?
pnpm add -D madge
# 检测循环依赖
madge --circular packages/*/src
Q4: IDE 跳转失效?
VS Code 需要安装 Monorepo Workspace 插件,或在 .vscode/settings.json 中配置:
{
"typescript.preferences.importModuleSpecifier": "shortest"
}
六、真实项目数据对比
某中台团队迁移到 Monorepo 后的收益:
| 指标 | 迁移前(Multi-repo) | 迁移后(Monorepo) | 提升 |
|---|---|---|---|
| 新需求开发周期 | 5 天 | 3 天 | ⬇️ 40% |
| 组件库升级成本 | 2 天/项目 | 实时同步 | ⬇️ 95% |
| CI 构建时间 | 45 分钟 | 12 分钟(缓存命中) | ⬇️ 73% |
| 跨项目重构 PR 数 | 15 个 | 1 个 | ⬇️ 93% |
| 新人上手时间 | 2 周 | 3 天 | ⬇️ 79% |
七、Monorepo 最佳实践 checklist
架构层面:
- 清晰的目录结构(apps / packages / tooling)
- 统一的技术栈(Node 版本、包管理器)
- 合理的包粒度(避免过度拆分)
工程化:
- 统一的 ESLint / Prettier / TSConfig
- 配置 Changesets 版本管理
- 配置 Turborepo Pipeline 缓存
- CI/CD 集成(GitHub Actions / GitLab CI)
协作规范:
- 提交信息规范(Conventional Commits)
- Code Review 流程
- 发布审批流程
📝 总结
Monorepo 不是银弹,但在以下场景能发挥巨大价值:
- ✅ 多团队共享组件库和工具
- ✅ 需要频繁跨项目重构
- ✅ 追求极致的构建性能(远程缓存)
关键成功因素:
- 选择适合团队规模的工具(小型 Turborepo,大型 Nx)
- 配置合理的缓存策略
- 建立清晰的协作规范
💡 小建议: 如果仓库规模超过 10GB 或 10万+ 文件,考虑使用 Sparse Checkout 或 Nx Cloud 的分布式构建。