单仓库多包管理(Monorepo)是大型前端工程的标配。相比 npm/yarn,pnpm 的硬链接机制让依赖安装速度提升 2-3 倍,配合 Turborepo 的智能缓存,可以实现真正的增量构建。本文从零搭建一套完整方案。
一、为什么选 pnpm + Turborepo?
pnpm 核心优势
- 硬链接存储:同一版本的包全局只存一份,节省磁盘空间
- 严格隔离:包只能访问自己声明的依赖,消除幽灵依赖
- 原生 workspace:内置多包管理,无需额外插件
Turborepo 核心优势
- 远程缓存:CI 共享构建缓存,重复构建接近零耗时
- 并行执行:自动分析任务依赖图,最大化并行度
- 增量构建:只重建受影响的包
二、项目结构设计
my-monorepo/
├── apps/
│ ├── web/ # Next.js 前端应用
│ └── admin/ # Vite + React 管理后台
├── packages/
│ ├── ui/ # 共享 UI 组件库
│ ├── utils/ # 工具函数库
│ └── config/ # 共享配置(ESLint、TypeScript 等)
├── package.json
├── pnpm-workspace.yaml
└── turbo.json
三、初始化项目
Step 1:安装 pnpm
corepack enable
corepack prepare pnpm@latest --activate
pnpm --version # 9.x
Step 2:创建目录
mkdir my-monorepo && cd my-monorepo
mkdir -p apps/web apps/admin packages/ui packages/utils packages/config
Step 3:pnpm-workspace.yaml
packages:
- apps/*
- packages/*
Step 4:根 package.json
{
"name": "my-monorepo",
"private": true,
"scripts": {
"build": "turbo build",
"dev": "turbo dev",
"lint": "turbo lint",
"test": "turbo test",
"clean": "turbo clean"
},
"devDependencies": { "turbo": "^2.0.0" },
"engines": { "node": ">=18", "pnpm": ">=9" }
}
Step 5:turbo.json
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"inputs": ["src/**", "package.json", "tsconfig.json"],
"outputs": ["dist/**", ".next/**"]
},
"dev": { "cache": false, "persistent": true },
"lint": { "dependsOn": ["^build"] },
"test": { "dependsOn": ["^build"] },
"clean": { "cache": false }
}
}
四、共享 UI 组件库
// packages/ui/package.json
{
"name": "@myapp/ui",
"version": "0.0.0",
"private": true,
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"scripts": {
"build": "tsup src/index.ts --format esm,cjs --dts",
"dev": "tsup src/index.ts --format esm,cjs --dts --watch"
},
"peerDependencies": { "react": "^18.0.0" },
"devDependencies": { "tsup": "^8.0.0", "typescript": "^5.0.0" }
}
// packages/ui/src/components/Button.tsx
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'ghost';
size?: 'sm' | 'md' | 'lg';
children: React.ReactNode;
onClick?: () => void;
}
export function Button({ variant = 'primary', size = 'md', children, onClick }: ButtonProps) {
const base = 'inline-flex items-center rounded font-medium transition-all';
const variants = {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
ghost: 'border border-gray-300 hover:bg-gray-50',
};
const sizes = { sm: 'px-3 py-1.5 text-sm', md: 'px-4 py-2', lg: 'px-6 py-3 text-lg' };
return <button className={`${base} ${variants[variant]} ${sizes[size]}`} onClick={onClick}>{children}</button>;
}
五、共享配置包
// packages/config/eslint-base.js
module.exports = {
extends: ['eslint:recommended'],
rules: { 'no-console': 'warn', 'no-unused-vars': 'error' },
env: { node: true, es2022: true },
};
// packages/config/tsconfig.base.json
{
"compilerOptions": {
"strict": true, "skipLibCheck": true, "esModuleInterop": true,
"module": "ESNext", "moduleResolution": "bundler", "target": "ES2022",
"declaration": true, "declarationMap": true, "sourceMap": true
}
}
六、在应用里引用共享包
// apps/web/package.json(关键部分)
{
"dependencies": {
"@myapp/ui": "workspace:*",
"@myapp/utils": "workspace:*",
"next": "^14.0.0",
"react": "^18.0.0"
}
}
// apps/web/src/app/page.tsx
import { Button } from '@myapp/ui';
import { formatDate } from '@myapp/utils';
export default function Home() {
return (
<div>
<p>今天是 {formatDate(new Date())}</p>
<Button variant="primary">开始使用</Button>
</div>
);
}
七、常用命令速查
pnpm install # 安装所有依赖
pnpm dev # 启动所有应用(并行)
pnpm --filter @myapp/web dev # 只启动某个包
pnpm --filter @myapp/web add axios # 为某个包添加依赖
pnpm add -Dw typescript # 为根 workspace 添加开发依赖
八、构建加速实测
# 首次构建
pnpm build
# Tasks: 3 successful | Cached: 0 | Time: 18.2s
# 未修改,再次构建
pnpm build
# Tasks: 3 successful | Cached: 3 cached | Time: 312ms <-- 全命中!
# 只改了 ui 包
pnpm build
# Tasks: 3 successful | Cached: 1 cached | Time: 8.1s
九、远程缓存(CI 共享)
npx turbo login
npx turbo link
# 之后 CI 自动共享缓存,团队成员构建也能命中
十、版本管理与发布
pnpm add -Dw @changesets/cli
pnpm changeset init
pnpm changeset # 记录变更(交互式)
pnpm changeset version # 升版本号 + 生成 CHANGELOG.md
pnpm publish -r --filter @myapp/ui --filter @myapp/utils
常见问题
幽灵依赖报错
# Cannot find module 'lodash'(未显式安装)
pnpm --filter @myapp/web add lodash
workspace 链接未更新
pnpm --filter @myapp/ui build # 重建 ui 包
turbo 缓存失效
pnpm build --force # 忽略缓存强制重建
核心要点:workspace:* 协议引用内部包、dependsOn: ["^build"] 声明构建依赖、Changesets 管理版本——这三点是这套方案的精髓。