🧱 前端 Monorepo 完整入门指南
✅ 从零开始搭建一个包含两个 Web 应用 + 共享组件库的 Monorepo
✅ 适用于个人项目、开源库、中小型团队
✅ 技术栈:pnpm+Vite+Turborepo+React
一、为什么使用 Monorepo?
🎯 场景:我们的 Demo 项目
假设你正在开发:
web-a:一个商品展示页(如电商首页)web-b:一个用户中心页(如个人资料)- 两者都用到了相同的按钮、弹窗、API 工具函数
❌ 如果用两个独立仓库(Multirepo):
-
每次改一个公共按钮,要:
- 在
shared-ui仓库提交 - 发布新 npm 包(如
v1.0.1) - 分别在
web-a和web-b中升级依赖 - 测试是否兼容
- 在
-
容易出现 “A 用了 v1.0.0,B 用了 v1.0.1” 的版本碎片问题
✅ 使用 Monorepo 后:
- 所有代码在一个仓库
- 公共组件直接引用源码,修改立即生效
- 一次提交可同时更新组件库 + 两个应用(原子变更)
- 统一 ESLint、TypeScript、构建配置
💡 Monorepo 的核心价值:提升紧密耦合项目的协作效率与工程一致性
二、Monorepo 背后的关键技术(结合 Demo 解释)
我们的 Demo 项目用到以下工具,它们各司其职:
| 技术 | 作用 | 在 Demo 中的角色 |
|---|---|---|
pnpm | 高效包管理器 | 管理 web-a、web-b、ui 三个子项目的依赖,并自动链接内部包 |
pnpm workspaces | 工作区支持 | 告诉 pnpm:“这三个目录属于同一个项目,请把 @my-monorepo/ui 当成本地包处理” |
Turborepo | 任务调度与缓存 | 让 pnpm build 只构建变更过的应用,第二次构建快 10 倍 |
Vite | 构建工具 | 为 web-a 和 web-b 提供极速开发服务器和生产构建 |
下面逐一说明:
🔧 1. pnpm:更快更省的包管理器
-
问题:npm/yarn 会为每个项目复制一份依赖,磁盘占用大
-
pnpm 方案:用硬链接 + 符号链接,所有项目共享同一份依赖存储
-
Demo 中:
web-a和web-b都用 React 18- pnpm 只下载一次 React,两个应用共用 → 节省 50%+ 磁盘空间
📦 2. pnpm Workspaces:本地包的“身份证”
-
问题:如何让
web-a知道@my-monorepo/ui不是 npm 包,而是本地代码? -
解决方案:通过
pnpm-workspace.yaml声明工作区范围 -
Demo 中:
# pnpm-workspace.yaml packages: - 'apps/*' # web-a, web-b - 'packages/*' # ui→ pnpm 自动将
packages/ui视为合法包,无需发布到 npm
⚡ 3. Turborepo:智能任务调度器
-
问题:每次改一行代码,都要重新构建两个应用?太慢!
-
Turborepo 方案:
- 分析依赖图(
web-a依赖ui) - 只重新构建受影响的部分
- 缓存构建结果(本地 + 远程)
- 分析依赖图(
-
Demo 中:
- 修改
ui→ 自动重建web-a和web-b - 修改
web-a→ 只重建web-a - 第二次运行
pnpm build→ 几乎瞬间完成(命中缓存)
- 修改
🛠️ 4. Vite:现代化构建工具
-
作用:提供开发服务器(
dev)和生产构建(build) -
Demo 中:
web-a和web-b各自独立构建为静态文件(dist/目录)- 支持 React Fast Refresh(保存即更新)
- 构建产物可直接部署到 GitHub Pages / Vercel
✅ 这些工具组合起来,构成了 轻量、高效、易维护的现代 Monorepo
三、从零搭建 Monorepo(完整步骤)
步骤 1:创建根目录
mkdir my-web-monorepo
cd my-web-monorepo
git init
步骤 2:初始化根 package.json
pnpm init
编辑为:
{
"name": "my-web-monorepo",
"version": "1.0.0",
"private": true
}
步骤 3:启用 Workspaces
创建 pnpm-workspace.yaml:
packages:
- 'apps/*'
- 'packages/*'
步骤 4:创建两个应用
pnpm dlx create-vite@latest apps/web-a -- --template react
pnpm dlx create-vite@latest apps/web-b -- --template react
步骤 5:安装依赖(根目录!)
pnpm install
步骤 6:集成 Turborepo
pnpm add -w -D turbo
创建 turbo.json:
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".vite/**", "dist/**"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
根 package.json 添加脚本:
{
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev --parallel"
}
}
步骤 7:创建共享组件库
mkdir -p packages/ui
cd packages/ui
pnpm init
packages/ui/package.json:
{
"name": "@my-monorepo/ui",
"version": "1.0.0",
"main": "index.jsx"
}
packages/ui/index.jsx:
export const SharedButton = () => (
<button style={{ padding: '8px 16px', background: '#4f46e5', color: 'white' }}>
Shared Button!
</button>
);
步骤 8:在应用中使用共享组件(关键!)
在根目录运行:
pnpm add @my-monorepo/ui@workspace:* --filter web-a
这会在 apps/web-a/package.json 中添加:
"dependencies": {
"@my-monorepo/ui": "workspace:*"
}
然后在 apps/web-a/src/App.jsx 中:
import { SharedButton } from '@my-monorepo/ui';
function App() {
return (
<div>
<h1>Web App A</h1>
<SharedButton />
</div>
);
}
步骤 9:验证
pnpm dev --filter web-a # 应显示共享按钮
pnpm build # 应生成 dist 目录
四、目录结构
my-web-monorepo/
├── apps/
│ ├── web-a/ ← 商品页
│ └── web-b/ ← 用户中心
├── packages/
│ └── ui/ ← 共享 UI 组件
├── pnpm-workspace.yaml
├── turbo.json
├── package.json
└── .gitignore
五、常用命令
| 命令 | 说明 |
|---|---|
pnpm install | 安装所有依赖(根目录) |
pnpm dev --filter web-a | 仅启动 web-a |
pnpm build | 构建所有应用(带缓存) |
pnpm add lodash --filter web-a | 给 web-a 加外部依赖 |
pnpm add @my-monorepo/ui@workspace:* --filter web-a | 声明内部依赖 |
✅ 总结
你现在拥有一个:
- ✅ 代码复用零成本 的共享组件机制
- ✅ 独立开发/部署 的多应用架构
- ✅ 秒级增量构建 的高性能工程体系
这个结构非常适合:
- 开源组件库 + 示例站点
- SaaS 产品的多个前端模块
- 个人作品集(多个小项目统一管理)
🌟 Monorepo 不是银弹,但对紧密协作的项目,它是效率倍增器。