1. 为什么需要Monorepo?
在传统的前端开发中,我们通常会为每个项目或模块创建独立的代码仓库。但随着项目规模扩大,这种多仓库(Multirepo)模式开始暴露出诸多问题:
- 依赖管理混乱:不同项目依赖相同库的不同版本,导致版本冲突
- 代码复用困难:公共组件需要在多个仓库间复制粘贴或发布私有npm包
- 协作效率低下:跨项目修改需要分别在多个仓库提交PR,协调成本高
- 构建流程冗余:每个项目都需要独立的CI/CD配置,资源浪费严重
Monorepo通过将相关项目集中管理,很好地解决了这些问题。以Google、Facebook为代表的科技巨头都采用这种模式管理代码,现在越来越多的前端团队也开始拥抱Monorepo。
2. 工具选型与对比
2.1 包管理工具:为什么选择pnpm?
在Monorepo场景下,包管理工具的选择至关重要。我们对比下主流工具的表现:
| 特性 | npm/yarn | pnpm |
|---|---|---|
| 磁盘空间占用 | 高 | 低 |
| 安装速度 | 慢 | 快 |
| 依赖隔离 | 无 | 有 |
| Monorepo支持 | 基础 | 完善 |
pnpm采用硬链接+符号链接的方式管理依赖:
- 所有依赖统一存储在全局store中
- 项目通过硬链接引用,节省磁盘空间
- 依赖树保持扁平化但彼此隔离
这种设计特别适合Monorepo场景,能显著减少node_modules体积。例如一个包含10个子项目的Monorepo,使用pnpm可节省70%以上的磁盘空间。
2.2 构建工具:Turbo vs Nx
构建工具负责处理项目间的依赖关系和任务调度:
| 特性 | Turbo | Nx |
|---|---|---|
| 学习曲线 | 低 | 高 |
| 缓存机制 | 文件哈希 | 精细化计算 |
| 远程缓存支持 | 有 | 有 |
| 插件生态系统 | 简单 | 复杂 |
| 执行速度 | 极快 | 快 |
Turbo的优势在于:
- 零配置上手,通过
turbo.json即可定义任务管道 - 增量构建极快,基于文件哈希的缓存机制效率很高
- 与pnpm深度集成,依赖解析更准确
对于大多数前端Monorepo项目,Turbo提供了最佳的性能和易用性平衡。
3. 项目初始化与基础配置
3.1 初始化Monorepo结构
bash复制# 初始化项目
mkdir monorepo-demo && cd monorepo-demo
pnpm init
# 创建基础目录结构
mkdir -p packages/{utils,components} apps/{web,admin}
推荐的项目结构:
code复制monorepo-demo/
├── apps/ # 应用项目
│ ├── web/ # 主站项目
│ └── admin/ # 后台项目
├── packages/ # 共享包
│ ├── utils/ # 工具库
│ └── components/ # UI组件库
├── package.json
└── pnpm-workspace.yaml
3.2 配置workspace
pnpm-workspace.yaml是pnpm识别Monorepo结构的核心文件:
yaml复制packages:
- "apps/*"
- "packages/*"
- "!**/__tests__/**" # 排除测试目录
这个配置告诉pnpm:
apps和packages下的子目录都是独立包- 排除所有
__tests__目录 - 支持跨包依赖解析
3.3 安装TypeScript基础配置
bash复制pnpm add -Dw typescript @types/node
tsconfig.base.json(共享基础配置):
json复制{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"strict": true,
"skipLibCheck": true,
"moduleResolution": "bundler",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
}
}
各子包通过extends继承基础配置:
json复制{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist"
}
}
4. Turbo构建系统配置
4.1 基础任务管道配置
turbo.json示例:
json复制{
"$schema": "https://turborepo.org/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["build"],
"outputs": [],
"cache": false
},
"lint": {
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
}
}
}
关键配置说明:
dependsOn: ["^build"]表示依赖上游包先构建outputs声明任务输出目录,用于缓存有效性判断persistent表示长期运行的任务(如dev server)
4.2 高级缓存策略
通过环境变量细化缓存规则:
json复制{
"build": {
"outputs": ["dist/**"],
"env": ["NODE_ENV", "API_URL"],
"inputs": ["src/**/*.ts", "tsconfig.json"]
}
}
这表示:
- 只有当
src/**/*.ts或tsconfig.json变化时才重建 - 环境变量
NODE_ENV和API_URL也会影响缓存键 - 输出到
dist/**的内容会被缓存
5. 代码规范与Git工作流
5.1 为什么选择Oxlint?
相比ESLint,Oxlint具有显著优势:
- 性能:Rust编写,速度比ESLint快50-100倍
- 零配置:内置合理默认值,开箱即用
- 类型感知:能利用TypeScript类型信息进行更精准检查
.oxlintrc.json配置示例:
json复制{
"rules": {
"correctness/noUnusedVariables": "error",
"style/noConsole": "warn",
"typescript/noExplicitAny": "error"
}
}
5.2 提交规范配置
- 安装依赖:
bash复制pnpm add -Dw @commitlint/cli @commitlint/config-conventional cz-git simple-git-hooks
commitlint.config.js:
js复制module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore']
]
}
}
package.json配置:
json复制{
"scripts": {
"commit": "czg",
"prepare": "simple-git-hooks"
},
"simple-git-hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint --edit"
},
"lint-staged": {
"*.{ts,tsx}": ["oxlint --fix", "oxfmt"]
}
}
6. 共享包开发实践
6.1 工具库开发示例
- 初始化utils包:
bash复制pnpm init @monorepo/utils
cd packages/utils
- 配置
tsdown打包:
json复制// tsdown.config.ts
export default {
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
clean: true
}
- 添加内部依赖:
bash复制# 从另一个包安装依赖
pnpm add @monorepo/logger -r --filter @monorepo/utils
6.2 组件库开发要点
- 样式隔离方案:
- CSS Modules:
[name].module.css - Scoped CSS:配合Vue/React插件
- CSS-in-JS:styled-components等
- 文档生成:
- Storybook:交互式组件文档
- Docusaurus:静态文档站点
- 版本管理:
- Changesets:管理多包版本和CHANGELOG
- 语义化版本:major.minor.patch
7. 应用项目集成
7.1 引用本地包
在apps/web/package.json中:
json复制{
"dependencies": {
"@monorepo/utils": "workspace:*",
"@monorepo/components": "workspace:*"
}
}
workspace:*表示始终使用本地最新版本,发布时会自动替换为具体版本号。
7.2 开发环境热更新
配置Turbo任务:
json复制{
"dev": {
"cache": false,
"persistent": true,
"env": ["PORT"],
"inputs": ["src/**"]
}
}
启动命令:
bash复制turbo run dev --parallel
8. 高级优化技巧
8.1 依赖提升策略
.npmrc配置:
code复制# 将某些依赖提升到根node_modules
public-hoist-pattern[]=*eslint*
public-hoist-pattern[]=*prettier*
8.2 选择性安装
只安装特定包及其依赖:
bash复制pnpm install --filter @monorepo/web
8.3 远程缓存配置
- 登录Turbo:
bash复制npx turbo login
- 链接项目:
bash复制npx turbo link
- 构建时上传缓存:
bash复制npx turbo run build --remote-only
9. 常见问题排查
9.1 依赖解析失败
症状:Cannot find module '@monorepo/utils'
解决方案:
- 确认包名在
package.json中正确声明 - 运行
pnpm install更新依赖 - 检查
pnpm-workspace.yaml包含该包路径
9.2 Turbo缓存失效
症状:修改文件后构建结果未更新
排查步骤:
- 检查
turbo.json中的inputs配置 - 清理缓存:
npx turbo prune --scope=packageName - 添加
--no-cache参数临时禁用缓存测试
9.3 类型检查报错
症状:跨包引用时类型找不到
解决方案:
- 确保依赖包已构建生成
.d.ts文件 - 在
tsconfig.json中添加paths映射:
json复制{
"compilerOptions": {
"paths": {
"@monorepo/utils": ["packages/utils/src"]
}
}
}
10. 生产环境实践
10.1 CI/CD流水线示例
.github/workflows/ci.yml:
yaml复制jobs:
build:
steps:
- uses: pnpm/action-setup@v2
- run: pnpm install
- run: pnpm run build
- uses: actions/upload-artifact@v3
with:
path: dist
10.2 多环境部署策略
- 通过环境变量区分:
json复制{
"build": {
"env": ["NODE_ENV", "API_BASE"]
}
}
- 动态配置注入:
js复制// apps/web/src/config.ts
export const API_BASE = process.env.API_BASE || 'https://api.example.com'
10.3 监控与告警
- 构建大小监控:
bash复制npx turbo run build --dry=json | jq '.tasks[].summary'
- 依赖安全检查:
bash复制pnpm audit
- 类型检查:
bash复制pnpm run check-types