本文是《從零到一:構建現代化企業級 Monorepo 項目實戰》系列的第二篇。上一篇我們深入分析了 Monorepo 的核心概念,這篇文章將聚焦於工具選型,帶你了解主流 Monorepo 工具的優劣,以及我的選擇理由。
讀完這篇文章,你將了解:
Monorepo 工具鏈
├── 包管理器層
│ ├── npm workspaces
│ ├── yarn workspaces
│ └── pnpm workspace ⭐ (我的選擇)
│
├── 構建編排層
│ ├── Lerna
│ ├── Rush
│ ├── Nx
│ └── Turborepo ⭐ (我的選擇)
│
└── 一體化方案
├── Nx (包管理 + 構建)
└── Rush (包管理 + 構建)
特性 | npm | yarn | pnpm | 推薦指數 |
---|---|---|---|---|
安裝速度 | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 🏆 pnpm |
磁碟空間 | ⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ | 🏆 pnpm |
依賴隔離 | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ | 🏆 pnpm |
幽靈依賴 | ❌ 有 | ❌ 有 | ✅ 無 | 🏆 pnpm |
生態成熟度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 🏆 npm |
學習成本 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 🏆 npm |
傳統 npm/yarn:
# 每個項目都複製一份依賴
~/projects/
├── project-a/node_modules/lodash/ # 1MB
├── project-b/node_modules/lodash/ # 1MB
├── project-c/node_modules/lodash/ # 1MB
└── project-d/node_modules/lodash/ # 1MB
# 總共 4MB
pnpm 的硬鏈接:
# 所有項目共享同一份依賴
~/.pnpm-store/
└── [email protected]/ # 1MB(只存一份)
~/projects/
├── project-a/node_modules/lodash/ → 硬鏈接
├── project-b/node_modules/lodash/ → 硬鏈接
├── project-c/node_modules/lodash/ → 硬鏈接
└── project-d/node_modules/lodash/ → 硬鏈接
# 總共只佔用 1MB!
實際效果:
# 我的項目數據
npm: 1.2 GB node_modules
pnpm: 350 MB node_modules
# 節省空間:70.8%!
什麼是幽靈依賴?
// package.json 中沒有聲明 lodash
{
"dependencies": {
"some-package": "^1.0.0" // some-package 依賴了 lodash
}
}
// 但你居然可以直接用!這就是幽靈依賴
import _ from 'lodash' // 😱 能用,但不安全!
pnpm 的嚴格模式:
# pnpm 會報錯
Error: Cannot find module 'lodash'
# 必須顯式聲明依賴才能使用 ✅
性能對比(安裝 1000+ 依賴):
npm: 45s ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
yarn: 32s ▓▓▓▓▓▓▓▓▓▓▓▓
pnpm: 12s ▓▓▓▓ ⚡
速度提升:
- 比 npm 快 3.75 倍
- 比 yarn 快 2.67 倍
工具 | 學習曲線 | 性能 | 功能豐富度 | 配置複雜度 | 社區活躍度 | 推薦指數 |
---|---|---|---|---|---|---|
Lerna | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
Rush | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐ | ⭐⭐⭐ |
Nx | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
Turborepo | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
優勢:
# 簡單易用
lerna init
lerna bootstrap
lerna publish
劣勢:
適用場景: 小型項目,簡單的版本管理需求
優勢:
// rush.json - 強大的配置能力
{
"projects": [
{ "packageName": "ui-lib", "projectFolder": "packages/ui" }
],
"pnpmOptions": {
"strictPeerDependencies": true
}
}
特點:
劣勢:
適用場景: 大型企業項目,需要嚴格管理
優勢:
# 強大的代碼生成
nx generate @nx/react:component Button
# 智能的依賴圖分析
nx graph
# 高效的快取
nx run-many --target=build --all
特點:
劣勢:
適用場景: 大型項目,需要完整的工具鏈支持
核心優勢:
真實項目數據對比
無快取 有快取 提升倍數
Lerna: 45s 45s 1x
Rush: 38s 12s 3.2x
Nx: 35s 2.5s 14x
Turborepo: 9s 0.45s 20x ⚡
# Turborepo 在快取命中時快了 19-20 倍!
Turborepo 配置:
// turbo.json - 僅需 76 行配置
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
}
}
}
對比 Nx 配置:
// nx.json + workspace.json + project.json
// 需要 200+ 行配置
# 3 步搞定
npx create-turbo@latest
cd my-turborepo
pnpm install
# 就這麼簡單!
# 第一次構建
pnpm build
✓ @company/utils built in 2.1s
✓ @company/ui built in 3.4s
# 代碼沒變化,再次構建
pnpm build
✓ @company/utils cached ⚡
✓ @company/ui cached ⚡
# 完成時間:0.3s!
GDU Common 項目特點:
✓ 4 個包(ui、utils、shared、controls-sdk)
✓ 都使用 Vue 3 + TypeScript
✓ 需要頻繁聯調
✓ 團隊 5-8 人
✓ 需要快速迭代
開始
↓
需要 Monorepo? → 是
↓
團隊規模? → 5-8人(中小型)
↓
是否需要代碼生成? → 否
↓
是否需要多框架支持? → 否(只用 Vue)
↓
最看重什麼? → 性能 + 簡單
↓
選擇:pnpm + Turborepo ✅
為什麼不選 npm/yarn?
# npm workspaces 的問題
npm install
# 幽靈依賴問題
# 速度較慢
# yarn workspaces 的問題
yarn install
# 依賴提升導致的版本衝突
# PnP 模式不夠成熟
# pnpm workspace 的優勢
pnpm install
# ✅ 快速
# ✅ 嚴格
# ✅ 節省空間
pnpm-workspace.yaml 配置:
packages:
- packages/*
- docs
- build
就這麼簡單!
為什麼不選 Nx?
# Nx 的問題
- 配置複雜(3-4 個配置文件)
- 學習曲線陡
- 功能過於豐富(我們用不上)
# Turborepo 的優勢
- 配置簡單(1 個 turbo.json)
- 性能極致(Go 語言編寫)
- 專注於構建(做好一件事)
為什麼不選 Lerna?
# Lerna 的問題
- 性能差(無快取機制)
- 功能有限
- 維護不活躍
# 數據對比
Lerna: 45s 構建
Turborepo: 9s 構建(無快取)
0.45s 構建(有快取)
# 差距太明顯了!
node_modules/
├── package-a/
│ ├── index.js
│ └── node_modules/
│ └── package-b/ # 依賴被提升到頂層
├── package-b/ # 重複了!
└── package-c/
問題:
node_modules/
├── .pnpm/
│ ├── [email protected]/
│ │ └── node_modules/
│ │ ├── package-a/ → ~/..pnpm-store/...
│ │ └── package-b/ → .pnpm/[email protected]/...
│ └── [email protected]/
│ └── node_modules/
│ └── package-b/ → ~/.pnpm-store/...
└── package-a/ → .pnpm/[email protected]/...
優勢:
package.json:
{
"name": "gdu-common",
"private": true,
"scripts": {
"install": "pnpm install"
}
}
pnpm-workspace.yaml:
packages:
- packages/*
- docs
- build
.npmrc 配置:
# 使用嚴格的 peer 依賴檢查
strict-peer-dependencies=true
# 不要幽靈依賴
shamefully-hoist=false
# 使用硬鏈接
link-workspace-packages=true
# 安裝依賴
pnpm install
# 添加依賴到根目錄
pnpm add -w lodash-es
# 添加依賴到特定包
pnpm add vue --filter @gdu-common/ui
# 運行所有包的腳本
pnpm -r build
# 只運行特定包
pnpm --filter @gdu-common/ui build
// turbo.json
{
"tasks": {
"build": {
"dependsOn": ["^build"], // ^ 表示依賴包的 build
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["build"], // 先 build 再 test
"outputs": ["coverage/**"]
}
}
}
依賴圖:
@gdu-common/ui:build
↓ 依賴
@gdu-common/utils:build
↓ 依賴
@gdu-common/shared:build
# Turborepo 會自動計算正確的執行順序
快取鍵計算:
# Turborepo 會基於這些內容計算快取鍵
- 源代碼的哈希值
- 依賴的哈希值
- 環境變數
- 任務配置
# 任何一個變化,快取失效
快取命中示例:
$ pnpm build
Tasks: 4 successful, 4 total
Cached: 4 cached, 4 total ⚡
Time: 450ms >>> FULL TURBO
# 4 個包全部命中快取,只用了 450ms!
# Turborepo 自動分析依賴關係,最大化並行
時間軸 →
shared:build ▓▓▓
↓
utils:build ▓▓▓▓
↓
ui:build ▓▓▓▓▓
docs:build ▓▓▓▓▓▓▓▓
# shared 和 docs 可以並行
# utils 等待 shared 完成
# ui 等待 utils 完成
我的項目構建性能:
場景 | 時間 | 快取命中 | 說明 |
---|---|---|---|
首次構建 | 9.2s | 0/4 | 無快取 |
完全快取 | 450ms | 4/4 ⚡ | FULL TURBO |
修改 1 個包 | 2.3s | 3/4 | 增量構建 |
修改配置文件 | 9.1s | 0/4 | 配置變化,快取失效 |
效率提升:
基礎配置:
{
"$schema": "https://turbo.build/schema.json",
"ui": "tui", // 使用終端 UI
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".vitepress/dist/**"],
"cache": true
},
"lint": {
"cache": true
},
"lint:fix": {
"cache": false // 修改文件的任務不快取
},
"clean": {
"cache": false
}
}
}
高級配置:
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"inputs": [
"$TURBO_DEFAULT$",
"!{dist,build,coverage,.turbo}/**",
"!**/*.md",
"!**/*.test.{ts,tsx}"
],
"outputs": ["dist/**"],
"cache": true
}
},
"globalDependencies": [
".env",
"tsconfig.json",
"eslint.config.js"
],
"globalEnv": [
"NODE_ENV",
"CI"
]
}
pnpm workspace (包管理)
+
Turborepo (構建編排)
=
完美組合 🎉
1. pnpm 負責依賴管理
2. Turborepo 負責構建編排
3. 強強聯合
# pnpm 快速安裝依賴
pnpm install # 12s
# Turborepo 快速構建
pnpm build # 9s(首次) / 0.45s(快取)
# 總時間:13s(首次) / 12.5s(快取)
開發效率提升:
# 傳統 Multirepo 工作流
修改共享函數 → 發布 → 更新依賴 → 重新安裝 → 測試
總耗時:5-10 分鐘 😫
# Monorepo + Turborepo 工作流
修改共享函數 → 保存 → 自動重建 → 熱更新
總耗時:2-3 秒 ⚡
# 效率提升:100-200 倍!
CI/CD 性能:
# .gitlab-ci.yml
build:
script:
- pnpm install # 3s
- pnpm build # 9s (首次)
# 後續 pipeline 只需 0.5s!
✅ 推薦:pnpm workspace
❌ 不需要:Turborepo
# 理由:包少,構建快,不需要複雜的編排
✅ 推薦:pnpm + Turborepo
⭐ 最佳組合!
# 理由:快取和並行構建帶來明顯收益
✅ 推薦:pnpm + Turborepo
或
✅ 推薦:pnpm + Nx
# Nx 提供更多功能(代碼生成、依賴圖)
# Turborepo 更簡單,性能更好
# 根據團隊技術儲備選擇
團隊特點 | 推薦方案 |
---|---|
前端團隊,技術棧統一 | pnpm + Turborepo |
全棧團隊,多技術棧 | pnpm + Nx |
大型企業,嚴格管理 | pnpm + Rush |
簡單項目,快速上手 | pnpm workspace |
# 使用官方腳手架
npx create-turbo@latest my-monorepo
# 選擇 pnpm
? Which package manager do you want to use? › pnpm
# 項目結構
my-monorepo/
├── apps/
│ ├── web/
│ └── docs/
├── packages/
│ ├── ui/
│ └── eslint-config/
├── turbo.json
└── package.json
# 安裝依賴
pnpm install
# 構建所有包
pnpm build
# 查看快取效果
pnpm build # 第二次運行,體驗閃電般的速度 ⚡
項目 | 學習成本 | 遷移時間 | 配置複雜度 |
---|---|---|---|
Lerna | 1 天 | 2-3 天 | 低 |
Rush | 3-5 天 | 1-2 周 | 高 |
Nx | 5-7 天 | 1-2 周 | 高 |
Turborepo | 半天 | 1-2 天 | 低 |
開發效率:
維護成本:
團隊協作:
經過詳細的對比和實踐,我選擇了 pnpm + Turborepo 組合,理由是:
在下一篇文章中,我將手把手帶你從零搭建一個完整的 pnpm + Turborepo 項目,包括:
你的項目用的是什麼 Monorepo 工具?效果如何?歡迎在評論區分享! 🙏
覺得 pnpm + Turborepo 組合不錯?點個讚收藏一下,下篇文章將實戰搭建! 👍