Appearance
Monorepo 入门指南
什么是 Monorepo?
Monorepo(Mono Repository)是一种将多个相关项目的代码存储在单个代码仓库中的软件开发策略。与传统的多仓库(Multirepo)模式相比,Monorepo 允许团队在一个统一的代码库中管理多个项目或包。
Monorepo vs Multirepo
Monorepo 优势:
- 代码复用更简单
- 跨项目变更更容易
- 统一的依赖管理
- 简化的 CI/CD 流程
Multirepo 优势:
- 项目隔离性更好
- 权限管理更简单
- 仓库更小,克隆更快
- 团队自主性更高
何时选择 Monorepo?
适合采用 Monorepo 的场景:
- 多个紧密相关的项目:项目之间有强依赖关系
- 统一的技术栈:多个项目使用相同或相似的技术
- 需要频繁跨项目协作:经常需要同时修改多个项目
- 共享组件库:有大量可复用的组件或工具
不适合采用 Monorepo 的场景:
- 项目完全独立:项目之间没有依赖关系
- 团队完全分离:不同团队完全独立工作
- 安全要求严格:需要严格的访问控制
- 项目生命周期不同:项目维护周期差异很大
选择合适的工具
1. pnpm Workspaces(推荐)
pnpm 是一个高性能的包管理器,特别适合 monorepo 使用:
bash
# 安装 pnpm
npm install -g pnpm
# 初始化工作区
mkdir my-monorepo
cd my-monorepo
pnpm init
# 创建 pnpm-workspace.yaml
echo "packages:
- 'packages/*'
- 'apps/*'" > pnpm-workspace.yaml
2. Yarn Workspaces
Yarn 提供了强大的工作区功能:
bash
# 安装 Yarn
npm install -g yarn
# 初始化项目
yarn init -y
# 启用工作区
# 在 package.json 中添加:
# {
# "private": true,
# "workspaces": ["packages/*"]
# }
3. Nx(全功能解决方案)
Nx 提供了完整的 monorepo 解决方案:
bash
# 创建 Nx 工作区
npx create-nx-workspace@latest
# 或添加 Nx 到现有项目
npm install -D @nx/workspace
npx nx init
创建第一个 Monorepo
1. 项目结构设置
my-monorepo/
├── package.json
├── pnpm-workspace.yaml (或 yarn)
├── packages/
│ ├── ui-components/
│ │ ├── package.json
│ │ └── src/
│ └── utils/
│ ├── package.json
│ └── src/
├── apps/
│ └── web-app/
│ ├── package.json
│ └── src/
└── docs/
2. 初始化工作区
bash
# 创建目录结构
mkdir -p packages/{ui-components,utils}
mkdir -p apps/web-app
# 初始化根 package.json
cat > package.json << EOF
{
"private": true,
"scripts": {
"build": "pnpm --recursive run build",
"test": "pnpm --recursive run test"
},
"devDependencies": {
"typescript": "^4.9.0"
}
}
EOF
# 创建 pnpm-workspace.yaml
cat > pnpm-workspace.yaml << EOF
packages:
- 'packages/*'
- 'apps/*'
EOF
3. 创建第一个包
bash
# 创建 utils 包
mkdir -p packages/utils/src
cat > packages/utils/package.json << EOF
{
"name": "@myorg/utils",
"version": "1.0.0",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"dev": "tsc --watch"
},
"files": [
"dist"
]
}
EOF
cat > packages/utils/src/index.ts << EOF
export function formatDate(date: Date): string {
return date.toLocaleDateString();
}
export function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
EOF
4. 创建使用该包的应用
bash
# 创建 web-app
mkdir -p apps/web-app/src
cat > apps/web-app/package.json << EOF
{
"name": "@myorg/web-app",
"version": "1.0.0",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"dependencies": {
"@myorg/utils": "workspace:*"
}
}
EOF
cat > apps/web-app/src/main.ts << EOF
import { formatDate, capitalize } from '@myorg/utils';
const now = new Date();
console.log(capitalize(formatDate(now)));
EOF
基本操作命令
1. 依赖管理
bash
# 安装依赖到根目录(开发依赖)
pnpm add -D typescript
# 安装依赖到特定包
pnpm --filter @myorg/web-app add react react-dom
# 安装全局依赖到所有包
pnpm --recursive add lodash
2. 运行脚本
bash
# 在所有包中运行脚本
pnpm --recursive run build
# 在特定包中运行脚本
pnpm --filter @myorg/utils run build
# 运行受影响的包(基于 Git 更改)
pnpm --filter "...[origin/main]" run test
3. 包管理
bash
# 查看包列表
pnpm list --depth 0
# 检查依赖问题
pnpm why <package-name>
# 更新依赖
pnpm update --recursive
配置共享
1. TypeScript 配置
json
// tsconfig.base.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"@myorg/utils": ["./packages/utils/src/index.ts"],
"@myorg/ui-components": ["./packages/ui-components/src/index.ts"]
}
}
}
2. ESLint 配置
json
// .eslintrc.base.json
{
"root": true,
"extends": [
"@myorg/eslint-config-base"
],
"settings": {
"import/resolver": {
"node": {
"paths": ["packages"]
}
}
}
}
3. Prettier 配置
json
// .prettierrc
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2
}
CI/CD 集成
1. GitHub Actions 配置
yaml
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x, 18.x]
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
- run: pnpm install
- run: pnpm test
- run: pnpm build
2. 受影响的包检测
bash
# 检测受更改影响的包
npx nx affected:build
npx nx affected:test
npx nx affected:lint
最佳实践
1. 包的组织
- 使用作用域包名(如 @myorg/package-name)
- 保持包职责单一
- 使用一致的命名约定
2. 依赖管理
- 将公共依赖提升到根目录
- 使用工作区协议引用内部包
- 定期同步依赖版本
3. 版本管理
- 使用语义化版本控制
- 实施自动版本发布流程
- 维护清晰的变更日志
4. 测试策略
- 实施分层测试(单元、集成、端到端)
- 使用影子测试优化 CI 时间
- 保持测试独立性
常见陷阱和解决方案
1. 仓库膨胀
问题: 仓库随时间增长变得庞大 解决方案:
- 使用 Git LFS 管理大文件
- 定期清理不必要的历史记录
- 考虑部分克隆策略
2. 依赖冲突
问题: 不同包需要不同版本的相同依赖 解决方案:
- 仔细规划依赖策略
- 使用 resolutions 字段
- 定期同步依赖版本
3. 权限管理
问题: 多团队共享代码库的权限管理 解决方案:
- 使用 CODEOWNERS 文件
- 设置分支保护规则
- 考虑适当的访问控制策略
扩展功能
1. 代码生成
使用 Nx 进行代码生成:
bash
# 生成新的库
nx generate @nx/workspace:library my-lib
# 生成新的应用
nx generate @nx/web:application my-app
2. 性能优化
- 实施增量构建
- 使用分布式缓存
- 优化依赖安装
3. 监控和分析
- 监控构建性能
- 跟踪依赖关系
- 分析代码质量指标
迁移现有项目
1. 评估阶段
- 分析现有项目的依赖关系
- 确定哪些项目适合合并
- 评估团队的准备情况
2. 迁移步骤
- 创建新的 monorepo 结构
- 逐步迁移项目
- 调整依赖关系
- 更新 CI/CD 流程
- 培训团队成员
总结
Monorepo 是一个强大的项目管理策略,特别适合需要频繁跨项目协作的场景。通过选择合适的工具、建立良好的结构和遵循最佳实践,可以充分发挥 Monorepo 的优势。
记住,Monorepo 不是银弹,需要根据项目特点和团队情况谨慎选择。成功的关键在于逐步实施、持续优化和团队协作。