当公司同时维护多个前端应用(主站、管理后台、移动端 H5、小程序)时,代码复用和跨项目协作成为巨大挑战。多仓库模式下,修改一个共享工具函数需要在 5 个仓库中分别修改、分别测试、分别发布------这种"复制粘贴式复用"正是代码腐化的温床。Monorepo(单一代码仓库) 将多个项目放在同一个 Git 仓库中管理,通过共享依赖、统一工具链、原子化提交,从根本上解决了跨项目协作的效率问题。2025 年,pnpm workspace + Turborepo 的组合已成为 Monorepo 领域的事实标准。本文从 Monorepo 的核心价值出发,深入讲解 pnpm workspace 的依赖管理原理与配置、Turborepo 的构建缓存与任务编排,以及 Changesets 的版本管理与发布流程,并通过一个完整的企业级 Monorepo 搭建实战,帮你掌握现代前端 Monorepo 的全链路实践。
一、Monorepo vs Multi-repo:如何选择?
Monorepo(单一代码仓库) 与 Multi-repo(多代码仓库) 的争论由来已久。2025 年的共识是:没有放之四海而皆准的方案,只有基于场景的权衡。
1.1 两种模式的对比

1.2 选择 Monorepo 的时机
前端 Monorepo 最适合以下场景:
维护多个共享 UI 组件库或设计系统的应用:多个应用共享同一套 UI 基元、设计令牌、领域模型或 API 客户端。
频繁需要跨模块变更:如设计系统升级、多语言改造、认证逻辑更新。
希望统一的开发者体验:一套脚本、一套 Lint 规则、一套测试框架。
团队能够承诺维护模块边界:Monorepo 不是"把所有代码扔进一个仓库",而是"有边界、有规则地组织代码"。
⚠️ 关键提醒:Monorepo 是关于版本控制的策略,而非架构。一个 Monorepo 可以包含清晰隔离的包(高内聚、低耦合),也可以变成一个"什么都往里扔的大泥球"。区别在于是否设计了明确的边界和公共 API。
二、pnpm workspace:依赖管理的革命
pnpm 通过硬链接 + 符号链接解决了传统包管理器的依赖冗余问题,成为 Monorepo 的理想搭档。
2.1 pnpm 的核心原理

2.2 pnpm workspace 配置
Step 1:创建 pnpm-workspace.yaml
yaml
# pnpm-workspace.yaml
packages:
- 'apps/*' # 所有应用(web、admin、mobile)
- 'packages/*' # 所有共享包(ui、utils、config)
- 'packages/**' # 支持多级目录[reference:12]
- '!**/test/**' # 排除测试目录
Step 2:配置根目录 package.json
json
{
"name": "my-monorepo",
"private": true, // 根目录不发布到 npm[reference:13]
"scripts": {
"dev": "turbo dev",
"build": "turbo build",
"lint": "turbo lint",
"test": "turbo test"
},
"devDependencies": {
"turbo": "latest",
"typescript": "^5.0.0"
}
}
⚠️ 注意:pnpm 9.x 后需在 .npmrc 中设置 link-workspace-packages=true,确保本地依赖优先链接。
2.3 常用 pnpm workspace 命令

三、Turborepo:构建加速引擎
Turborepo 专注解决 Monorepo 的构建效率瓶颈------当 10 个子项目都需要构建时,如何不每次都重新构建所有内容?
3.1 Turborepo 的核心能力

2024-2025 年,Monorepo 工具链趋于稳定,Turborepo v2 和 Nx v19 均已成熟。Turborepo v2 的关键变化包括:turbo.json 中用 tasks 取代 pipeline。
3.2 配置 turbo.json
json
{
"KaTeX parse error: Expected '}', got 'EOF' at end of input: ... "inputs": "TURBO_DEFAULT$" // 默认输入(package.json、src 等)
},
"dev": {
"cache": false, // 开发模式不缓存
"persistent": true
},
"lint": {
"dependsOn": "\^build"
},
"test": {
"dependsOn": "build",
"inputs": "src/**/\*.test.ts", "src/** /\*.spec.ts"
}
},
"globalEnv": "NODE_ENV", "API_URL" // 影响所有任务的环境变量
}
目录结构示例:
text
my-monorepo/
├── pnpm-workspace.yaml
├── package.json
├── turbo.json
├── apps/
│ ├── web/ # 主站应用
│ ├── admin/ # 管理后台
│ └── mobile/ # 移动端 H5
└── packages/
├── ui/ # 共享 UI 组件库
├── utils/ # 共享工具函数
└── config/ # 共享配置(ESLint、TS)
3.3 Turborepo 在 CI 中的价值
在 CI 环境中,Turborepo 的远程缓存可以将构建时间从"全量构建"压缩到"仅构建变更部分"。配置远程缓存:
bash
# 在 CI 中启用远程缓存
turbo build --remote-only
结合 turbo prune 可以为 Docker 构建创建轻量级的部分 Monorepo:
bash
# 为 web-app 创建精简的 Monorepo 副本(用于 Docker 构建)
turbo prune --scope=web-app --docker
四、版本管理与发布:Changesets
在 Monorepo 中,多个包可能独立发布,版本管理比单仓库复杂得多。Changesets 是专为 Monorepo 设计的版本管理工具。
4.1 Changesets 的核心工作流
bash
# 1. 安装 Changesets
pnpm add -w @changesets/cli
pnpm changeset init
# 2. 开发者提交变更时,添加 changeset
pnpm changeset
# 交互式选择:哪些包发生了变化?版本号是 patch/minor/major?
# 3. 生成 changeset 文件(.changeset/*.md)
# 该文件描述了变更内容,提交到 Git
# 4. 在发布前执行 version 命令
pnpm changeset version
# 自动更新 package.json 版本号,生成 CHANGELOG.md
# 5. 发布到 npm
pnpm publish -r
4.2 Changesets 的自动化流程
Changesets 配合 CI 可以实现自动化发布:
开发者提交 PR 时附带 .changeset/*.md 文件。
PR 合并到 main 后,CI 自动创建一个 "Version Packages" 的 PR。
该 PR 中预览所有版本变更和 CHANGELOG。
合并该 PR 后,自动触发 npm 发布。
💡 优势:Changesets 完全支持 Turborepo Monorepo,可仅发布核心包(packages/core),文档和示例包不发布。
五、实战:从零搭建企业级 Monorepo
5.1 初始化项目
bash
# 1. 创建项目目录
mkdir my-monorepo && cd my-monorepo
# 2. 初始化 Git
git init
# 3. 创建 pnpm-workspace.yaml
cat > pnpm-workspace.yaml << EOF
packages:
- 'apps/*'
- 'packages/*'
EOF
# 4. 初始化 package.json
cat > package.json << EOF
{
"name": "my-monorepo",
"private": true,
"scripts": {
"dev": "turbo dev",
"build": "turbo build",
"lint": "turbo lint",
"test": "turbo test"
}
}
EOF
# 5. 安装 Turborepo
pnpm add -w turbo typescript
# 6. 创建 turbo.json
cat > turbo.json << EOF
{
"tasks": {
"build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
"dev": { "cache": false, "persistent": true },
"lint": {},
"test": {}
}
}
EOF
5.2 创建应用和包
bash
# 创建应用
mkdir -p apps/web
cd apps/web
pnpm init
# 添加 web 应用的依赖...
# 创建共享 UI 组件库
mkdir -p packages/ui
cd packages/ui
pnpm init
# 添加组件库的依赖...
5.3 配置跨包引用
在 apps/web/package.json 中引用 @myorg/ui:
json
{
"dependencies": {
"@myorg/ui": "workspace:" // workspace: 表示使用本地版本
}
}
运行 pnpm install 后,pnpm 会自动建立本地包的符号链接。
六、Monorepo 的最佳实践与陷阱
6.1 最佳实践
定义清晰的模块边界:使用公共 API(如 packages/ui/index.ts 控制导出),避免跨包直接引用内部文件。
统一工具链版本:在根目录统一管理 ESLint、Prettier、TypeScript 版本。
使用 Changesets 管理版本:不要手动修改 package.json 版本号。
在 CI 中启用远程缓存:减少 CI 环境的全量构建时间。
使用 CODEOWNERS 管理权限:不同团队负责不同的 packages。
6.2 常见陷阱

七、小结
Monorepo 的核心价值:代码复用、统一依赖管理、原子化提交、标准化工具链。2025 年,pnpm workspace + Turborepo 已成为 Monorepo 的事实标准。
pnpm workspace:通过硬链接 + 符号链接解决依赖冗余,杜绝幽灵依赖。
Turborepo:通过并行化构建、增量构建和远程缓存大幅提升构建效率。Turborepo v2 中 tasks 取代了 pipeline。
Changesets:专为 Monorepo 设计的版本管理工具,支持多包独立发布和自动化 CHANGELOG 生成。