关于 monorepo-management
Monorepo Management 是用于规划、维护和排查多包或多应用代码库的技能,面向管理 monorepo 的开发团队。内容涵盖工作区与构建工具、代码共享、性能、CI/CD 和包发布。
主要功能
- 01工作区设置与包管理
- 02Turborepo 和 Nx 指南
- 03代码与配置共享模式
- 04构建缓存与 CI/CD 实践
- 05版本管理与包发布
适用场景
- 01搭建或迁移 monorepo
- 02提升 monorepo 构建和测试效率
- 03为受影响的项目配置 CI/CD
- 04管理共享依赖和包发布
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ ovachiever/ droid-tings --skill monorepo-management安装后在 Claude Code 中输入/monorepo-management确认已加载。
详细介绍
单仓库管理
构建高效、可扩展的单仓库,实现代码共享、工具统一,并支持跨多个包和应用进行原子化变更。
何时使用此技能
- 搭建新的单仓库项目
- 从多仓库迁移到单仓库
- 优化构建和测试性能
- 管理共享依赖
- 实施代码共享策略
- 为单仓库设置 CI/CD
- 对包进行版本管理和发布
- 调试单仓库特有问题
核心概念
1. 为什么使用单仓库?
优势:
- 共享代码和依赖
- 跨项目进行原子化提交
- 工具和标准保持一致
- 重构更轻松
- 简化依赖管理
- 提高代码可见性
挑战:
- 大规模场景下的构建性能
- CI/CD 复杂度
- 访问控制
- Git 仓库规模较大
2. 单仓库工具
包管理器:
- pnpm workspaces(推荐)
- npm workspaces
- Yarn workspaces
构建系统:
- Turborepo(大多数场景下推荐)
- Nx(功能丰富,但较复杂)
- Lerna(较旧,处于维护模式)
Turborepo 设置
初始设置
# Create new monorepo
npx create-turbo@latest my-monorepo
cd my-monorepo
# Structure:
# apps/
# web/ - Next.js app
# docs/ - Documentation site
# packages/
# ui/ - Shared UI components
# config/ - Shared configurations
# tsconfig/ - Shared TypeScript configs
# turbo.json - Turborepo configuration
# package.json - Root package.json
配置
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"]
},
"lint": {
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
},
"type-check": {
"dependsOn": ["^build"],
"outputs": []
}
}
}
// package.json (root)
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"apps/*",
"packages/*"
],
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"test": "turbo run test",
"lint": "turbo run lint",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"clean": "turbo run clean && rm -rf node_modules"
},
"devDependencies": {
"turbo": "^1.10.0",
"prettier": "^3.0.0",
"typescript": "^5.0.0"
},
"packageManager": "[email protected]"
}
包结构
// packages/ui/package.json
{
"name": "@repo/ui",
"version": "0.0.0",
"private": true,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./button": {
"import": "./dist/button.js",
"types": "./dist/button.d.ts"
}
},
"scripts": {
"build": "tsup src/index.ts --format esm,cjs --dts",
"dev": "tsup src/index.ts --format esm,cjs --dts --watch",
"lint": "eslint src/",
"type-check": "tsc --noEmit"
},
"devDependencies": {
"@repo/tsconfig": "workspace:*",
"tsup": "^7.0.0",
"typescript": "^5.0.0"
},
"dependencies": {
"react": "^18.2.0"
}
}
pnpm Workspaces
设置
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
- 'tools/*'
// .npmrc
# Hoist shared dependencies
shamefully-hoist=true
# Strict peer dependencies
auto-install-peers=true
strict-peer-dependencies=true
# Performance
store-dir=~/.pnpm-store
依赖管理
# Install dependency in specific package
pnpm add react --filter @repo/ui
pnpm add -D typescript --filter @repo/ui
# Install workspace dependency
pnpm add @repo/ui --filter web
# Install in all packages
pnpm add -D eslint -w
# Update all dependencies
pnpm update -r
# Remove dependency
pnpm remove react --filter @repo/ui
脚本
# Run script in specific package
pnpm --filter web dev
pnpm --filter @repo/ui build
# Run in all packages
pnpm -r build
pnpm -r test
# Run in parallel
pnpm -r --parallel dev
# Filter by pattern
pnpm --filter "@repo/*" build
pnpm --filter "...web" build # Build web and dependencies
Nx 单仓库
设置
# Create Nx monorepo
npx create-nx-workspace@latest my-org
# Generate applications
nx generate @nx/react:app my-app
nx generate @nx/next:app my-next-app
# Generate libraries
nx generate @nx/react:lib ui-components
nx generate @nx/js:lib utils
配置
// nx.json
{
"extends": "nx/presets/npm.json",
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"],
"cache": true
},
"test": {
"inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"],
"cache": true
},
"lint": {
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
"cache": true
}
},
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": [
"default",
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
"!{projectRoot}/tsconfig.spec.json"
],
"sharedGlobals": []
}
}
运行任务
# Run task for specific project
nx build my-app
nx test ui-components
nx lint utils
# Run for affected projects
nx affected:build
nx affected:test --base=main
# Visualize dependencies
nx graph
# Run in parallel
nx run-many --target=build --all --parallel=3
共享配置
TypeScript 配置
// packages/tsconfig/base.json
{
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"incremental": true,
"declaration": true
},
"exclude": ["node_modules"]
}
// packages/tsconfig/react.json
{
"extends": "./base.json",
"compilerOptions": {
"jsx": "react-jsx",
"lib": ["ES2022", "DOM", "DOM.Iterable"]
}
}
// apps/web/tsconfig.json
{
"extends": "@repo/tsconfig/react.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
ESLint 配置
// packages/config/eslint-preset.js
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'prettier',
],
plugins: ['@typescript-eslint', 'react', 'react-hooks'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
settings: {
react: {
version: 'detect',
},
},
rules: {
'@typescript-eslint/no-unused-vars': 'error',
'react/react-in-jsx-scope': 'off',
},
};
// apps/web/.eslintrc.js
module.exports = {
extends: ['@repo/config/eslint-preset'],
rules: {
// App-specific rules
},
};
代码共享模式
模式 1:共享 UI 组件
// packages/ui/src/button.tsx
import * as React from 'react';
export interface ButtonProps {
variant?: 'primary' | 'secondary';
children: React.ReactNode;
onClick?: () => void;
}
export function Button({ variant = 'primary', children, onClick }: ButtonProps) {
return (
<button
className={`btn btn-${variant}`}
onClick={onClick}
>
{children}
</button>
);
}
// packages/ui/src/index.ts
export { Button, type ButtonProps } from './button';
export { Input, type InputProps } from './input';
// apps/web/src/app.tsx
import { Button } from '@repo/ui';
export function App() {
return <Button variant="primary">Click me</Button>;
}
模式 2:共享工具函数
// packages/utils/src/string.ts
export function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
export function truncate(str: string, length: number): string {
return str.length > length ? str.slice(0, length) + '...' : str;
}
// packages/utils/src/index.ts
export * from './string';
export * from './array';
export * from './date';
// Usage in apps
import { capitalize, truncate } from '@repo/utils';
模式 3:共享类型
// packages/types/src/user.ts
export interface User {
id: string;
email: string;
name: string;
role: 'admin' | 'user';
}
export interface CreateUserInput {
email: string;
name: string;
password: string;
}
// Used in both frontend and backend
import type { User, CreateUserInput } from '@repo/types';
构建优化
Turborepo 缓存
// turbo.json
{
"pipeline": {
"build": {
// Build depends on dependencies being built first
"dependsOn": ["^build"],
// Cache these outputs
"outputs": ["dist/**", ".next/**"],
// Cache based on these inputs (default: all files)
"inputs": ["src/**/*.tsx", "src/**/*.ts", "package.json"]
},
"test": {
// Run tests in parallel, don't depend on build
"cache": true,
"outputs": ["coverage/**"]
}
}
}
远程缓存
# Turborepo Remote Cache (Vercel)
npx turbo login
npx turbo link
# Custom remote cache
# turbo.json
{
"remoteCache": {
"signature": true,
"enabled": true
}
}
单仓库 CI/CD
GitHub Actions
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # For Nx affected commands
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm turbo run build
- name: Test
run: pnpm turbo run test
- name: Lint
run: pnpm turbo run lint
- name: Type check
run: pnpm turbo run type-check
仅部署受影响的部分
# Deploy only changed apps
- name: Deploy affected apps
run: |
if pnpm nx affected:apps --base=origin/main --head=HEAD | grep -q "web"; then
echo "Deploying web app"
pnpm --filter web deploy
fi
最佳实践
- 版本统一:在整个工作区锁定依赖版本
- 共享配置:集中管理 ESLint、TypeScript、Prettier 配置
- 依赖图:保持无环,避免循环依赖
- 有效利用缓存:正确配置输入和输出
- 类型安全:在前端和后端之间共享类型
- 测试策略:在包中编写单元测试,在应用中编写 E2E 测试
- 文档:在每个包中提供 README
- 发布策略:使用 changesets 进行版本管理
常见问题
- 循环依赖:A 依赖 B,B 又依赖 A
- 幽灵依赖:使用了未列在 package.json 中的依赖
- 缓存输入不正确:Turborepo 的 inputs 中缺少文件
- 共享过度:共享了本应分开的代码
- 共享不足:在多个包之间重复代码
- 单仓库规模过大:缺少合适的工具时,构建速度会变慢
发布包
# Using Changesets
pnpm add -Dw @changesets/cli
pnpm changeset init
# Create changeset
pnpm changeset
# Version packages
pnpm changeset version
# Publish
pnpm changeset publish
# .github/workflows/release.yml
- name: Create Release Pull Request or Publish
uses: changesets/action@v1
with:
publish: pnpm release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
资源
- references/turborepo-guide.md:全面的 Turborepo 文档
- references/nx-guide.md:Nx 单仓库模式
- references/pnpm-workspaces.md:pnpm 工作区功能
- assets/monorepo-checklist.md:设置检查清单
- assets/migration-guide.md:从多仓库迁移到单仓库
- scripts/dependency-graph.ts:可视化包依赖关系
评论
支持 Markdown