Appearance
pnpm workspace - Monorepo 包管理解决方案
1. pnpm workspace 简介
1.1 什么是 pnpm workspace
pnpm workspace 是 pnpm 包管理器提供的 Monorepo(单体仓库)管理功能。它允许在单个仓库中管理多个相关的包(packages),这些包可以相互依赖,共享配置和工具链。
核心优势:
- 磁盘空间优化:pnpm 使用内容寻址存储,所有包文件都存储在全局 store 中,项目中只保留硬链接
- 严格的依赖管理:使用符号链接创建非扁平的 node_modules 结构,避免幽灵依赖
- 快速安装:并行安装依赖,性能优于 npm 和 yarn
- Monorepo 支持:原生支持工作空间,无需额外配置
- 节省带宽:相同版本的包只下载一次
1.2 与其他 Monorepo 工具对比
pnpm vs npm workspaces
bash
# npm workspaces 特点
- 原生支持,无需额外安装
- 相对简单,适合小型项目
- 磁盘占用较大
- 依赖提升可能导致幽灵依赖
# pnpm workspace 特点
- 需要单独安装 pnpm
- 功能更强大,性能更优
- 严格的依赖隔离
- 磁盘空间节省显著pnpm vs Yarn workspaces
bash
# Yarn workspaces 特点
- 成熟的 Monorepo 解决方案
- 磁盘占用中等
- 依赖提升策略灵活
- 社区支持广泛
# pnpm workspace 特点
- 更快的安装速度
- 更小的磁盘占用
- 更严格的依赖管理
- 兼容性更好1.3 适用场景
pnpm workspace 特别适合以下场景:
- 大型前端项目:多个应用共享组件库和工具
- 组件库开发:管理多个独立的组件包
- 全栈项目:前端、后端、共享代码在同一仓库
- 多团队协作:不同团队维护不同的包
- 包开发工具链:开发和测试 npm 包
2. 安装与配置
2.1 安装 pnpm
bash
# 使用 npm 安装
npm install -g pnpm
# 使用 Homebrew (macOS)
brew install pnpm
# 使用 Scoop (Windows)
scoop install pnpm
# 使用独立脚本
curl -fsSL https://get.pnpm.io/install.sh | sh -
# 验证安装
pnpm --version2.2 初始化 workspace
创建项目结构
bash
# 创建项目根目录
mkdir my-monorepo
cd my-monorepo
# 初始化 package.json
pnpm init配置 pnpm-workspace.yaml
在项目根目录创建 pnpm-workspace.yaml 文件:
yaml
# pnpm-workspace.yaml
packages:
# 所有在 packages 目录下的子目录
- 'packages/*'
# 所有在 apps 目录下的子目录
- 'apps/*'
# 排除测试目录
- '!**/test/**'更灵活的配置
yaml
# pnpm-workspace.yaml - 高级配置
packages:
# 使用通配符匹配多层目录
- 'packages/**'
- 'apps/**'
# 匹配特定命名模式
- 'components/react-*'
# 排除特定目录
- '!**/node_modules/**'
- '!**/dist/**'
- '!**/__tests__/**'
# 包含根目录的包
- '.'2.3 项目结构示例
my-monorepo/
├── pnpm-workspace.yaml # workspace 配置文件
├── package.json # 根 package.json
├── pnpm-lock.yaml # 锁定文件
├── .npmrc # pnpm 配置
├── packages/
│ ├── shared/ # 共享工具库
│ │ ├── package.json
│ │ └── src/
│ ├── ui-components/ # UI 组件库
│ │ ├── package.json
│ │ └── src/
│ └── utils/ # 工具函数库
│ ├── package.json
│ └── src/
└── apps/
├── web/ # Web 应用
│ ├── package.json
│ └── src/
└── admin/ # 管理后台
├── package.json
└── src/2.4 配置 .npmrc
创建 .npmrc 文件配置 pnpm 行为:
ini
# .npmrc
# 使用严格的对等依赖
strict-peer-dependencies=true
# 自动安装对等依赖
auto-install-peers=true
# 不提升依赖(推荐)
hoist=false
# 或者选择性提升
hoist-pattern[]=*eslint*
hoist-pattern[]=*prettier*
# 共享工作空间锁文件
shared-workspace-lockfile=true
# 保存精确版本
save-exact=true
# 使用符号链接
symlink=true
# 公共提升模式(可选)
public-hoist-pattern[]=*types*
public-hoist-pattern[]=eslint*
public-hoist-pattern[]=prettier*
# 设置仓库镜像(国内用户)
registry=https://registry.npmmirror.com
# 启用严格的 SSL
strict-ssl=true
# 引擎严格模式
engine-strict=true3. 基本使用
3.1 安装依赖
为根目录安装依赖
bash
# 安装开发依赖到根目录
pnpm add -D -w typescript eslint prettier
# -w 或 --workspace-root 标志表示安装到工作空间根目录
pnpm add -w lodash为特定包安装依赖
bash
# 为 web 应用安装 react
pnpm add react --filter web
# 为 ui-components 安装开发依赖
pnpm add -D @types/react --filter ui-components
# 使用相对路径
pnpm add react --filter ./apps/web
# 使用包名
pnpm add react --filter @myorg/web批量安装依赖
bash
# 为所有包安装依赖
pnpm install
# 为所有 apps 目录下的包安装
pnpm add react --filter "./apps/*"
# 为匹配模式的包安装
pnpm add lodash --filter "@myorg/*"3.2 workspace 协议
pnpm 支持使用 workspace: 协议引用工作空间内的包:
基本语法
json
// apps/web/package.json
{
"name": "@myorg/web",
"dependencies": {
"@myorg/ui-components": "workspace:*",
"@myorg/utils": "workspace:^",
"@myorg/shared": "workspace:~"
}
}协议说明
json
{
"dependencies": {
// workspace:* - 链接到工作空间内任何版本
"@myorg/ui": "workspace:*",
// workspace:^ - 链接并在发布时转换为 ^version
"@myorg/utils": "workspace:^",
// workspace:~ - 链接并在发布时转换为 ~version
"@myorg/shared": "workspace:~",
// workspace:版本号 - 指定具体版本
"@myorg/config": "workspace:1.0.0"
}
}自动链接配置
bash
# 添加工作空间依赖
cd apps/web
pnpm add @myorg/ui-components
# pnpm 会自动检测并使用 workspace: 协议
# 生成的 package.json:
# "@myorg/ui-components": "workspace:*"3.3 运行脚本
在特定包中运行脚本
bash
# 在 web 应用中运行 dev 脚本
pnpm --filter web dev
# 简写形式
pnpm -F web dev
# 在多个包中运行
pnpm --filter "@myorg/*" build并行运行脚本
bash
# 并行运行所有包的测试
pnpm -r --parallel test
# 限制并发数
pnpm -r --parallel --workspace-concurrency=2 build
# 顺序运行(考虑依赖关系)
pnpm -r build运行多个脚本
bash
# 使用 pnpm-recursive (pnpm -r)
pnpm -r run build
# 只在有该脚本的包中运行
pnpm -r --if-present test
# 根据拓扑顺序运行
pnpm -r --workspace-concurrency=1 build3.4 filter 过滤器
基本过滤
bash
# 按包名过滤
pnpm --filter @myorg/web dev
# 按目录过滤
pnpm --filter "./apps/web" dev
# 按模式过滤
pnpm --filter "@myorg/*" build高级过滤
bash
# 过滤包及其依赖
pnpm --filter @myorg/web... build
# 过滤包的依赖(不包括自身)
pnpm --filter ...@myorg/web build
# 过滤包及其依赖者
pnpm --filter ...@myorg/ui-components... build
# 排除特定包
pnpm --filter "!@myorg/admin" test
# 组合过滤器
pnpm --filter "./apps/*" --filter "!@myorg/admin" dev基于更改的过滤
bash
# 只运行改变的包
pnpm --filter "...[origin/main]" build
# 运行改变的包及其依赖者
pnpm --filter "...[origin/main]..." test
# 基于 Git 更改
pnpm --filter "{packages/ui-components}[HEAD~1]" build4. 依赖管理
4.1 workspace 依赖类型
内部依赖(Workspace Dependencies)
json
// packages/ui-components/package.json
{
"name": "@myorg/ui-components",
"version": "1.0.0",
"dependencies": {
// 工作空间内的包
"@myorg/utils": "workspace:*",
"@myorg/shared": "workspace:^"
}
}外部依赖(External Dependencies)
json
{
"dependencies": {
// 普通的 npm 包
"react": "^18.2.0",
"lodash": "^4.17.21"
}
}开发依赖(Dev Dependencies)
json
{
"devDependencies": {
"typescript": "^5.0.0",
"@types/react": "^18.0.0"
}
}4.2 依赖提升策略
禁用提升(推荐)
ini
# .npmrc
hoist=false这样每个包只能访问其声明的依赖,避免幽灵依赖。
选择性提升
ini
# .npmrc
hoist=false
# 提升特定包到根目录
hoist-pattern[]=*eslint*
hoist-pattern[]=*prettier*
hoist-pattern[]=*types*
# 公共提升(所有包都能访问)
public-hoist-pattern[]=*eslint*
public-hoist-pattern[]=*types*完全提升(不推荐)
ini
# .npmrc
hoist=true
shamefully-hoist=true4.3 对等依赖处理
自动安装对等依赖
ini
# .npmrc
auto-install-peers=true严格的对等依赖
ini
# .npmrc
strict-peer-dependencies=true在 package.json 中声明
json
{
"peerDependencies": {
"react": ">=17.0.0",
"react-dom": ">=17.0.0"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
}
}4.4 版本管理
Changesets 集成
安装 Changesets:
bash
pnpm add -D -w @changesets/cli
pnpm changeset init配置 .changeset/config.json:
json
{
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}使用 Changesets:
bash
# 添加变更集
pnpm changeset
# 更新版本
pnpm changeset version
# 发布
pnpm changeset publish版本锁定配置
json
// package.json
{
"scripts": {
"version": "changeset version",
"release": "pnpm build && changeset publish"
}
}5. 实战案例
5.1 创建 React Monorepo
步骤 1:初始化项目
bash
# 创建项目
mkdir react-monorepo
cd react-monorepo
pnpm init
# 创建 workspace 配置
cat > pnpm-workspace.yaml << EOF
packages:
- 'packages/*'
- 'apps/*'
EOF步骤 2:创建包结构
bash
# 创建目录
mkdir -p packages/ui apps/web apps/admin
# 初始化包
cd packages/ui && pnpm init && cd ../..
cd apps/web && pnpm init && cd ../..
cd apps/admin && pnpm init && cd ../..步骤 3:配置 TypeScript
安装 TypeScript:
bash
pnpm add -D -w typescript @types/node创建根 tsconfig.json:
json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"baseUrl": ".",
"paths": {
"@myorg/ui": ["./packages/ui/src"],
"@myorg/ui/*": ["./packages/ui/src/*"]
}
}
}步骤 4:配置 UI 组件包
json
// packages/ui/package.json
{
"name": "@myorg/ui",
"version": "1.0.0",
"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 cjs,esm --dts",
"dev": "tsup src/index.ts --format cjs,esm --dts --watch"
},
"peerDependencies": {
"react": ">=18.0.0"
}
}安装依赖:
bash
pnpm add react --filter @myorg/ui --save-peer
pnpm add -D tsup --filter @myorg/ui步骤 5:创建组件
tsx
// packages/ui/src/Button.tsx
import React from 'react';
export interface ButtonProps {
children: React.ReactNode;
onClick?: () => void;
variant?: 'primary' | 'secondary';
}
export const Button: React.FC<ButtonProps> = ({
children,
onClick,
variant = 'primary'
}) => {
return (
<button
onClick={onClick}
className={`btn btn-${variant}`}
>
{children}
</button>
);
};tsx
// packages/ui/src/index.ts
export { Button } from './Button';
export type { ButtonProps } from './Button';步骤 6:配置 Web 应用
json
// apps/web/package.json
{
"name": "@myorg/web",
"version": "1.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"@myorg/ui": "workspace:*"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.0.0",
"vite": "^5.0.0"
}
}安装依赖:
bash
pnpm add react react-dom --filter @myorg/web
pnpm add -D vite @vitejs/plugin-react --filter @myorg/web创建 Vite 配置:
ts
// apps/web/vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@myorg/ui': path.resolve(__dirname, '../../packages/ui/src')
}
}
});步骤 7:使用组件
tsx
// apps/web/src/App.tsx
import { Button } from '@myorg/ui';
function App() {
return (
<div>
<h1>My App</h1>
<Button onClick={() => alert('Clicked!')}>
Click Me
</Button>
</div>
);
}
export default App;步骤 8:添加脚本
json
// package.json (根目录)
{
"scripts": {
"dev": "pnpm --filter @myorg/web dev",
"dev:all": "pnpm --parallel -r dev",
"build": "pnpm -r build",
"build:ui": "pnpm --filter @myorg/ui build",
"build:web": "pnpm --filter @myorg/web build"
}
}5.2 创建全栈 Monorepo
项目结构
fullstack-monorepo/
├── pnpm-workspace.yaml
├── package.json
├── packages/
│ ├── shared/ # 共享类型和工具
│ ├── database/ # 数据库模型和迁移
│ └── config/ # 共享配置
└── apps/
├── web/ # Next.js 前端
├── api/ # Express 后端
└── mobile/ # React Native 应用共享类型包
typescript
// packages/shared/src/types/user.ts
export interface User {
id: string;
email: string;
name: string;
createdAt: Date;
}
export interface CreateUserDto {
email: string;
name: string;
password: string;
}
export interface LoginDto {
email: string;
password: string;
}json
// packages/shared/package.json
{
"name": "@myorg/shared",
"version": "1.0.0",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch"
},
"devDependencies": {
"typescript": "^5.0.0"
}
}API 应用
json
// apps/api/package.json
{
"name": "@myorg/api",
"version": "1.0.0",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
},
"dependencies": {
"express": "^4.18.0",
"@myorg/shared": "workspace:*",
"@myorg/database": "workspace:*"
},
"devDependencies": {
"@types/express": "^4.17.0",
"tsx": "^3.12.0",
"typescript": "^5.0.0"
}
}typescript
// apps/api/src/routes/users.ts
import { Router } from 'express';
import { CreateUserDto, User } from '@myorg/shared';
import { UserModel } from '@myorg/database';
const router = Router();
router.post('/users', async (req, res) => {
const dto: CreateUserDto = req.body;
const user = await UserModel.create(dto);
res.json(user);
});
export default router;Web 应用
json
// apps/web/package.json
{
"name": "@myorg/web",
"version": "1.0.0",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "^14.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"@myorg/shared": "workspace:*"
}
}tsx
// apps/web/src/app/api/users/route.ts
import { NextResponse } from 'next/server';
import { CreateUserDto } from '@myorg/shared';
export async function POST(request: Request) {
const dto: CreateUserDto = await request.json();
const response = await fetch('http://localhost:3001/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(dto)
});
const user = await response.json();
return NextResponse.json(user);
}5.3 组件库 Monorepo
项目结构
ui-library/
├── pnpm-workspace.yaml
├── packages/
│ ├── react/ # React 组件
│ ├── vue/ # Vue 组件
│ ├── core/ # 框架无关的核心逻辑
│ ├── icons/ # 图标库
│ └── themes/ # 主题系统
└── apps/
├── docs/ # 文档站点
└── playground/ # 演示应用Core 包(框架无关)
typescript
// packages/core/src/utils/classnames.ts
export function cn(...classes: (string | undefined | null | false)[]): string {
return classes.filter(Boolean).join(' ');
}
// packages/core/src/hooks/useClickOutside.ts
export function createClickOutsideHandler(
callback: () => void
) {
return (element: HTMLElement) => {
const handler = (event: MouseEvent) => {
if (!element.contains(event.target as Node)) {
callback();
}
};
document.addEventListener('click', handler);
return () => {
document.removeEventListener('click', handler);
};
};
}React 组件包
json
// packages/react/package.json
{
"name": "@myorg/react",
"version": "1.0.0",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"dependencies": {
"@myorg/core": "workspace:*",
"@myorg/icons": "workspace:*"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}
}tsx
// packages/react/src/Button/Button.tsx
import React from 'react';
import { cn } from '@myorg/core';
export interface ButtonProps {
children: React.ReactNode;
variant?: 'primary' | 'secondary' | 'outline';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
onClick?: () => void;
}
export const Button: React.FC<ButtonProps> = ({
children,
variant = 'primary',
size = 'md',
disabled = false,
onClick
}) => {
return (
<button
className={cn(
'btn',
`btn-${variant}`,
`btn-${size}`,
disabled && 'btn-disabled'
)}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
};文档站点
json
// apps/docs/package.json
{
"name": "@myorg/docs",
"version": "1.0.0",
"scripts": {
"dev": "vitepress dev",
"build": "vitepress build",
"preview": "vitepress preview"
},
"dependencies": {
"@myorg/react": "workspace:*",
"@myorg/vue": "workspace:*"
},
"devDependencies": {
"vitepress": "^1.0.0"
}
}6. 性能优化
6.1 并行安装优化
ini
# .npmrc
# 网络并发数
network-concurrency=16
# 子进程并发数
child-concurrency=5
# 使用本地缓存
prefer-offline=true
# 缓存位置
store-dir=/path/to/.pnpm-store6.2 构建优化
增量构建配置
json
// package.json
{
"scripts": {
"build": "pnpm -r --filter \"...[origin/main]\" build",
"build:all": "pnpm -r build"
}
}缓存构建产物
使用 Turborepo 或 Nx 缓存构建结果:
bash
# 安装 Turborepo
pnpm add -D -w turbo
# turbo.json
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
}
}
}6.3 依赖去重
bash
# 检查重复依赖
pnpm list --depth=Infinity
# 去重依赖
pnpm dedupe
# 修剪不需要的依赖
pnpm prune6.4 本地开发优化
使用 pnpm patch
bash
# 创建补丁
pnpm patch some-package@1.0.0
# 编辑补丁后提交
pnpm patch-commit /path/to/patch使用链接开发
bash
# 全局链接
cd packages/ui
pnpm link --global
# 在其他项目中使用
cd ~/other-project
pnpm link --global @myorg/ui7. 持续集成
7.1 GitHub Actions 配置
yaml
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run linter
run: pnpm -r lint
- name: Run tests
run: pnpm -r test
- name: Build
run: pnpm -r build7.2 增量 CI
只测试和构建更改的包:
yaml
# .github/workflows/ci.yml
- name: Get changed packages
id: changed-packages
run: |
echo "packages=$(pnpm list --filter \"...[origin/main]\" --depth -1 --json | jq -r '.[].name' | tr '\n' ',')" >> $GITHUB_OUTPUT
- name: Test changed packages
if: steps.changed-packages.outputs.packages != ''
run: pnpm --filter ${{ steps.changed-packages.outputs.packages }} test
- name: Build changed packages
if: steps.changed-packages.outputs.packages != ''
run: pnpm --filter "${{ steps.changed-packages.outputs.packages }}..." build7.3 发布流程
yaml
# .github/workflows/release.yml
name: Release
on:
push:
branches:
- main
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- name: Create Release Pull Request or Publish
uses: changesets/action@v1
with:
publish: pnpm release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}8. 常见问题与解决方案
8.1 幽灵依赖问题
问题: 代码中使用了未在 package.json 中声明的依赖。
解决方案:
ini
# .npmrc
hoist=false
strict-peer-dependencies=true8.2 循环依赖
问题: 包 A 依赖包 B,包 B 又依赖包 A。
解决方案:
- 重构代码,提取共享逻辑到新包
- 使用依赖注入
- 延迟导入
typescript
// 延迟导入避免循环依赖
export function someFunction() {
const { someOtherFunction } = require('./other-module');
return someOtherFunction();
}8.3 TypeScript 路径解析
问题: TypeScript 无法解析 workspace 包的路径。
解决方案:
json
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@myorg/*": ["packages/*/src"]
}
}
}8.4 构建顺序问题
问题: 包的构建顺序不正确,导致依赖的包未构建。
解决方案:
bash
# 使用拓扑排序构建
pnpm -r --workspace-concurrency=1 build
# 或使用 filter 的依赖链
pnpm --filter "@myorg/web..." build8.5 开发时热更新
问题: 修改组件库后,应用未自动刷新。
解决方案:
在组件库中启用 watch 模式:
json
// packages/ui/package.json
{
"scripts": {
"dev": "tsup src/index.ts --format cjs,esm --dts --watch"
}
}在应用中配置别名指向源码:
ts
// apps/web/vite.config.ts
export default defineConfig({
resolve: {
alias: {
'@myorg/ui': path.resolve(__dirname, '../../packages/ui/src')
}
}
});9. 最佳实践
9.1 包命名规范
使用 scope 命名:
json
{
"name": "@公司名/包名",
"name": "@myorg/ui-components",
"name": "@myorg/web-app"
}9.2 版本管理策略
Fixed Versioning(固定版本)
所有包使用相同版本号:
json
// .changeset/config.json
{
"fixed": [
["@myorg/ui", "@myorg/icons", "@myorg/themes"]
]
}Independent Versioning(独立版本)
每个包独立管理版本:
json
// .changeset/config.json
{
"fixed": []
}9.3 共享配置
创建共享配置包:
json
// packages/config/package.json
{
"name": "@myorg/eslint-config",
"main": "index.js",
"dependencies": {
"eslint": "^8.0.0",
"eslint-config-prettier": "^9.0.0"
}
}js
// packages/config/index.js
module.exports = {
extends: ['eslint:recommended', 'prettier'],
rules: {
'no-console': 'warn'
}
};在其他包中使用:
json
// apps/web/package.json
{
"devDependencies": {
"@myorg/eslint-config": "workspace:*"
}
}json
// apps/web/.eslintrc.json
{
"extends": "@myorg/eslint-config"
}9.4 脚本组织
根目录的 package.json 脚本应该清晰明了:
json
{
"scripts": {
// 开发
"dev": "pnpm --parallel -r dev",
"dev:web": "pnpm --filter @myorg/web dev",
"dev:api": "pnpm --filter @myorg/api dev",
// 构建
"build": "pnpm -r build",
"build:libs": "pnpm --filter \"./packages/*\" build",
"build:apps": "pnpm --filter \"./apps/*\" build",
// 测试
"test": "pnpm -r test",
"test:unit": "pnpm -r --filter \"./packages/*\" test",
"test:e2e": "pnpm --filter \"./apps/*\" test:e2e",
// 代码质量
"lint": "pnpm -r lint",
"lint:fix": "pnpm -r lint:fix",
"type-check": "pnpm -r type-check",
// 清理
"clean": "pnpm -r clean && rm -rf node_modules",
"clean:dist": "pnpm -r --parallel run clean",
// 发布
"changeset": "changeset",
"version-packages": "changeset version",
"release": "pnpm build && changeset publish"
}
}9.5 文档管理
为每个包提供 README:
markdown
# @myorg/ui-components
React UI 组件库
## 安装
\`\`\`bash
pnpm add @myorg/ui-components
\`\`\`
## 使用
\`\`\`tsx
import { Button } from '@myorg/ui-components';
function App() {
return <Button>Click me</Button>;
}
\`\`\`
## API
### Button
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| variant | 'primary' \| 'secondary' | 'primary' | 按钮样式 |
| size | 'sm' \| 'md' \| 'lg' | 'md' | 按钮大小 |10. 总结
pnpm workspace 是一个强大的 Monorepo 管理工具,具有以下优势:
10.1 核心优势
- 节省磁盘空间:通过硬链接共享依赖
- 严格的依赖管理:避免幽灵依赖
- 快速安装:并行安装,智能缓存
- 原生 Monorepo 支持:workspace 协议简单易用
- 性能优异:比 npm 和 yarn 更快
10.2 适用场景
- 大型 React/Vue 项目
- 组件库开发
- 全栈应用
- 多团队协作项目
- npm 包开发
10.3 关键要点
- 使用
pnpm-workspace.yaml定义工作空间 - 使用
workspace:协议引用内部包 - 使用
--filter精确控制操作范围 - 配置
.npmrc优化行为 - 集成 Changesets 管理版本
- 使用 CI/CD 自动化流程
10.4 注意事项
- 避免循环依赖
- 合理配置依赖提升策略
- 注意 TypeScript 路径配置
- 确保构建顺序正确
- 做好文档和规范
通过合理使用 pnpm workspace,可以极大提升 Monorepo 项目的开发效率和代码质量。