pnpm + Turborepo 通俗实战指南
我们将抛开晦涩的术语,用**"乐高积木"和"中央厨房"**的比喻,带你从零开始理解并搭建一个现代化的 Monorepo(单体仓库)。
第一部分:核心概念通俗版
1. 为什么需要 Monorepo(单体仓库)?
想象你开了一家连锁餐厅(你的公司),你有:
- APP A(网页版点餐)
- APP B(小程序点餐)
- 共享组件库(按钮、菜单样式、Logo)
- 共享工具库(计算价格、验证用户)
传统做法(多仓库): 你把"按钮"代码复制一份到 APP A,再复制一份到 APP B。
- ❌ 痛点:如果你要改按钮颜色,得去两个地方改,改漏了一个就出 Bug。而且依赖包(如 React)在每个项目里都下载一遍,硬盘爆炸。
Monorepo 做法: 所有代码放在一个大仓库里。
- ✅ 优势:按钮代码只写一次,A 和 B 直接引用。改一处,全生效。
2. pnpm:超级省空间的"仓库管理员"
npm/yarn 的做法: 每个项目都在自己的 node_modules 里把依赖包完整拷贝一份。
- 🏠 项目 A:存了一份 React (100MB)
- 🏠 项目 B:存了一份 React (100MB)
- 结果:浪费空间,安装慢。
pnpm 的做法(硬链接 + 符号链接): pnpm 在全局建了一个"中央仓库",所有包只存一份。
- 🏢 中央仓库:存了一份 React (100MB)
- 🏠 项目 A:放了一个"快捷方式"指向中央仓库。
- 🏠 项目 B:放了一个"快捷方式"指向中央仓库。
- 结果:速度极快,节省 50%+ 磁盘空间。而且它很严格,你没声明的依赖,它不让你用(防止"幽灵依赖")。
3. Turborepo:聪明的"施工队长"
当你运行 build(构建)命令时:
普通做法: 队长傻傻地按顺序来:先建 A,等 A 完了,再建 B,再建 C... 哪怕 B 和 C 跟 A 没关系,也得排队。
Turborepo 的做法: 队长手里有一张地图(依赖图)。
- 并行:它发现 A 和 B 互不影响,于是大喊:"A 队和 B 队,同时开工!"
- 缓存:它记得"上次 A 队的代码没变过",于是直接拿出上次的成品:"A 队休息,直接用旧成果!"
- 依赖感知:它知道 C 依赖 A,所以会等 A 完工后,再让 C 开工。
第二部分:手把手实战教程
我们假设你要做一个项目:包含一个 官网 (Web) 和一个 共享 UI 库 (UI)。
步骤 1:初始化项目(一键生成)
打开终端,运行官方提供的创建命令(这是最快最稳的方式):
# 使用 pnpm 创建名为 "my-awesome-app" 的项目
pnpm create turbo@latest my-awesome-app
# 过程中会让你选择包管理器,选 pnpm
# 还会问你要什么模板,选 "Default" (包含 Next.js 示例) 或 "Basic"进入目录看看结构:
my-awesome-app/
├── apps/ # 【应用层】具体的网站或 App
│ ├── web/ # 例如:你的主官网
│ └── docs/ # 例如:文档站
├── packages/ # 【库层】共享的代码
│ ├── ui/ # 共享的按钮、输入框等组件
│ └── eslint-config/ # 共享的代码规范
├── turbo.json # 【核心】告诉 Turborepo 怎么干活
├── pnpm-workspace.yaml# 【核心】告诉 pnpm 哪些文件夹是一伙的
└── package.json # 根配置文件步骤 2:理解"工作区" (Workspace)
打开 pnpm-workspace.yaml,你会看到:
packages:
- 'apps/*'
- 'packages/*'人话翻译:
"pnpm 听好了,
apps文件夹里的所有子文件夹,和packages文件夹里的所有子文件夹,都是我的自家人。它们之间可以互相引用,不用发布到 npm 网上也能用。"
步骤 3:如何让"应用"引用"共享库"?
这是 Monorepo 最核心的操作。假设你想在 apps/web 里使用 packages/ui 里的按钮。
找到
apps/web/package.json。添加依赖:
json{ "name": "web", "dependencies": { "@my-org/ui": "workspace:*" } }关键点:
workspace:*这个写法非常特殊。- 它的意思是:"别去 npm 网上下载
@my-org/ui了,直接去我们本地仓库里找那个叫ui的包链接过来!" - 这样你在
packages/ui里改一行代码,apps/web立马就能感觉到(开发模式下)。
- 它的意思是:"别去 npm 网上下载
安装依赖: 回到项目根目录,运行:
bashpnpm installpnpm 会自动把本地链接打通。
步骤 4:配置"施工队长" (turbo.json)
打开 turbo.json,这是大脑。让我们看懂默认配置:
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"lint": {}
}
}逐行解读:
"build"任务:"dependsOn": ["^build"]:那个^符号很重要!意思是**"先构建我的上游依赖"**。- 如果你运行
web的 build,因为它依赖ui,Turborepo 会自动先运行ui的 build,然后再运行web的 build。你不需要手动分两步走。
- 如果你运行
"outputs":告诉 Turbo,构建完后生成的文件夹是.next和dist。Turbo 会把这些文件夹打包存进缓存。下次代码没变,直接解压复用,秒完成。
"dev"任务:"cache": false:开发模式不要缓存,我要实时看变化。"persistent": true:这是一个长期运行的命令(服务器不会停),别把它当成一次性任务。
步骤 5:日常开发命令大全
所有命令都在根目录运行,Turbo 会帮你分发。
| 你想做什么 | 命令 | 发生了什么 |
|---|---|---|
| 启动所有项目 | pnpm dev | 同时启动 web、docs 等的本地服务器。占用多个端口,并行运行。 |
| 只启动官网 | pnpm dev --filter=web | 只启动 apps/web。适合只想调试某个项目时。 |
| 构建所有项目 | pnpm build | 智能顺序:先建 ui,再建 web。利用缓存加速。 |
| 检查代码规范 | pnpm lint | 并行检查所有项目的代码风格。 |
| 查看任务图谱 | pnpm turbo run build --graph | 神器! 它会生成一张图片,展示任务之间的依赖关系,帮你排查谁依赖谁。 |
第三部分:进阶技巧与避坑指南
1. 怎么发布我的共享库到 npm?
在 Monorepo 里发布多个包很麻烦(版本号要同步,Changelog 要写)。
推荐方案:Changesets
它是目前业界标准。
- 安装:
pnpm add -D @changesets/cli - 初始化:
pnpm changeset init - 每次改代码后,运行
pnpm changeset,它会问你改了哪个包,是 major/minor/patch 版本。 - 准备发布时,运行
pnpm changeset version(自动改版本号)->pnpm changeset publish(自动发布到 npm)。
2. 常见报错与解决
报错:
ERR_PNPM_WORKSPACE_PKG_NOT_FOUND- 原因:你在
package.json里写了"@my-org/ui": "workspace:*",但 pnpm 找不到这个包。 - 解决:检查
packages/ui/package.json里的name字段是不是真的叫@my-org/ui?检查pnpm-workspace.yaml有没有包含packages/*?
- 原因:你在
报错:TypeScript 找不到类型
- 原因:TS 编译器有时候比较笨,不知道本地链接的存在。
- 解决:确保你的
tsconfig.json使用了references或者配置了paths。通常 Turborepo 模板里已经配好了baseUrl: "."和paths。如果不行,尝试在根目录运行pnpm install刷新一下链接。
问题:我想加一个新的 React 包到 UI 库,会影响官网吗?
- 答案:会。因为
web依赖ui。 - 优化:Turborepo 很聪明。如果你只改了
ui里的一个文件,运行pnpm build时,它只会重新构建ui和依赖ui的web。其他的docs如果没依赖ui或者代码没变,它根本不会动。
- 答案:会。因为
3. 目录命名建议
为了清晰,建议遵循以下约定:
apps/:放最终产物(能跑的服务器、网站、App)。apps/web(Next.js)apps/mobile(Expo/React Native)
packages/:放纯代码库(不能直接跑,只能被引用)。packages/ui(React 组件)packages/utils(纯函数工具)packages/config(共享的 tsconfig, eslint 配置)
总结:为什么要坚持用这套组合?
- 开发体验爽:改一个组件,所有用到它的页面即时更新。
- 省钱省资源:pnpm 让你的 CI/CD 服务器磁盘压力减半,安装时间减半。
- 构建飞快:Turborepo 的缓存机制,让第二次构建通常在 1 秒内完成(只要代码没变)。
- 逻辑清晰:强制你把代码拆分成"应用"和"库",避免了 spaghetti code(面条代码)。
一句话口诀:
大仓管理用 pnpm,构建加速靠 Turbo; > 内部引用 workspace,依赖顺序它做主。
