单仓库管理
概述
建立可扩展的单仓库结构,支持多个相互依赖的软件包,同时保持构建效率、依赖管理和部署协调。
何时使用
- 多软件包项目
- 跨服务共享库
- 微服务架构
- 基于插件的系统
- 多应用平台(Web + 移动)
- 工作区依赖管理
- 团队规模化开发
实施示例
1. Npm 工作区配置
{
"name": "monorepo-root",
"version": "1.0.0",
"private": true,
"workspaces": [
"packages/*",
"apps/*"
],
"devDependencies": {
"lerna": "^7.0.0",
"turbo": "^1.10.0"
},
"scripts": {
"lint": "npm run lint -r",
"test": "npm run test -r",
"build": "npm run build -r",
"clean": "npm run clean -r"
}
}
2. Lerna 配置
{
"name": "monorepo-with-lerna",
"version": "1.0.0",
"private": true,
"packages": [
"packages/*",
"apps/*"
],
"command": {
"bootstrap": {
"hoist": true,
"ignore": "@myorg/infra"
},
"publish": {
"conventionalCommits": true,
"createRelease": "github",
"message": "chore(release): publish"
}
}
}
3. Turborepo 配置
{
"turbo": {
"globalDependencies": ["tsconfig.json"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"],
"cache": true
},
"test": {
"dependsOn": ["^build"],
"cache": true,
"outputs": ["coverage/**"]
},
"lint": {
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
}
}
}
}
4. Nx 工作区配置
{
"version": 2,
"projectNameAndRootFormat": "as-provided",
"plugins": [
"@nx/next/plugin",
"@nx/react/plugin",
"@nx/node/plugin"
],
"targetDefaults": {
"build": {
"cache": true,
"inputs": [
"production",
"^production"
]
},
"test": {
"cache": true,
"inputs": [
"default",
"^production"
]
}
}
}
5. 单仓库目录结构
monorepo/
├── packages/
│ ├── core/
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── utils/
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ └── shared/
│ ├── src/
│ ├── package.json
│ └── tsconfig.json
├── apps/
│ ├── web/
│ │ ├── pages/
│ │ ├── package.json
│ │ └── next.config.js
│ ├── api/
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ └── mobile/
│ ├── src/
│ ├── package.json
│ └── app.json
├── tools/
│ ├── scripts/
│ └── generators/
├── lerna.json
├── turbo.json
├── nx.json
├── package.json
├── tsconfig.json
└── .github/workflows/
6. 工作区依赖
{
"name": "@myorg/web-app",
"version": "1.0.0",
"dependencies": {
"@myorg/core": "workspace:*",
"@myorg/shared-ui": "workspace:^",
"@myorg/utils": "workspace:~"
},
"devDependencies": {
"@myorg/test-utils": "workspace:*"
}
}
7. Lerna 命令
# 引导软件包并安装依赖项
lerna bootstrap
# 安装依赖项并提升公共项
lerna bootstrap --hoist
# 创建新版本
lerna version --conventional-commits
# 发布所有更改的软件包
lerna publish from-git
# 在所有软件包中运行命令
lerna exec -- npm run build
# 并行运行命令
lerna exec --parallel -- npm run test
# 列出所有软件包
lerna list
# 显示依赖图
lerna graph
# 在特定软件包中运行脚本
lerna run build --scope="@myorg/core" --include-dependents
8. Turborepo 命令
# 按依赖顺序构建所有软件包
turbo run build
# 使用特定过滤器构建
turbo run build --filter=web --filter=api
# 排除某些软件包构建
turbo run build --filter='!./apps/mobile'
# 带缓存运行测试
turbo run test --cache-dir=.turbo
# 开发模式(无缓存)
turbo run dev --parallel
# 显示执行图
turbo run build --graph
# 分析构建时间
turbo run build --profile=profile.json
9. CI/CD 单仓库
# .github/workflows/monorepo-ci.yml
name: 单仓库 CI
on: [push, pull_request]
jobs:
affected:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: 安装依赖项
run: npm ci
- name: 获取更改的软件包
id: changed
run: |
npx lerna changed --json > changed.json
echo "packages=$(cat changed.json | jq -r '.[].name')" >> $GITHUB_OUTPUT
- name: 构建更改的软件包
run: npx turbo run build --filter='${{ steps.changed.outputs.packages }}'
- name: 测试更改的软件包
run: npx turbo run test --filter='${{ steps.changed.outputs.packages }}'
- name: 检查更改的软件包
run: npx turbo run lint --filter='${{ steps.changed.outputs.packages }}'
10. 跨软件包版本管理
#!/bin/bash
# sync-versions.sh
# 使用 lerna 保持版本同步
lerna version --exact --force-publish
# 或手动同步 package.json 版本
MONOREPO_VERSION=$(jq -r '.version' package.json)
for package in packages/*/package.json; do
jq --arg version "$MONOREPO_VERSION" '.version = $version' "$package" > "$package.tmp"
mv "$package.tmp" "$package"
done
echo "✅ 所有软件包同步到版本 $MONOREPO_VERSION"
最佳实践
✅ 执行
- 使用工作区协议进行依赖
- 实施共享 tsconfig 以保持一致性
- 在 CI/CD 中缓存构建输出
- 在 CI 中过滤软件包以避免不必要的构建
- 提升公共依赖项
- 记录工作区结构
- 使用一致的版本控制策略
- 在工作区中实施预提交钩子
- 测试跨软件包依赖
- 在适当时独立版本控制软件包
❌ 不要
- 创建循环依赖
- 使用工作区软件包的硬编码版本
- 仅更改一个软件包时构建所有软件包
- 忘记更新锁文件
- 忽略工作区边界
- 创建紧密耦合的软件包
- 跳过依赖管理
- 每个软件包使用不同的工具
工作区依赖解析
# workspace:* - 在工作区中使用确切版本
"@myorg/core": "workspace:*"
# workspace:^ - 使用兼容版本
"@myorg/shared": "workspace:^"
# workspace:~ - 使用补丁兼容版本
"@myorg/utils": "workspace:~"