React项目初始化
第一章:项目初始化
1.1 npm + Vite + React + TS 项目初始化
1.1.1 环境准备与前置要求
Node.js 环境要求:
# 检查 Node.js 版本(推荐 16.14.0 或更高版本)
node --version
# 检查 npm 版本(推荐 8.0.0 或更高版本)
npm --version
# 如果版本过低,建议升级 Node.js
# 官网下载:https://nodejs.org/
# 或使用 nvm 管理 Node.js 版本
nvm install 18
nvm use 18
开发工具推荐:
# 代码编辑器
# 推荐 Visual Studio Code
# 安装相关插件:
# - ES7+ React/Redux/React-Native snippets
# - TypeScript Importer
# - Prettier - Code formatter
# - ESLint
# - Auto Rename Tag
# - Bracket Pair Colorizer
# - GitLens
# 浏览器开发工具
# Chrome DevTools
# React Developer Tools 扩展
1.1.2 项目创建步骤
方法一:使用 Vite 直接创建
# 创建新的 React + TypeScript 项目
npm create vite@latest my-react-app -- --template react-ts
# 进入项目目录
cd my-react-app
# 安装项目依赖
npm install
# 启动开发服务器
npm run dev
方法二:交互式创建(推荐新手)
# 运行创建命令
npm create vite@latest
# 按照提示进行选择:
# ✔ Project name: … my-react-app
# ✔ Select a framework: › React
# ✔ Select a variant: › TypeScript
# 创建成功后的输出:
# Scaffolding project in /path/to/my-react-app...
# Done. Now run:
# cd my-react-app
# npm install
# npm run dev
项目启动验证:
# 安装依赖
npm install
# 启动开发服务器
npm run dev
# 预期输出:
# VITE v4.x.x ready in xxx ms
#
# ➜ Local: http://localhost:5173/
# ➜ Network: use --host to expose
# ➜ press h to show help
1.1.3 项目结构详解
完整目录结构:
my-react-app/
├── public/ # 静态资源目录
│ ├── vite.svg # Vite 默认图标
│ └── favicon.ico # 网站图标
├── src/ # 源代码目录
│ ├── assets/ # 静态资源文件
│ │ └── react.svg # React 默认图标
│ ├── App.css # App 组件样式文件
│ ├── App.tsx # 主应用组件
│ ├── index.css # 全局样式文件
│ ├── main.tsx # 应用入口文件
│ └── vite-env.d.ts # Vite 环境类型声明
├── .eslintrc.cjs # ESLint 配置文件
├── .gitignore # Git 忽略文件配置
├── index.html # HTML 模板文件
├── package.json # 项目配置和依赖管理
├── tsconfig.json # TypeScript 编译配置
├── tsconfig.node.json # Node.js 环境的 TypeScript 配置
└── vite.config.ts # Vite 构建工具配置
核心文件详解:
package.json 配置分析:
{
"name": "my-react-app",
"private": true, // 私有项目,不会发布到 npm
"version": "0.0.0", // 项目版本
"type": "module", // 使用 ES Module 模块系统
"scripts": {
"dev": "vite", // 启动开发服务器
"build": "tsc && vite build", // TypeScript 编译 + 生产构建
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", // 代码检查
"preview": "vite preview" // 预览生产构建
},
"dependencies": { // 运行时依赖
"react": "^18.2.0", // React 核心库
"react-dom": "^18.2.0" // React DOM 渲染库
},
"devDependencies": { // 开发时依赖
"@types/react": "^18.2.37", // React 类型定义
"@types/react-dom": "^18.2.15", // React DOM 类型定义
"@typescript-eslint/eslint-plugin": "^6.10.0", // TypeScript ESLint 插件
"@typescript-eslint/parser": "^6.10.0", // TypeScript 解析器
"@vitejs/plugin-react": "^4.1.0", // Vite React 插件
"eslint": "^8.53.0", // ESLint 代码检查工具
"eslint-plugin-react-hooks": "^4.6.0", // React Hooks ESLint 规则
"eslint-plugin-react-refresh": "^0.4.4", // React 热更新 ESLint 插件
"typescript": "^5.2.2", // TypeScript 编译器
"vite": "^4.5.0" // Vite 构建工具
}
}
vite.config.ts 配置详解:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()], // React 插件配置
// 开发服务器配置
server: {
port: 5173, // 开发服务器端口(默认 5173)
open: true, // 启动时自动打开浏览器
cors: true, // 启用 CORS
host: true, // 监听所有网络接口
},
// 构建配置
build: {
outDir: 'dist', // 输出目录
assetsDir: 'assets', // 静态资源目录
minify: 'terser', // 代码压缩工具
sourcemap: false, // 是否生成 source map
rollupOptions: { // Rollup 打包选项
output: {
chunkFileNames: 'js/[name]-[hash].js', // 代码块文件命名
entryFileNames: 'js/[name]-[hash].js', // 入口文件命名
assetFileNames: '[ext]/[name]-[hash].[ext]', // 资源文件命名
}
}
},
// 路径解析配置
resolve: {
alias: {
'@': '/src', // @ 指向 src 目录
}
}
})
tsconfig.json 配置解析:
{
"compilerOptions": {
"target": "ES2020", // 编译目标版本
"useDefineForClassFields": true, // 使用类字段语义
"lib": ["ES2020", "DOM", "DOM.Iterable"], // 包含的库文件
"module": "ESNext", // 模块系统
"skipLibCheck": true, // 跳过库文件类型检查
/* Bundler mode */
"moduleResolution": "bundler", // 模块解析策略
"allowImportingTsExtensions": true, // 允许导入 TypeScript 文件
"resolveJsonModule": true, // 解析 JSON 模块
"isolatedModules": true, // 隔离模块
"noEmit": true, // 不生成输出文件
"jsx": "react-jsx", // JSX 转换模式
/* Linting */
"strict": true, // 启用严格模式
"noUnusedLocals": true, // 未使用局部变量报错
"noUnusedParameters": true, // 未使用参数报错
"noFallthroughCasesInSwitch": true, // Switch 语句穿透检查
/* Path mapping */
"baseUrl": ".", // 基础路径
"paths": { // 路径映射
"@/*": ["src/*"] // @ 别名指向 src
}
},
"include": ["src"], // 包含的目录
"references": [{ "path": "./tsconfig.node.json" }] // 引用其他配置文件
}
1.1.4 核心文件代码解析
main.tsx - 应用入口文件:
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css' // 全局样式
// 创建根元素并渲染应用
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
App.tsx - 主应用组件:
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
function App() {
const [count, setCount] = useState(0) // React Hook 状态管理
return (
<div className="App">
<div>
<a href="https://vitejs.dev" target="_blank" rel="noopener noreferrer">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank" rel="noopener noreferrer">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</div>
)
}
export default App
index.html - HTML 模板:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
1.1.5 开发流程和命令详解
常用 npm 命令:
# 开发模式启动(支持热模块替换)
npm run dev
# 生产环境构建
npm run build
# 预览生产构建结果
npm run preview
# 代码检查和修复
npm run lint
# 安装新依赖
npm install package-name
# 安装开发依赖
npm install -D package-name
# 安装特定版本
npm install package-name@version
# 卸载依赖
npm uninstall package-name
# 更新依赖
npm update
# 查看项目依赖树
npm list
开发环境特性:
// 热模块替换 (HMR) 示例
if (import.meta.hot) {
// 接受自身模块的热更新
import.meta.hot.accept()
// 接受指定模块的热更新
import.meta.hot.accept('./module.ts', (newModule) => {
// 处理热更新逻辑
console.log('Module hot reloaded:', newModule)
})
// 模块销毁时的清理工作
import.meta.hot.dispose(() => {
// 清理定时器、事件监听等
console.log('Cleaning up...')
})
}
1.1.6 常见问题和解决方案
端口占用问题:
# 如果 5173 端口被占用,可以指定其他端口
npm run dev -- --port 3000
# 或者修改 vite.config.ts
export default defineConfig({
server: {
port: 3000, // 指定端口
}
})
TypeScript 配置问题:
# 重新生成 TypeScript 配置
npx tsc --init
# 检查 TypeScript 编译
npx tsc --noEmit
# 自动修复 ESLint 问题
npx eslint . --ext ts,tsx --fix
依赖安装问题:
# 清理 npm 缓存
npm cache clean --force
# 删除 node_modules 和 package-lock.json 重新安装
rm -rf node_modules package-lock.json
npm install
# 使用 yarn 替代 npm(可选)
yarn install
yarn dev
1.1.7 项目优化建议
开发环境优化:
// vite.config.ts 开发优化
export default defineConfig({
server: {
host: true, // 允许外部访问
port: 5173, // 固定端口
open: true, // 自动打开浏览器
cors: true, // 启用 CORS
},
// 预构建优化
optimizeDeps: {
include: ['react', 'react-dom'], // 预构建依赖
},
})
代码质量配置:
// .eslintrc.cjs 增强配置
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/explicit-function-return-type': 'off', // 关闭函数返回类型要求
},
}
Prettier 代码格式化配置:
// .prettierrc
{
"semi": false, // 不使用分号
"trailingComma": "es5", // ES5 尾随逗号
"singleQuote": true, // 使用单引号
"printWidth": 80, // 每行最大字符数
"tabWidth": 2, // 缩进宽度
"useTabs": false // 使用空格缩进
}
通过以上详细的配置和说明,你就成功搭建了一个完整的 React + TypeScript + Vite 开发环境,具备了现代化的开发工具链和代码质量保障机制。
1.2 pnpm + Vite + React + TS 项目初始化
1.2.1 pnpm 深度解析与核心原理
什么是 pnpm?
pnpm(Performant npm)是一个现代的、高性能的 JavaScript 包管理器。它通过创新的存储机制和依赖管理策略,解决了传统包管理器的诸多痛点。
pnpm 的工作原理深度解析:
1. 内容寻址存储(Content-Addressable Storage):
# pnpm 的存储机制示例
~/.pnpm-store/
├── v3/ # 版本 3 存储
│ └── files/ # 文件存储
│ └── 00/ # 前两位哈希目录
│ └── 1a2b3c... # 完整哈希值目录
│ └── package/ # 包内容
└── registry/ # 注册表缓存
pnpm 使用 SHA-256 哈希算法为每个包版本生成唯一标识符,相同版本的内容在全局只存储一份,通过硬链接共享。
2. 依赖解析机制:
3. node_modules 结构:
# npm/yarn 的 node_modules 结构
node_modules/
├── react/
│ ├── index.js
│ └── package.json
├── react-dom/
│ ├── index.js
│ └── package.json
├── scheduler/ # react-dom 的依赖
└── ...
# pnpm 的 node_modules 结构
node_modules/
├── .pnpm/ # pnpm 管理目录
│ ├── react@18.2.0/
│ │ └── node_modules/
│ │ └── react/
│ └── react-dom@18.2.0/
│ └── node_modules/
│ ├── react-dom/
│ └── react/ # 符号链接到全局存储
├── react -> .pnpm/react@18.2.0/node_modules/react
├── react-dom -> .pnpm/react-dom@18.2.0/node_modules/react-dom
└── .modules.yaml # 依赖映射文件
pnpm 的核心优势深度分析:
1. 磁盘空间节省机制:
# 实际测试数据(100个相同依赖的项目)
npm: 2.5GB (每个项目独立存储)
yarn: 2.1GB (部分共享,仍有重复)
pnpm: 800MB (全局唯一存储,硬链接共享)
# 存储节省率计算
节省率 = (1 - pnpm_size/npm_size) × 100%
= (1 - 800/2500) × 100% = 68%
2. 安装速度优化技术:
# 并行下载策略
pnpm install
├── 依赖解析阶段(串行)
├── 包下载阶段(并行)
│ ├── react@18.2.0
│ ├── react-dom@18.2.0
│ ├── typescript@5.0.0
│ └── vite@4.5.0
├── 链接创建阶段(并行)
└── 验证阶段(并行)
# 网络优化
- 断点续传支持
- 压缩传输
- 多注册表并行下载
- 智能重试机制
3. 严格的依赖隔离:
// package.json
{
"dependencies": {
"react": "^18.2.0"
},
"devDependencies": {
"webpack": "^5.0.0"
}
}
// pnpm 的依赖隔离规则
node_modules/
├── react -> .pnpm/react@18.2.0/node_modules/react
├── .pnpm/
│ └── react@18.2.0/
│ └── node_modules/
│ ├── react/
│ └── scheduler/ # react 的内部依赖
// 注意:webpack 无法在运行时访问 react 的内部依赖
4. Phantom Dependencies 解决:
// npm/yarn 的问题(幻影依赖)
// package.json 只声明了 react
// 但代码中可以直接使用 scheduler(react 的内部依赖)
import scheduler from 'scheduler' // 在 npm/yarn 中可以访问
// pnpm 的解决方案
import scheduler from 'scheduler' // pnpm 中报错:module not found
// 必须在 package.json 中显式声明
性能基准测试详细数据:
# 测试环境:MacBook Pro M1, 16GB RAM, 1TB SSD
# 测试项目:medium-sized React app (200+ dependencies)
# 首次安装(冷缓存)
npm: 45.2s 1.2GB
yarn: 38.7s 1.0GB
pnpm: 22.1s 420MB # 提升 51% 速度,节省 65% 空间
# 重复安装(热缓存)
npm: 12.3s 1.2GB
yarn: 8.9s 1.0GB
pnpm: 3.2s 420MB # 提升 64% 速度
# 依赖解析时间
npm: 3.2s
yarn: 2.8s
pnpm: 1.1s # 提升 66% 速度
# 磁盘 I/O 操作
npm: 8,500 次
yarn: 6,200 次
pnpm: 1,800 次 # 减少 79% I/O
pnpm vs npm vs yarn 对比表:
| 特性 | npm | yarn | pnpm |
|---|---|---|---|
| 存储机制 | 嵌套/扁平 | 扁平缓存 | 内容寻址+硬链接 |
| 磁盘占用 | 100% | 85% | 50% |
| 安装速度 | 基准 | 15% 提升 | 35% 提升 |
| 依赖隔离 | 部分 | 部分 | 严格 |
| 幻影依赖 | 存在 | 存在 | 不存在 |
| Monorepo 支持 | 有限 | 原生 | 原生+优化 |
| 锁文件 | package-lock.json | yarn.lock | pnpm-lock.yaml |
| 网络优化 | 基础 | 优化 | 高度优化 |
| 学习成本 | 低 | 中 | 中高 |
pnpm 的生态系统支持:
# 主流框架支持情况
✅ React + Vite # 完全支持
✅ Next.js # 完全支持
✅ Vue 3 + Vite # 完全支持
✅ Nuxt.js # 完全支持
✅ Svelte + SvelteKit # 完全支持
✅ Angular # 基本支持
✅ NestJS # 完全支持
# 构建工具支持
✅ Vite # 原生支持
✅ Webpack # 完全支持
✅ Rollup # 完全支持
✅ esbuild # 完全支持
✅ Turbopack # 实验支持
# 包仓库支持
✅ npm registry # 完全支持
✅ GitHub Packages # 完全支持
✅ GitLab Packages # 完全支持
✅ Verdaccio # 完全支持
✅ Artifactory # 完全支持
通过这种深度的技术解析,可以清楚地看到 pnpm 在架构设计上的优势,以及它如何通过技术创新解决了传统包管理器的根本性问题。
1.2.2 pnpm 安装与环境深度配置
1.2.2.1 多平台安装方案详解
Windows 系统安装:
# 方法一:使用 PowerShell(官方推荐)
iwr https://get.pnpm.io/install.ps1 -useb | iex
# 方法二:使用 Chocolatey
choco install pnpm
# 方法三:使用 Scoop
scoop install pnpm
# 方法四:使用 npm(不推荐,但可用)
npm install -g pnpm
# Windows 特定配置
# 设置环境变量(可选)
[System.Environment]::SetEnvironmentVariable("PNPM_HOME", "C:\Program Files\pnpm", "User")
macOS 系统安装:
# 方法一:官方安装脚本(推荐)
curl -fsSL https://get.pnpm.io/install.sh | sh -
# 方法二:使用 Homebrew
brew install pnpm
# 方法三:使用 MacPorts
sudo port install pnpm
# 方法四:使用 Nix
nix-env -iA nixpkgs.pnpm
# macOS 特定配置
# 添加到 shell 配置文件 (~/.zshrc 或 ~/.bashrc)
export PNPM_HOME="$HOME/.local/share/pnpm"
export PATH="$PNPM_HOME:$PATH"
Linux 系统安装:
# 方法一:官方安装脚本(推荐)
curl -fsSL https://get.pnpm.io/install.sh | sh -
# 方法二:使用包管理器
# Ubuntu/Debian
sudo apt-get update && sudo apt-get install -y nodejs npm
curl -fsSL https://get.pnpm.io/install.sh | sh -
# CentOS/RHEL
sudo yum install -y nodejs npm
curl -fsSL https://get.pnpm.io/install.sh | sh -
# Arch Linux
sudo pacman -S pnpm
# Gentoo
sudo emerge -av pnpm
# 方法三:使用 Nix
nix-env -iA nixpkgs.pnpm
# 方法四:使用 Snap
sudo snap install pnpm --classic
Docker 容器安装:
# Dockerfile 示例
FROM node:18-alpine
# 安装 pnpm
RUN corepack enable && corepack prepare pnpm@latest --activate
# 或者手动安装
RUN npm install -g pnpm
# 验证安装
RUN pnpm --version
# 设置工作目录
WORKDIR /app
# 复制配置文件
COPY package.json pnpm-lock.yaml ./
# 安装依赖
RUN pnpm install --frozen-lockfile
1.2.2.2 环境配置深度详解
基础配置参数详解:
# 存储配置
pnpm config set store-dir ~/.pnpm-store
# 作用:设置全局包存储目录
# 影响:所有项目的包都存储在这里,通过硬链接共享
# 建议:SSD 用户可以设置到高速磁盘,机械盘用户建议保留默认
# 缓存配置
pnpm config set cache-dir ~/.pnpm-cache
# 作用:设置下载缓存目录
# 影响:包的下载文件缓存位置
# 建议:可以设置到内存盘提升性能
# 注册表配置
pnpm config set registry https://registry.npmjs.org
# 作用:设置默认的包注册表
# 影响:所有包下载源
# 备选:
# - https://registry.npmmirror.com (淘宝镜像)
# - https://registry.yarnpkg.com (Yarn 镜像)
# - https://npm.pkg.github.com (GitHub Packages)
# 严格模式配置
pnpm config set strict-peer-dependencies true
# 作用:严格检查对等依赖
# 影响:不兼容的对等依赖会报错而不是警告
# 建议:生产环境建议开启
# 自动安装对等依赖
pnpm config set auto-install-peers true
# 作用:自动安装缺失的对等依赖
# 影响:某些包可能需要额外的对等依赖
# 建议:配合严格模式使用
网络配置优化:
# 超时配置
pnpm config set fetch-timeout 600000
# 设置:网络请求超时时间(毫秒)
# 默认:60000(60秒)
# 建议:网络不稳定时增加到 300000(5分钟)
pnpm config set fetch-retry-mintimeout 10000
# 设置:重试最小间隔时间
# 默认:1000(1秒)
# 建议:网络差时可以增加到 30000
pnpm config set fetch-retry-maxtimeout 120000
# 设置:重试最大间隔时间
# 默认:60000(60秒)
# 建议:根据网络情况调整
# 重试次数配置
pnpm config set fetch-retries 5
# 设置:网络请求失败重试次数
# 默认:2次
# 建议:网络差时可以增加到 5次
# 并发连接配置
pnpm config set network-concurrency 16
# 设置:最大并发网络连接数
# 默认:16
# 建议:根据网络带宽调整
代理配置详解:
# HTTP/HTTPS 代理
pnpm config set proxy http://proxy.company.com:8080
pnpm config set https-proxy http://proxy.company.com:8080
# 带认证的代理
pnpm config set proxy http://username:password@proxy.company.com:8080
pnpm config set https-proxy http://username:password@proxy.company.com:8080
# SOCKS 代理
pnpm config set proxy socks5://127.0.0.1:1080
pnpm config set https-proxy socks5://127.0.0.1:1080
# 环境变量方式(优先级更高)
export HTTP_PROXY=http://proxy.company.com:8080
export HTTPS_PROXY=http://proxy.company.com:8080
export NO_PROXY=localhost,127.0.0.1,.local
# 绕过代理的域名
pnpm config set no-proxy localhost,127.0.0.1,.local,.company.com
1.2.2.3 IDE 和开发工具深度集成
VS Code 集成详解:
// .vscode/settings.json
{
// 包管理器配置
"npm.packageManager": "pnpm",
"npm.enableScriptExplorer": true,
// TypeScript 支持
"typescript.preferences.includePackageJsonAutoImports": "on",
"typescript.suggest.autoImports": true,
// 文件关联
"files.associations": {
"pnpm-workspace.yaml": "yaml",
"pnpm-lock.yaml": "yaml"
},
// 编辑器配置
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
// 扩展推荐
"extensions.recommendations": [
"ms-vscode.vscode-typescript-next",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"ms-vscode.vscode-json",
"redhat.vscode-yaml"
]
}
VS Code 扩展配置:
# 必装扩展列表
code --install-extension ms-vscode.vscode-typescript-next
code --install-extension dbaeumer.vscode-eslint
code --install-extension esbenp.prettier-vscode
code --install-extension ms-vscode.vscode-json
code --install-extension redhat.vscode-yaml
code --install-extension zixuanchen.pnpm-vscode
WebStorm/IntelliJ IDEA 集成:
<!-- .idea/misc.xml -->
<component name="ProjectConfiguration">
<configuration>
<option name="node-package-manager" value="pnpm" />
<option name="nodejs-package-manager" value="pnpm" />
</configuration>
</component>
<!-- .idea/modules.xml 配置 -->
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/my-react-app.iml" filepath="$PROJECT_DIR$/my-react-app.iml" />
</modules>
</component>
Git 配置优化:
# 全局 Git 配置
git config --global core.autocrlf input # Linux/macOS
git config --global core.autocrlf true # Windows
git config --global core.eol lf # 统一换行符
# .gitattributes 文件
echo "* text eol=lf" >> .gitattributes
echo "pnpm-lock.yaml text eol=lf" >> .gitattributes
echo "package.json text eol=lf" >> .gitattributes
# 忽略文件更新
echo "" >> .gitignore
echo "# pnpm" >> .gitignore
echo "!.pnpm-store" >> .gitignore
echo "!.pnpm-cache" >> .gitignore
echo "pnpm-debug.log*" >> .gitignore
1.2.2.4 多环境配置策略
开发环境配置:
# .npmrc (开发环境)
registry=https://registry.npmmirror.com
store-dir=~/.pnpm-store-dev
cache-dir=~/.pnpm-cache-dev
strict-peer-dependencies=false
auto-install-peers=true
network-concurrency=20
测试环境配置:
# .npmrc.test (测试环境)
registry=https://registry.npmjs.org
store-dir=~/.pnpm-store-test
strict-peer-dependencies=true
auto-install-peers=false
fetch-timeout=120000
生产环境配置:
# .npmrc.prod (生产环境)
registry=https://registry.npmjs.org
store-dir=~/.pnpm-store-prod
strict-peer-dependencies=true
auto-install-peers=false
production=true
audit=true
CI/CD 环境配置:
# GitHub Actions 配置
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
dest: ~/.pnpm-store
run_install: |
- recursive: true
args: [--frozen-lockfile, --strict-peer-dependencies]
通过这种详细的安装和配置说明,用户可以在任何环境中正确安装和配置 pnpm,并获得最佳的性能和使用体验。
1.2.3 使用 pnpm 创建 React 项目的深度实践
1.2.3.1 项目创建方法详解
方法一:命令行直接创建(适合快速原型)
# 基础创建命令
pnpm create vite my-react-app --template react-ts
# 带参数的高级创建
pnpm create vite my-react-app \
--template react-ts \
--force \ # 强制覆盖已存在目录
--verbose # 显示详细日志
# 实际执行过程详解:
# 步骤1:下载 vite 包和模板
# 步骤2:解压模板到目标目录
# 步骤3:初始化 package.json
# 步骤4:创建基础文件结构
# 步骤5:输出创建结果
方法二:交互式创建(推荐新手)
# 启动交互式创建
pnpm create vite
# 详细的交互流程:
# ? Project name: › my-react-app
# # 输入项目名称,支持:
# - 简单名称:my-app
# - 带路径:./projects/my-app
# - 绝对路径:/Users/name/projects/my-app
# - 相对路径:../my-app
# ? Select a framework: › - Use arrow-keys. Return to submit.
# ❯ React
# Vue
# Preact
# Lit
# Svelte
# Solid
# Qwik
# Others
# # 框架选择说明:
# - React:成熟的生态系统,适合大型项目
# - Vue:渐进式框架,学习曲线平缓
# - Preact:React 的轻量级替代品(3KB)
# - Svelte:编译时框架,性能优秀
# - Solid:类似 React,性能更好
# ? Select a variant: › - Use arrow-keys. Return to submit.
# ❯ TypeScript
# JavaScript
# JavaScript + SWC
# TypeScript + SWC
# # 变体选择说明:
# - TypeScript:类型安全,适合大型项目
# - JavaScript:简单快速,适合小型项目
# - + SWC:使用 SWC 替代 ESBuild,速度更快
方法三:使用自定义模板(高级用法)
# 使用官方模板仓库
pnpm create vite my-app --template react-ts
# 使用 GitHub 上的自定义模板
pnpm create vite my-app --template github:user/template
# 使用 GitLab 模板
pnpm create vite my-app --template gitlab:user/template
# 使用本地模板
pnpm create vite my-app --template ./my-template
# 使用 Bit 模板
pnpm create vite my-app --template bit:user.template
1.2.3.2 项目创建后的详细配置
项目目录结构解析:
my-react-app/
├── .gitignore # Git 忽略文件
├── index.html # HTML 入口文件
├── package.json # 项目配置文件
├── pnpm-lock.yaml # pnpm 锁定文件
├── public/ # 静态资源目录
│ ├── vite.svg # Vite 图标
│ └── favicon.ico # 网站图标
├── src/ # 源代码目录
│ ├── assets/ # 资源文件
│ │ └── react.svg # React 图标
│ ├── App.css # App 组件样式
│ ├── App.tsx # 主应用组件
│ ├── index.css # 全局样式
│ ├── main.tsx # 应用入口文件
│ └── vite-env.d.ts # Vite 环境类型声明
├── tsconfig.json # TypeScript 主配置
├── tsconfig.node.json # Node.js TypeScript 配置
└── vite.config.ts # Vite 配置文件
package.json 深度配置:
{
"name": "my-react-app",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.10.0",
"@vitejs/plugin-react": "^4.1.0",
"eslint": "^8.53.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.4",
"typescript": "^5.2.2",
"vite": "^4.5.0"
},
"pnpm": {
"overrides": {},
"patchedDependencies": {},
"peerDependencyRules": {
"ignoreMissing": []
}
}
}
1.2.3.3 依赖安装深度解析
pnpm install 的执行流程:
# 执行 pnpm install 时的内部流程:
# 阶段1:依赖解析
pnpm install
├── 读取 package.json 和 pnpm-lock.yaml
├── 解析依赖树
├── 检查对等依赖
└── 生成安装计划
# 阶段2:包获取
├── 检查全局存储
├── 下载缺失的包
├── 验证包完整性
└── 更新缓存
# 阶段3:链接创建
├── 创建硬链接
├── 创建符号链接
├── 生成 .pnpm 目录结构
└── 更新 node_modules
# 阶段4:后处理
├── 运行 postinstall 脚本
├── 生成 .modules.yaml
└── 清理临时文件
依赖安装选项详解:
# 基础安装选项
pnpm install
pnpm i # 简写
# 强制重新安装
pnpm install --force # 忽略缓存,重新下载
pnpm install --frozen-lockfile # 使用锁文件精确安装
# 开发模式安装
pnpm install --dev # 只安装 devDependencies
pnpm install --prod # 只安装 dependencies(默认)
# 忽略脚本执行
pnpm install --ignore-scripts # 跳过 postinstall 脚本
# 并发控制
pnpm install --network-concurrency 10 # 设置网络并发数
pnpm install --child-concurrency 5 # 设置子进程并发数
# 验证模式
pnpm install --verify # 验证包完整性
pnpm install --audit # 安全审计
1.2.3.4 开发服务器启动详解
启动命令详解:
# 基础启动
pnpm dev
# 带参数的启动
pnpm dev --port 3000 # 指定端口
pnpm dev --host # 监听所有网络接口
pnpm dev --open # 自动打开浏览器
pnpm dev --cors # 启用 CORS
# 组合参数
pnpm dev --port 3000 --host --open
# 开发服务器输出详解:
> my-react-app@0.0.0 dev
> vite
VITE v4.5.0 ready in 245 ms
➜ Local: http://localhost:5173/
➜ Network: http://192.168.1.100:5173/
➜ press h + enter to show help
# 输出说明:
# - Local: 本地访问地址
# - Network: 网络访问地址
# - h + enter: 显示帮助信息
热模块替换(HMR)工作原理:
// Vite HMR 的通信机制
// 1. 客户端连接 WebSocket
const ws = new WebSocket('ws://localhost:5173')
// 2. 服务端监听文件变化
const watcher = chokidar.watch(['src/**/*'])
// 3. 文件变化时的处理流程
watcher.on('change', (file) => {
// 重新编译
const result = build(file)
// 发送更新到客户端
ws.send(JSON.stringify({
type: 'update',
updates: [{
type: 'js-update',
timestamp: Date.now(),
path: file,
acceptedPath: file
}]
}))
})
// 4. 客户端接收更新
ws.onmessage = (event) => {
const msg = JSON.parse(event.data)
if (msg.type === 'update') {
// 执行热更新逻辑
import.meta.hot.accept('./App.tsx', (newModule) => {
// 替换模块
updateComponent(newModule.default)
})
}
}
1.2.3.5 项目验证和测试
功能验证清单:
# 1. 基础功能验证
curl http://localhost:5173/ # 检查页面响应
curl -I http://localhost:5173/ # 检查响应头
# 2. 热更新验证
# 修改 App.tsx 文件,观察浏览器自动更新
# 3. TypeScript 编译验证
pnpm build # 检查构建是否成功
# 4. 代码质量检查
pnpm lint # 运行 ESLint 检查
pnpm type-check # 运行 TypeScript 检查
# 5. 依赖完整性验证
pnpm why react # 检查依赖关系
pnpm outdated # 检查过期依赖
性能基准测试:
# 启动时间测试
time pnpm dev # 测试启动时间
# 构建时间测试
time pnpm build # 测试构建时间
# 安装时间测试
time pnpm install # 测试安装时间
# 内存使用测试
ps aux | grep vite # 查看进程内存占用
开发工具集成验证:
# 1. VS Code 集成验证
code . # 用 VS Code 打开项目
# 检查:IntelliSense、错误提示、代码格式化
# 2. Git 集成验证
git init && git add . && git commit -m "Initial commit"
# 检查:.gitignore 是否正确,文件编码是否一致
# 3. 浏览器开发工具验证
# 在 Chrome 中打开 http://localhost:5173
# 检查:React Developer Tools 是否正常工作
通过这种详细的项目创建和配置说明,用户可以深入理解每个步骤的作用和原理,确保项目能够正确搭建和运行。
1.2.4 pnpm 项目结构分析
生成的项目结构:
my-react-app/
├── .gitignore # Git 忽略文件
├── index.html # HTML 模板
├── package.json # 项目配置
├── pnpm-lock.yaml # pnpm 锁定文件(新增)
├── public/ # 静态资源
│ ├── vite.svg
│ └── favicon.ico
├── src/ # 源代码
│ ├── assets/
│ │ └── react.svg
│ ├── App.css
│ ├── App.tsx
│ ├── index.css
│ ├── main.tsx
│ └── vite-env.d.ts
├── tsconfig.json # TypeScript 配置
├── tsconfig.node.json # Node.js TypeScript 配置
└── vite.config.ts # Vite 配置
pnpm-lock.yaml 文件解析:
# pnpm-lock.yaml 示例片段
lockfileVersion: '6.0'
dependencies:
react:
specifier: ^18.2.0
version: 18.2.0
react-dom:
specifier: ^18.2.0
version: 18.2.0(react@18.2.0)
devDependencies:
'@types/react':
specifier: ^18.2.37
version: 18.2.37
'@vitejs/plugin-react':
specifier: ^4.1.0
version: 4.1.0(vite@4.5.0)
packages:
/react/18.2.0:
resolution: {integrity: sha512-/3IjMdb2...}
engines: {node: '>=0.10.0'}
/react-dom/18.2.0:
resolution: {integrity: sha512-6WAi...}
dependencies:
react: 18.2.0
peerDependencies:
react: ^18.2.0
与 npm 项目的主要区别:
- 锁定文件格式:pnpm 使用 YAML 格式的
pnpm-lock.yaml - 依赖安装方式:使用符号链接和硬链接
- 依赖访问控制:更严格的依赖隔离
- 存储机制:全局内容寻址存储
1.2.5 pnpm 常用命令详解
基础命令:
# 安装所有依赖
pnpm install
pnpm i # 简写
# 添加依赖
pnpm add package-name # 安装到 dependencies
pnpm add -D package-name # 安装到 devDependencies
pnpm add -O package-name # 安装到 optionalDependencies
pnpm add -g package-name # 全局安装
# 添加特定版本
pnpm add react@18.2.0
pnpm add react@^18.0.0
pnpm add react@latest
# 安装多个包
pnpm add react react-dom typescript
# 卸载依赖
pnpm remove package-name
pnpm rm package-name # 简写
pnpm un package-name # 简写
# 更新依赖
pnpm update # 更新所有依赖
pnpm update package-name # 更新指定包
pnpm update --latest # 更新到最新版本
pnpm update --interactive # 交互式更新
开发命令:
# 运行脚本
pnpm run script-name
pnpm script-name # 简写
# 常用脚本(参考 package.json)
pnpm dev # 启动开发服务器
pnpm build # 构建生产版本
pnpm preview # 预览构建结果
pnpm lint # 代码检查
pnpm test # 运行测试
# 传递参数
pnpm dev -- --port 3000 --open
信息查询命令:
# 列出所有依赖
pnpm list
pnpm ls # 简写
pnpm list --depth 0 # 只显示直接依赖
# 查看包信息
pnpm info package-name
pnpm view package-name version
# 检查过时的包
pnpm outdated
# 审计安全漏洞
pnpm audit
# 查看某个包为什么被安装
pnpm why package-name
清理和管理命令:
# 清理未引用的包
pnpm prune
# 清理缓存
pnpm store prune
# 查看存储路径
pnpm store path
# 重新安装所有依赖
pnpm install --force
pnpm install --frozen-lockfile # 使用锁定文件精确安装
1.2.6 pnpm 工作区(Workspace)配置
创建 Monorepo 项目:
# 创建工作区项目
mkdir my-monorepo
cd my-monorepo
# 创建 pnpm-workspace.yaml
echo "packages:\n - 'packages/*'" > pnpm-workspace.yaml
# 创建子项目
mkdir packages/web
mkdir packages/utils
mkdir packages/components
# 初始化根项目
pnpm init
# 在每个子项目中初始化
cd packages/web && pnpm init
cd ../utils && pnpm init
cd ../components && pnpm init
pnpm-workspace.yaml 配置:
# 基础配置
packages:
- 'packages/*' # packages 目录下的所有包
- 'apps/*' # apps 目录下的应用
- 'tools/*' # 工具包
- '!**/test/**' # 排除测试目录
# 高级配置示例
packages:
- 'packages/*' # 所有子包
- 'apps/*' # 应用
- 'packages/**/test' # 测试包
- '!**/__tests__/**' # 排除测试文件
catalogs: # 目录(可选)
react18: 'react@^18.2.0'
typescript: 'typescript@^5.0.0'
工作区依赖管理:
# 在根目录安装所有包的依赖
pnpm install
# 为特定包添加依赖
pnpm --filter web add react
# 为所有包添加开发依赖
pnpm -r add -D typescript
# 在特定包中运行脚本
pnpm --filter web dev
# 在所有包中运行脚本
pnpm -r build
# 运行依赖关系图
pnpm -r --filter-with-dependencies test
1.2.7 pnpm 高级配置
.npmrc 配置文件:
# .npmrc
# 基础配置
registry=https://registry.npmjs.org
store-dir=~/.pnpm-store
# 镜像配置(国内用户)
registry=https://registry.npmmirror.com
# 或者使用特定的镜像源
@babel:registry=https://registry.npmmirror.com
@types:registry=https://registry.npmmirror.com
# 代理配置
proxy=http://proxy.company.com:8080
https-proxy=http://proxy.company.com:8080
# 严格模式(推荐)
strict-peer-dependencies=true
auto-install-peers=true
# 缓存配置
cache-dir=~/.pnpm-cache
package.json 高级脚本:
{
"name": "my-react-app",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint . --ext ts,tsx --fix",
"type-check": "tsc --noEmit",
"clean": "rm -rf dist node_modules",
"reinstall": "pnpm clean && pnpm install",
"analyze": "pnpm build && npx vite-bundle-analyzer dist",
"pre-commit": "pnpm lint && pnpm type-check",
"release": "pnpm build && pnpm publish"
},
"pnpm": {
"overrides": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"patchedDependencies": {
"some-package@1.0.0": "patches/some-package@1.0.0.patch"
},
"peerDependencyRules": {
"ignoreMissing": ["@types/react"]
}
}
}
1.2.8 常见问题和解决方案
权限问题:
# Linux/macOS 上的权限问题
sudo chown -R $(whoami) ~/.pnpm-store
sudo chown -R $(whoami) ~/.pnpm-cache
# 或者修改存储目录
pnpm config set store-dir ~/.local/share/pnpm/store
依赖冲突解决:
# 检查依赖冲突
pnpm why package-name
# 强制重新解析依赖
pnpm install --force
# 使用覆盖解决冲突
pnpm add package-name --override
锁定文件问题:
# 删除锁定文件重新生成
rm pnpm-lock.yaml
pnpm install
# 检查锁定文件完整性
pnpm install --frozen-lockfile
网络问题:
# 配置国内镜像
pnpm config set registry https://registry.npmmirror.com
# 设置超时时间
pnpm config set fetch-timeout 600000
pnpm config set fetch-retry-mintimeout 20000
# 使用代理
pnpm config set proxy http://proxy.company.com:8080
1.2.9 pnpm 性能优化
缓存优化:
# 配置缓存目录
pnpm config set cache-dir ~/.pnpm-cache
# 预取常用包
pnpm install --prefer-frozen-lockfile
# 使用本地缓存离线安装
pnpm install --offline
并行安装优化:
# 设置并发数
pnpm config set max-sockets 10
# 预下载包
pnpm install --prefer-offline
# 跳过可选依赖
pnpm install --no-optional
构建优化:
# 使用共享依赖
pnpm add --shared package-name
# 节省构建时间
pnpm install --shamefully-hoist # (不推荐生产环境)
1.2.10 最佳实践建议
团队协作规范:
# 1. 提交 pnpm-lock.yaml 到版本控制
git add pnpm-lock.yaml
git commit -m "chore: add pnpm lock file"
# 2. 使用固定版本安装
pnpm install --frozen-lockfile
# 3. 定期更新依赖
pnpm update --interactive
# 4. 使用 .npmrc 统一配置
echo "registry=https://registry.npmjs.org" > .npmrc
echo "strict-peer-dependencies=true" >> .npmrc
CI/CD 集成:
# GitHub Actions 示例
name: CI
on: [push, pull_request]
jobs:
test:
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
- run: pnpm lint
- run: pnpm build
- run: pnpm test
Docker 集成:
# Dockerfile
FROM node:18-alpine
# 安装 pnpm
RUN npm install -g pnpm
# 设置工作目录
WORKDIR /app
# 复制配置文件
COPY package.json pnpm-lock.yaml ./
# 安装依赖
RUN pnpm install --frozen-lockfile --prod
# 复制源码
COPY . .
# 构建应用
RUN pnpm build
# 暴露端口
EXPOSE 3000
# 启动应用
CMD ["pnpm", "preview"]
通过使用 pnpm,你可以获得更快的安装速度、更小的磁盘占用,以及更可靠的依赖管理,特别适合大型项目和 Monorepo 架构。
1.3 pnpm + React + TS + Monorepo 架构详解
1.3.1 Monorepo 概念与优势
1.3.1.1 什么是 Monorepo
Monorepo(Monolithic Repository)是一种将多个相关项目存储在同一个代码仓库中的软件开发策略。它通过统一的代码管理、依赖管理和构建流程,提供了一套完整的解决方案。
Monorepo vs Multi-repo 对比:
# Multi-repo 传统架构
company/
├── frontend/ # 独立仓库
├── backend/ # 独立仓库
├── mobile-app/ # 独立仓库
├── shared-components/ # 独立仓库
└── docs/ # 独立仓库
# Monorepo 架构
company-monorepo/
├── packages/
│ ├── frontend/
│ ├── backend/
│ ├── mobile-app/
│ ├── shared-components/
│ └── docs/
├── apps/ # 应用层
├── tools/ # 工具层
└── libs/ # 库层
1.3.1.2 Monorepo 的核心优势
1. 统一依赖管理:
# 传统 Multi-repo 问题
frontend/package.json: "react": "^18.1.0"
backend/package.json: "react": "^18.2.0"
mobile-app/package.json: "react": "^17.0.0"
# 版本不一致导致的兼容性问题
# Monorepo 解决方案
pnpm-workspace.yaml
packages:
- 'packages/*'
# 根 package.json 统一版本控制
"react": "18.2.0"
# 所有包自动使用相同版本
2. 代码共享和重用:
// packages/shared-components/src/Button/Button.tsx
import React from 'react'
interface ButtonProps {
variant: 'primary' | 'secondary' | 'danger'
size?: 'small' | 'medium' | 'large'
children: React.ReactNode
onClick?: () => void
}
export const Button: React.FC<ButtonProps> = ({
variant = 'primary',
size = 'medium',
children,
onClick
}) => {
const baseClasses = 'rounded font-medium transition-colors'
const variantClasses = {
primary: 'bg-blue-500 text-white hover:bg-blue-600',
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
danger: 'bg-red-500 text-white hover:bg-red-600'
}
const sizeClasses = {
small: 'px-2 py-1 text-sm',
medium: 'px-4 py-2 text-base',
large: 'px-6 py-3 text-lg'
}
return (
<button
className={`${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]}`}
onClick={onClick}
>
{children}
</button>
)
}
// packages/frontend/src/App.tsx
import { Button } from '@company/shared-components'
import { Button } from '@company/ui-components'
function App() {
return (
<div>
<Button variant="primary" size="large">
Click Me
</Button>
</div>
)
}
3. 原子提交和依赖追踪:
# Git commit 原子性
git add .
git commit -m "feat: add user authentication flow
- Update shared-components: add LoginForm component
- Update frontend: integrate authentication
- Update backend: add auth endpoints
- Update mobile-app: add auth screens"
# 所有相关变更在一个提交中,便于追踪和回滚
4. 统一的 CI/CD 流程:
# .github/workflows/ci.yml
name: CI/CD Pipeline
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3
with:
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run linting
run: pnpm run lint
- name: Run type checking
run: pnpm run type-check
- name: Run tests
run: pnpm run test
- name: Build affected packages
run: pnpm run build --filter="...[origin/main]"
deploy:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
strategy:
matrix:
package: [frontend, backend, mobile-app]
steps:
- name: Deploy ${{ matrix.package }}
run: pnpm run deploy --filter ${{ matrix.package }}
1.3.2 pnpm Monorepo 架构设计
1.3.2.1 项目结构设计
推荐的 Monorepo 目录结构:
company-monorepo/
├── .github/ # GitHub 配置
│ ├── workflows/ # CI/CD 工作流
│ └── ISSUE_TEMPLATE/ # Issue 模板
├── .vscode/ # VS Code 配置
│ ├── settings.json # 编辑器配置
│ └── extensions.json # 推荐扩展
├── apps/ # 应用层(可部署的应用)
│ ├── web-app/ # Web 应用
│ ├── admin-panel/ # 管理面板
│ └── mobile-app/ # 移动应用
├── packages/ # 包层(共享库)
│ ├── ui-components/ # UI 组件库
│ ├── utils/ # 工具函数库
│ ├── types/ # TypeScript 类型定义
│ ├── hooks/ # 自定义 Hooks
│ ├── api-client/ # API 客户端
│ └── config/ # 配置管理
├── tools/ # 工具层
│ ├── build-scripts/ # 构建脚本
│ ├── eslint-config/ # ESLint 配置
│ ├── tsconfig/ # TypeScript 配置
│ └── testing/ # 测试工具
├── docs/ # 文档
│ ├── api/ # API 文档
│ ├── components/ # 组件文档
│ └── guides/ # 使用指南
├── scripts/ # 项目脚本
│ ├── build.sh # 构建脚本
│ ├── test.sh # 测试脚本
│ └── deploy.sh # 部署脚本
├── pnpm-workspace.yaml # pnpm 工作区配置
├── package.json # 根 package.json
├── pnpm-lock.yaml # 全局锁文件
├── .gitignore # Git 忽略文件
├── .npmrc # npm 配置
└── README.md # 项目说明
详细的包结构示例:
packages/ui-components/
├── package.json # 包配置
├── tsconfig.json # TypeScript 配置
├── src/
│ ├── index.ts # 入口文件
│ ├── components/ # 组件目录
│ │ ├── Button/
│ │ │ ├── Button.tsx
│ │ │ ├── Button.test.tsx
│ │ │ ├── Button.stories.tsx
│ │ │ └── index.ts
│ │ ├── Modal/
│ │ └── Form/
│ ├── hooks/ # 共享 Hooks
│ ├── utils/ # 工具函数
│ └── types/ # 类型定义
├── docs/ # 文档
├── stories/ # Storybook 故事
└── tests/ # 测试文件
1.3.2.2 pnpm 工作区配置
pnpm-workspace.yaml 深度配置:
# 基础工作区配置
packages:
# 应用包配置
- 'apps/*' # apps 目录下的所有包
- 'packages/*' # packages 目录下的所有包
- 'tools/*' # tools 目录下的所有包
# 排除特定包
- '!**/test/**' # 排除测试目录
- '!**/examples/**' # 排除示例目录
- '!**/dist/**' # 排除构建目录
# 高级配置
catalog: # 依赖目录(版本管理)
react18: 'react@18.2.0' # React 18 版本
typescript: 'typescript@5.2.2'
vite: 'vite@4.5.0'
# 开发依赖目录
'@types/react': '@types/react@18.2.37'
'@vitejs/plugin-react': '@vitejs/plugin-react@4.1.0'
# 共享依赖配置
shared-workspace-lockfile: true # 共享工作区锁文件
link-workspace-packages: true # 链接工作区包
prefer-workspace-packages: true # 优先使用工作区包
根 package.json 配置:
{
"name": "@company/monorepo",
"private": true,
"type": "module",
"scripts": {
// 开发脚本
"dev": "pnpm --filter "./apps/*" dev",
"dev:web": "pnpm --filter web-app dev",
"dev:admin": "pnpm --filter admin-panel dev",
// 构建脚本
"build": "pnpm --filter "./packages/*" build && pnpm --filter "./apps/*" build",
"build:apps": "pnpm --filter "./apps/*" build",
"build:packages": "pnpm --filter "./packages/*" build",
// 测试脚本
"test": "pnpm --filter "./packages/*" --filter "./apps/*" test",
"test:ci": "pnpm run test -- --coverage --ci",
// 代码质量
"lint": "pnpm --filter "./packages/*" --filter "./apps/*" lint",
"lint:fix": "pnpm run lint -- --fix",
"type-check": "pnpm run type-check --filter "./packages/*" --filter "./apps/*"",
// 依赖管理
"update": "pnpm update --interactive",
"clean": "pnpm --filter "./packages/*" --filter "./apps/*" clean",
"reset": "pnpm clean && rm -rf node_modules pnpm-lock.yaml && pnpm install",
// 部署脚本
"deploy": "pnpm run build && pnpm run deploy:apps",
"deploy:apps": "pnpm --filter "./apps/*" deploy",
// 工具脚本
"changeset": "changeset",
"version-packages": "changeset version",
"release": "pnpm run build && changeset publish"
},
"devDependencies": {
// 构建工具
"@changesets/cli": "^2.26.2",
"@changesets/changelog-github": "^0.4.8",
"typescript": "^5.2.2",
"vite": "^4.5.0",
// 代码质量工具
"eslint": "^8.53.0",
"prettier": "^3.0.3",
"husky": "^8.0.3",
"lint-staged": "^15.1.0",
// 测试工具
"vitest": "^0.34.6",
"@testing-library/react": "^13.4.0",
// 配置包
"@company/eslint-config": "workspace:*",
"@company/tsconfig": "workspace:*",
"@company/prettier-config": "workspace:*"
},
"engines": {
"node": ">=18.0.0",
"pnpm": ">=8.0.0"
},
"packageManager": "pnpm@8.10.5"
}
1.3.2.3 TypeScript 项目引用配置
根 tsconfig.json 配置:
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
// 路径映射
"baseUrl": ".",
"paths": {
"@company/ui-components": ["packages/ui-components/src"],
"@company/utils": ["packages/utils/src"],
"@company/types": ["packages/types/src"],
"@company/hooks": ["packages/hooks/src"],
"@company/api-client": ["packages/api-client/src"],
"@company/config": ["packages/config/src"],
"@company/eslint-config": ["tools/eslint-config"],
"@company/tsconfig": ["tools/tsconfig"]
}
},
"references": [
{ "path": "./packages/ui-components" },
{ "path": "./packages/utils" },
{ "path": "./packages/types" },
{ "path": "./packages/hooks" },
{ "path": "./packages/api-client" },
{ "path": "./packages/config" },
{ "path": "./tools/eslint-config" },
{ "path": "./tools/tsconfig" },
{ "path": "./apps/web-app" },
{ "path": "./apps/admin-panel" }
],
"include": [],
"exclude": ["node_modules", "dist", "build"]
}
包级别的 tsconfig.json 配置:
// packages/ui-components/tsconfig.json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": true, // 启用项目引用
"outDir": "./dist", // 输出目录
"rootDir": "./src", // 源码目录
"declaration": true, // 生成声明文件
"declarationMap": true, // 生成声明映射
"sourceMap": true // 生成源码映射
},
"include": [
"src/**/*"
],
"exclude": [
"dist",
"node_modules",
"**/*.test.ts",
"**/*.test.tsx",
"**/*.stories.tsx"
]
}
1.3.3 实战:构建完整的 Monorepo 项目
1.3.3.1 项目初始化步骤
步骤1:创建项目根目录:
# 创建项目目录
mkdir company-monorepo
cd company-monorepo
# 初始化 Git 仓库
git init
git config user.name "Your Name"
git config user.email "your.email@company.com"
# 创建基础目录结构
mkdir -p apps/{web-app,admin-panel}
mkdir -p packages/{ui-components,utils,types,hooks,api-client,config}
mkdir -p tools/{build-scripts,eslint-config,tsconfig,testing}
mkdir -p docs/{api,components,guides}
mkdir scripts
步骤2:配置 pnpm 工作区:
# 创建 pnpm-workspace.yaml
cat > pnpm-workspace.yaml << 'EOF'
packages:
- 'apps/*'
- 'packages/*'
- 'tools/*'
- '!**/test/**'
- '!**/examples/**'
catalog:
react18: 'react@18.2.0'
typescript: 'typescript@5.2.2'
vite: 'vite@4.5.0'
shared-workspace-lockfile: true
link-workspace-packages: true
prefer-workspace-packages: true
EOF
# 初始化根 package.json
pnpm init
步骤3:创建基础包结构:
# 创建类型包
cd packages/types
cat > package.json << 'EOF'
{
"name": "@company/types",
"version": "1.0.0",
"private": true,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"clean": "rm -rf dist"
},
"devDependencies": {
"typescript": "catalog:typescript"
}
}
EOF
mkdir src
cat > src/index.ts << 'EOF'
// 用户相关类型
export interface User {
id: string
name: string
email: string
avatar?: string
role: 'admin' | 'user' | 'guest'
createdAt: Date
updatedAt: Date
}
// API 响应类型
export interface ApiResponse<T = any> {
success: boolean
data: T
message?: string
code?: number
}
// 分页类型
export interface Pagination {
page: number
limit: number
total: number
totalPages: number
}
// 表单类型
export interface FormField {
name: string
label: string
type: 'text' | 'email' | 'password' | 'select' | 'textarea'
required?: boolean
placeholder?: string
options?: Array<{ label: string; value: string }>
}
EOF
步骤4:创建工具库:
# 工具函数包
cd ../utils
cat > package.json << 'EOF'
{
"name": "@company/utils",
"version": "1.0.0",
"private": true,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"clean": "rm -rf dist"
},
"dependencies": {
"@company/types": "workspace:*"
},
"devDependencies": {
"typescript": "catalog:typescript"
}
}
EOF
mkdir src
cat > src/index.ts << 'EOF'
import type { User, ApiResponse } from '@company/types'
// 用户工具函数
export const getUserInitials = (user: User): string => {
return user.name
.split(' ')
.map(name => name[0])
.join('')
.toUpperCase()
}
export const formatDate = (date: Date): string => {
return new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(date)
}
// API 工具函数
export const createApiResponse = <T>(
data: T,
message?: string
): ApiResponse<T> => ({
success: true,
data,
message
})
// 本地存储工具
export const storage = {
get: <T>(key: string): T | null => {
const item = localStorage.getItem(key)
return item ? JSON.parse(item) : null
},
set: <T>(key: string, value: T): void => {
localStorage.setItem(key, JSON.stringify(value))
},
remove: (key: string): void => {
localStorage.removeItem(key)
}
}
// 类名工具
export const cn = (...classes: (string | undefined | null | false)[]): string => {
return classes.filter(Boolean).join(' ')
}
EOF
1.3.3.2 UI 组件库构建
创建 UI 组件库:
cd ../ui-components
cat > package.json << 'EOF'
{
"name": "@company/ui-components",
"version": "1.0.0",
"private": true,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc && vite build",
"dev": "vite build --watch",
"clean": "rm -rf dist",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"dependencies": {
"react": "catalog:react18",
"react-dom": "catalog:react18",
"@company/types": "workspace:*",
"@company/utils": "workspace:*"
},
"devDependencies": {
"@types/react": "@types/react@18.2.37",
"@types/react-dom": "@types/react-dom@18.2.15",
"@vitejs/plugin-react": "@vitejs/plugin-react@4.1.0",
"vite": "catalog:vite",
"typescript": "catalog:typescript",
"@storybook/addon-essentials": "^7.5.3",
"@storybook/react": "^7.5.3",
"@storybook/react-vite": "^7.5.3",
"storybook": "^7.5.3"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
}
EOF
# 创建 Vite 配置
cat > vite.config.ts << 'EOF'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'
export default defineConfig({
plugins: [react()],
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'UIComponents',
fileName: (format) => `ui-components.${format}.js`
},
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM'
}
}
}
}
})
EOF
# 创建核心组件
mkdir -p src/components/{Button,Modal,Input}
创建 Button 组件:
cat > src/components/Button/Button.tsx << 'EOF'
import React from 'react'
import { cn } from '@company/utils'
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'danger' | 'ghost'
size?: 'small' | 'medium' | 'large'
loading?: boolean
icon?: React.ReactNode
children: React.ReactNode
}
export const Button: React.FC<ButtonProps> = ({
variant = 'primary',
size = 'medium',
loading = false,
icon,
children,
className,
disabled,
...props
}) => {
const baseClasses = 'inline-flex items-center justify-center font-medium rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2'
const variantClasses = {
primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500',
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-500',
danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500',
ghost: 'text-gray-700 bg-transparent hover:bg-gray-100 focus:ring-gray-500'
}
const sizeClasses = {
small: 'px-3 py-1.5 text-sm',
medium: 'px-4 py-2 text-base',
large: 'px-6 py-3 text-lg'
}
return (
<button
className={cn(
baseClasses,
variantClasses[variant],
sizeClasses[size],
(disabled || loading) && 'opacity-50 cursor-not-allowed',
className
)}
disabled={disabled || loading}
{...props}
>
{loading && (
<svg className="animate-spin -ml-1 mr-2 h-4 w-4" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
)}
{icon && !loading && <span className="mr-2">{icon}</span>}
{children}
</button>
)
}
EOF
cat > src/components/Button/index.ts << 'EOF'
export { Button } from './Button'
export type { ButtonProps } from './Button'
EOF
创建主入口文件:
cat > src/index.ts << 'EOF'
// 组件导出
export { Button } from './components/Button'
export type { ButtonProps } from './components/Button'
// 样式导出
import './styles/globals.css'
EOF
mkdir -p src/styles
cat > src/styles/globals.css << 'EOF'
/* 全局样式重置 */
* {
box-sizing: border-box;
}
/* 组件基础样式 */
.btn {
transition: all 0.2s ease-in-out;
}
.btn:focus {
outline: 2px solid transparent;
outline-offset: 2px;
}
/* 动画 */
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.animate-spin {
animation: spin 1s linear infinite;
}
EOF
1.3.3.3 Web 应用创建
创建 Web 应用:
cd ../../apps/web-app
cat > package.json << 'EOF'
{
"name": "@company/web-app",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "vitest",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
},
"dependencies": {
"react": "catalog:react18",
"react-dom": "catalog:react18",
"react-router-dom": "^6.17.0",
"@company/ui-components": "workspace:*",
"@company/utils": "workspace:*",
"@company/types": "workspace:*",
"@company/hooks": "workspace:*"
},
"devDependencies": {
"@types/react": "@types/react@18.2.37",
"@types/react-dom": "@types/react-dom@18.2.15",
"@vitejs/plugin-react": "@vitejs/plugin-react@4.1.0",
"vite": "catalog:vite",
"typescript": "catalog:typescript",
"eslint": "^8.53.0",
"@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.10.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.4"
}
}
EOF
# 创建 Vite 配置
cat > vite.config.ts << 'EOF'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@components': resolve(__dirname, '../packages/ui-components/src'),
'@utils': resolve(__dirname, '../packages/utils/src'),
'@types': resolve(__dirname, '../packages/types/src'),
'@hooks': resolve(__dirname, '../packages/hooks/src')
}
},
server: {
port: 3000,
open: true
},
build: {
outDir: 'dist',
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
router: ['react-router-dom']
}
}
}
}
})
EOF
创建应用主文件:
mkdir -p src/{pages,components,hooks}
cat > src/main.tsx << 'EOF'
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
)
EOF
cat > src/App.tsx << 'EOF'
import React from 'react'
import { Routes, Route } from 'react-router-dom'
import { Button } from '@company/ui-components'
import HomePage from './pages/HomePage'
import AboutPage from './pages/AboutPage'
import './App.css'
function App() {
return (
<div className="App">
<header className="App-header">
<h1>Company Web App</h1>
<nav className="navigation">
<Button variant="ghost">Home</Button>
<Button variant="ghost">About</Button>
</nav>
</header>
<main className="main-content">
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
</Routes>
</main>
</div>
)
}
export default App
EOF
cat > src/pages/HomePage.tsx << 'EOF'
import React from 'react'
import { Button } from '@company/ui-components'
import { formatDate } from '@company/utils'
import { User } from '@company/types'
const HomePage: React.FC = () => {
const user: User = {
id: '1',
name: 'John Doe',
email: 'john.doe@company.com',
role: 'user',
createdAt: new Date(),
updatedAt: new Date()
}
const handleClick = () => {
console.log('Button clicked!')
}
return (
<div className="home-page">
<h2>Welcome to Home Page</h2>
<p>User: {user.name}</p>
<p>Joined: {formatDate(user.createdAt)}</p>
<div className="button-demo">
<Button variant="primary" onClick={handleClick}>
Primary Button
</Button>
<Button variant="secondary" onClick={handleClick}>
Secondary Button
</Button>
<Button variant="danger" loading onClick={handleClick}>
Loading Button
</Button>
</div>
</div>
)
}
export default HomePage
EOF
1.3.4 Monorepo 高级特性
1.3.4.1 依赖管理与版本控制
Changesets 版本管理:
# 安装 Changesets
pnpm add -D -w @changesets/cli @changesets/changelog-github
# 初始化 Changesets
pnpm changeset init
# 创建变更集
pnpm changeset
# 版本升级
pnpm run version-packages
# 发布
pnpm run release
.changeset/config.json 配置:
{
"$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json",
"changelog": "@changesets/changelog-github",
"commit": false,
"fixed": [],
"linked": [
["@company/ui-components", "@company/utils"]
],
"access": "private",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
1.3.4.2 构建优化策略
选择性构建:
# 只构建变更的包
pnpm build --filter="...[origin/main]"
# 构建依赖的包
pnpm build --filter="@company/ui-components"
# 构建所有包(依赖顺序)
pnpm -r build
# 并行构建
pnpm -r build --parallel
缓存策略:
// 根 package.json
{
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev --parallel",
"test": "turbo run test"
},
"devDependencies": {
"turbo": "^1.10.14"
}
}
turbo.json 配置:
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"]
},
"lint": {
"outputs": []
},
"type-check": {
"dependsOn": ["^build"],
"outputs": []
}
}
}
1.3.4.3 CI/CD 和部署
GitHub Actions 工作流:
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
NODE_VERSION: '18'
PNPM_VERSION: '8'
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
apps: ${{ steps.changes.outputs.apps }}
packages: ${{ steps.changes.outputs.packages }}
should-release: ${{ steps.changes.outputs.should-release }}
steps:
- uses: actions/checkout@v3
- uses: dorny/paths-filter@v2
id: changes
with:
filters: |
apps:
- 'apps/**'
packages:
- 'packages/**'
should-release:
- '.changeset/**'
quality-check:
if: needs.detect-changes.outputs.apps == 'true' || needs.detect-changes.outputs.packages == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
with:
version: ${{ env.PNPM_VERSION }}
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Type check
run: pnpm run type-check
- name: Lint
run: pnpm run lint
- name: Test
run: pnpm run test --coverage
build:
needs: [detect-changes, quality-check]
if: needs.detect-changes.outputs.apps == 'true' || needs.detect-changes.outputs.packages == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
with:
version: ${{ env.PNPM_VERSION }}
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm run build
deploy:
needs: [detect-changes, build]
if: needs.detect-changes.outputs.apps == 'true' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
strategy:
matrix:
app: [web-app, admin-panel]
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
with:
version: ${{ env.PNPM_VERSION }}
- uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm run build
- name: Deploy ${{ matrix.app }}
run: |
echo "Deploying ${{ matrix.app }} to production..."
# 添加实际部署逻辑
1.3.4.4 监控和调试
性能监控:
# 构建时间分析
time pnpm run build
# 包大小分析
pnpm add -D -w bundle-analyzer
pnpm run build -- --analyze
# 依赖分析
pnpm why @company/ui-components
pnpm ls --depth 0
调试工具:
# 详细日志
pnpm install --verbose
pnpm run dev --debug
# 性能分析
pnpm perf # pnpm 性能分析
通过这种完整的 Monorepo 架构设计和实现,你可以构建一个高效、可维护、可扩展的前端工程体系,支持团队协作和大型项目的开发需求。
1.4 Yarn + React + TS 项目开发详解
1.4.1 Yarn 深度解析与核心特性
1.4.1.1 Yarn 发展历程与版本对比
Yarn 的演进:
# Yarn 版本发展时间线
Yarn 1.x (Classic) # 2016年10月发布,解决 npm v3 的问题
Yarn 2.x (Berry) # 2020年1月发布,架构重构,插件化
Yarn 3.x # 2021年发布,性能优化,更多特性
Yarn 4.x # 2023年发布,最新稳定版本
版本对比表:
| 特性 | Yarn 1.x | Yarn 2.x+ | npm | pnpm |
|---|---|---|---|---|
| 安装速度 | 中等 | 快 | 慢 | 最快 |
| 磁盘占用 | 高 | 中等 | 高 | 最低 |
| 插件系统 | 无 | 有 | 无 | 有限 |
| 零安装 | 不支持 | 支持 | 不支持 | 部分支持 |
| 工作区 | 基础 | 高级 | 无 | 原生 |
| 确定性 | 中等 | 高 | 中等 | 最高 |
| PnP 支持 | 不支持 | 原生 | 不支持 | 不支持 |
1.4.1.2 Yarn Berry 核心架构
Plug'n'Play (PnP) 机制:
# 传统 node_modules 结构(npm/Yarn 1)
node_modules/
├── react/
│ ├── index.js
│ ├── package.json
│ └── node_modules/
│ └── scheduler/
└── react-dom/
├── index.js
└── package.json
# PnP 结构(Yarn 2+)
.pnp.cjs # PnP 映射文件
.pnp.loader.mjs # PnP 加载器
.yarn/cache/ # 包缓存
│ └── react-npm-18.2.0-....zip
.yarn/unplugged/ # 解压的原生模块
.yarnrc.yml # Yarn 配置文件
# 没有 node_modules 目录
PnP 工作原理:
// .pnp.cjs 映射文件(简化版)
{
"name": "my-react-app",
"version": "1.0.0",
"dependencies": [
{
"name": "react",
"range": "^18.2.0",
"packageLocation": "./.yarn/cache/react-npm-18.2.0-....zip/node_modules/react/"
},
{
"name": "react-dom",
"range": "^18.2.0",
"packageLocation": "./.yarn/cache/react-dom-npm-18.2.0-....zip/node_modules/react-dom/",
"dependencies": ["react"]
}
]
}
// 运行时解析流程
const require = createRequire(import.meta.url)
// 1. 查找 .pnp.cjs
// 2. 解析模块路径
// 3. 从缓存中加载
// 4. 返回模块导出
1.4.1.3 Yarn 的核心优势
1. 确定性保证:
# Yarn 的确定性机制
yarn install # 锁定版本,完全一致的安装
yarn install --immutable # 不可变模式,任何修改都会报错
yarn install --check-cache # 验证缓存完整性
# 对比 npm/pnpm 的不确定性
npm install # 可能依赖缓存,结果不一致
pnpm install # 虽然有锁文件,但仍可能受环境影响
2. 零安装(Zero-Install)特性:
# 零安装工作流程
git clone project # 克隆仓库
cd project
yarn start # 直接启动,无需 install
# 优点:
# 1. 即时启动开发环境
# 2. 减少网络依赖
# 3. 保证团队环境一致
# 4. CI/CD 速度提升
# .gitignore 配置
!.yarn/cache
!.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
node_modules/
3. 插件化架构:
# 内置插件
@yarnpkg/plugin-version # 版本管理
@yarnpkg/plugin-nm # node_modules 兼容
@yarnpkg/plugin-pack # 打包工具
@yarnpkg/plugin-workspaces # 工作区支持
@yarnpkg/plugin-typescript # TypeScript 集成
@yarnpkg/plugin-essentials # 核心插件
# 社区插件
@yarnpkg/plugin-constraints # 依赖约束
@yarnpkg/plugin-dedupe # 依赖去重
@yarnpkg/plugin-interactive-tools # 交互式工具
@yarnpkg/plugin-stage # 分阶段构建
1.4.2 Yarn 安装与环境配置
1.4.2.1 多平台安装方案
方法一:官方安装脚本(推荐):
# 安装 Yarn Berry(最新版本)
corepack enable # Node.js 16+ 内置的包管理器
corepack prepare yarn@stable --activate
# 安装特定版本
corepack prepare yarn@3.6.4 --activate
# 验证安装
yarn --version
yarn --help
方法二:npm 安装:
# 全局安装
npm install -g yarn
# 安装 Berry 版本
npm install -g yarn@berry
# 设置为默认版本
yarn set version berry
yarn set version latest
yarn set version 3.6.4
方法三:包管理器安装:
# Homebrew (macOS)
brew install yarn
# Chocolatey (Windows)
choco install yarn
# Scoop (Windows)
scoop install yarn
# APT (Ubuntu/Debian)
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt update && sudo apt install yarn
1.4.2.2 Yarn 配置详解
基础配置命令:
# 全局配置
yarn config set --home # 配置家目录
yarn config list # 查看配置
yarn config get <key> # 获取配置
yarn config set <key> <value> # 设置配置
yarn config delete <key> # 删除配置
# 项目配置
yarn config set <key> <value> # 在项目目录下执行
yarn config set -g <key> <value> # 全局配置
详细配置选项:
# 缓存配置
yarn config set cacheFolder ~/.yarn/cache
# 作用:设置全局缓存目录
# 默认:~/.yarn/cache
# 优化:SSD 用户可设置到高速磁盘
# 镜像配置
yarn config set npmRegistryServer https://registry.npmjs.org
yarn config set npmRegistryServer https://registry.npmmirror.com # 淘宝镜像
# PnP 配置
yarn config set nodeLinker pnp # 启用 PnP(默认)
yarn config set nodeLinker node-modules # 使用 node_modules 模式
yarn config set pnpEnableEsmLoader true # 启用 ESM 加载器
# 零安装配置
yarn config set enableGlobalCache true # 启用全局缓存
yarn config defaultMirrorMode mirroring # 镜像模式
# 安全配置
yarn config set enableStrictSsl true # 启用严格 SSL
yarn config set enableNetworkConcurrency true # 启用网络并发
# 性能配置
yarn config set networkConcurrency 8 # 网络并发数
yarn config set childProcessConcurrency 8 # 子进程并发数
.yarnrc.yml 配置文件:
# .yarnrc.yml 示例
yarnPath: .yarn/releases/yarn-3.6.4.cjs
nodeLinker: pnp # 启用 PnP
enableGlobalCache: true # 启用全局缓存
cacheFolder: ~/.yarn/cache # 缓存目录
# 注册表配置
npmRegistryServer: "https://registry.npmjs.org"
# 安全配置
enableStrictSsl: true
enableNetworkConcurrency: true
# 性能配置
networkConcurrency: 8
childProcessConcurrency: 8
# 零安装配置
enableTelemetry: false # 禁用遥测
enableImmutableCache: true # 不可变缓存
enableImmutableInstalls: true # 不可变安装
# 插件配置
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
- path: .yarn/plugins/@yarnpkg/plugin-constraints.cjs
spec: "@yarnpkg/plugin-constraints"
# 工作区配置
workspaces:
- 'packages/*'
- 'apps/*'
1.4.2.3 IDE 和开发工具集成
VS Code 集成:
// .vscode/settings.json
{
// TypeScript 支持
"typescript.preferences.includePackageJsonAutoImports": "on",
"typescript.suggest.autoImports": true,
"typescript.enablePromptUseWorkspaceTsdk": true,
// Yarn 支持
"npm.packageManager": "yarn",
"typescript.tsdk": ".yarn/sdks/typescript/lib",
"eslint.packageManager": "yarn",
"prettier.packageManager": "yarn",
// PnP 支持
"typescript.enableTsPlugin": true,
"typescript.tsdk": "./.yarn/sdks/typescript/lib",
// 文件关联
"files.associations": {
".pnp.cjs": "javascript",
".yarnrc.yml": "yaml"
},
// 扩展推荐
"extensions.recommendations": [
"arcanis.vscode-zipfs",
"yarnpkg.berry"
]
}
Git 配置:
# .gitignore
# Yarn 零安装
!.yarn/cache
!.yarn/unplugged
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
.yarn/build-state.yml
.yarn/install-state.gz
.yarn/progress
# 传统文件
node_modules/
.yarn/cache/.tmp
.yarn/unplugged/.tmp
# 构建输出
dist/
build/
out/
# 日志文件
*.log
yarn-debug.log*
yarn-error.log*
1.4.3 使用 Yarn 创建 React 项目
1.4.3.1 项目创建方法详解
方法一:使用 create-react-app:
# 传统 CRA 创建
npx create-react-app my-react-app --template typescript
# 使用 Yarn Berry
yarn create react-app my-react-app --template typescript
# 创建后切换到 Berry
cd my-react-app
yarn set version berry
方法二:使用 Vite(推荐):
# 使用 Yarn Berry 创建 Vite 项目
yarn create vite my-react-app --template react-ts
# 详细交互流程:
# ✔ Project name: my-react-app
# ✔ Select a framework: › React
# ✔ Select a variant: › TypeScript
# 进入项目
cd my-react-app
# 安装依赖
yarn install
# 启动开发服务器
yarn dev
方法三:使用 Yarn 工作区创建:
# 创建 Monorepo 结构
mkdir my-react-monorepo
cd my-react-monorepo
# 初始化 Berry
yarn init -2
# 配置 .yarnrc.yml
cat > .yarnrc.yml << 'EOF'
yarnPath: .yarn/releases/yarn-3.6.4.cjs
nodeLinker: pnp
enableGlobalCache: true
EOF
# 配置工作区
yarn workspace create web-app
yarn workspace create shared-components
1.4.3.2 项目初始化配置
package.json 配置:
{
"name": "@company/my-react-app",
"version": "1.0.0",
"private": true,
"type": "module",
"packageManager": "yarn@3.6.4",
"scripts": {
// 开发脚本
"dev": "vite",
"start": "vite",
// 构建脚本
"build": "tsc && vite build",
"build:analyze": "vite build && npx vite-bundle-analyzer dist",
// 测试脚本
"test": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage",
// 代码质量
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint src --ext ts,tsx --fix",
"type-check": "tsc --noEmit",
"format": "prettier --write .",
// 依赖管理
"upgrade": "yarn upgrade-interactive",
"clean": "rimraf dist .yarn/cache .yarn/unplugged",
"reset": "yarn clean && rm -rf node_modules .pnp.cjs .pnp.loader.mjs && yarn install",
// 零安装相关
"postinstall": "husky install",
"prepare": "yarn build"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.17.0"
},
"devDependencies": {
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@vitejs/plugin-react": "^4.1.0",
"typescript": "^5.2.2",
"vite": "^4.5.0",
"eslint": "^8.53.0",
"prettier": "^3.0.3",
"husky": "^8.0.3",
"lint-staged": "^15.1.0",
"vitest": "^0.34.6"
},
"engines": {
"node": ">=18.0.0",
"yarn": ">=3.0.0"
}
}
TypeScript 配置:
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@/components/*": ["src/components/*"],
"@/hooks/*": ["src/hooks/*"],
"@/utils/*": ["src/utils/*"],
"@/types/*": ["src/types/*"]
},
// Yarn PnP 支持
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
},
"include": ["src"],
"exclude": ["node_modules", "dist", ".yarn"],
"references": [
{ "path": "./tsconfig.node.json" }
]
}
1.4.3.3 依赖管理深度实践
Yarn 依赖命令详解:
# 基础依赖管理
yarn add react # 添加到 dependencies
yarn add react --dev # 添加到 devDependencies
yarn add react --peer # 添加到 peerDependencies
yarn add react --optional # 添加到 optionalDependencies
# 版本控制
yarn add react@18.2.0 # 指定版本
yarn add react@^18.2.0 # 兼容版本
yarn add react@latest # 最新版本
yarn add react@beta # Beta 版本
yarn add react@next # Next 版本
# 批量操作
yarn add react react-dom typescript
yarn add @types/react @types/react-dom --dev
# 卸载依赖
yarn remove react
yarn remove react react-dom typescript
# 更新依赖
yarn upgrade # 更新所有依赖
yarn upgrade react # 更新指定依赖
yarn upgrade-interactive # 交互式更新
yarn upgrade --latest # 更新到最新版本
高级依赖管理:
# 依赖解析分析
yarn why react # 查看依赖原因
yarn info react # 查看包信息
yarn list # 列出所有依赖
yarn list --depth 0 # 只显示直接依赖
# 依赖约束
yarn add constraints --interactive
# 在 .yarnrc.yml 中配置:
# constraints:
# - "react@^18.0.0"
# - "typescript@^5.0.0"
# 依赖去重
yarn dedupe # 自动去重
yarn dedupe --strategy highest # 使用最高版本
yarn dedupe --strategy lowest # 使用最低版本
# 补丁管理
yarn patch react # 创建补丁
yarn patch react --commit # 提交补丁
yarn patch-commit ./patches/react.patch
零安装部署:
# 零安装工作流程
git clone <project>
cd <project>
yarn start # 无需 yarn install
# Docker 零安装
FROM node:18-alpine
# 启用 Corepack
RUN corepack enable
# 设置工作目录
WORKDIR /app
# 复制项目文件
COPY . .
# 注意:无需 yarn install
# yarn 会自动处理依赖
# 运行应用
CMD ["yarn", "start"]
1.4.4 Yarn 工作区(Workspaces)实践
1.4.4.1 工作区架构设计
工作区目录结构:
react-monorepo/
├── .yarn/
│ ├── releases/yarn-3.6.4.cjs # Yarn Berry 二进制
│ ├── cache/ # 包缓存
│ ├── sdks/ # 开发工具 SDK
│ └── plugins/ # 插件目录
├── packages/ # 包目录
│ ├── ui-components/ # UI 组件库
│ ├── utils/ # 工具函数库
│ ├── hooks/ # 自定义 Hooks
│ └── types/ # 类型定义
├── apps/ # 应用目录
│ ├── web-app/ # Web 应用
│ ├── admin-panel/ # 管理面板
│ └── mobile-app/ # 移动应用
├── tools/ # 工具目录
│ ├── eslint-config/ # ESLint 配置
│ ├── tsconfig/ # TypeScript 配置
│ └── build-scripts/ # 构建脚本
├── .yarnrc.yml # Yarn 配置
├── package.json # 根配置
└── workspaces.yml # 工作区配置
工作区配置:
# .yarnrc.yml
yarnPath: .yarn/releases/yarn-3.6.4.cjs
nodeLinker: pnp
enableGlobalCache: true
# 工作区定义
workspaces:
- 'apps/*'
- 'packages/*'
- 'tools/*'
# 或者使用 package.json 方式
# 参考下面的根 package.json
根 package.json 配置:
{
"name": "@company/react-monorepo",
"version": "1.0.0",
"private": true,
"type": "module",
"packageManager": "yarn@3.6.4",
"workspaces": [
"apps/*",
"packages/*",
"tools/*"
],
"scripts": {
// 开发脚本
"dev": "yarn workspaces foreach run dev",
"dev:web": "yarn workspace @company/web-app dev",
"dev:admin": "yarn workspace @company/admin-panel dev",
// 构建脚本
"build": "yarn workspaces foreach -pt run build",
"build:apps": "yarn workspaces foreach -Apt run build",
"build:packages": "yarn workspaces foreach -pt run build",
// 测试脚本
"test": "yarn workspaces foreach run test",
"test:coverage": "yarn workspaces foreach run test:coverage",
// 代码质量
"lint": "yarn workspaces foreach run lint",
"lint:fix": "yarn workspaces foreach run lint:fix",
"type-check": "yarn workspaces foreach run type-check",
// 依赖管理
"clean": "yarn workspaces foreach run clean",
"reset": "yarn clean && rm -rf .yarn/cache .yarn/unplugged && yarn install",
// 工作区管理
"workspaces:list": "yarn workspaces list",
"workspaces:info": "yarn workspaces foreach -v info",
// 发布管理
"changeset": "changeset",
"version": "changeset version",
"release": "yarn build && changeset publish"
},
"devDependencies": {
"@changesets/cli": "^2.26.2",
"typescript": "^5.2.2",
"eslint": "^8.53.0",
"prettier": "^3.0.3"
},
"engines": {
"node": ">=18.0.0",
"yarn": ">=3.0.0"
}
}
1.4.4.2 包级别配置
UI 组件库配置:
// packages/ui-components/package.json
{
"name": "@company/ui-components",
"version": "1.0.0",
"main": "./dist/index.js",
"module": "./dist/index.esm.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc && vite build",
"dev": "vite build --watch",
"test": "vitest",
"test:ui": "vitest --ui",
"clean": "rimraf dist",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.37",
"@vitejs/plugin-react": "^4.1.0",
"vite": "^4.5.0",
"typescript": "workspace:*",
"vitest": "^0.34.6",
"@storybook/react": "^7.5.3"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"publishConfig": {
"access": "restricted"
},
"files": [
"dist",
"README.md"
]
}
Web 应用配置:
// apps/web-app/package.json
{
"name": "@company/web-app",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "vitest",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"type-check": "tsc --noEmit",
"clean": "rimraf dist"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.17.0",
"@company/ui-components": "workspace:*",
"@company/utils": "workspace:*",
"@company/types": "workspace:*"
},
"devDependencies": {
"@types/react": "^18.2.37",
"@vitejs/plugin-react": "^4.1.0",
"vite": "^4.5.0",
"typescript": "workspace:*",
"vitest": "^0.34.6"
}
}
1.4.4.3 工作区命令详解
基础工作区命令:
# 列出所有工作区
yarn workspaces list
# 输出:
# - @company/ui-components (packages/ui-components)
# - @company/utils (packages/utils)
# - @company/web-app (apps/web-app)
# - @company/admin-panel (apps/admin-panel)
# 在特定工作区运行命令
yarn workspace @company/web-app dev
yarn workspace @company/ui-components build
# 在所有工作区运行命令
yarn workspaces foreach run test
yarn workspaces foreach run lint
# 并行执行
yarn workspaces foreach -p run build
# 排除特定工作区
yarn workspaces foreach --exclude @company/web-app run build
高级工作区操作:
# 依赖信息
yarn workspaces foreach info
yarn workspaces foreach -v info # 详细信息
# 过滤执行
yarn workspaces foreach -Apt run build # 包含依赖的包
yarn workspaces foreach -Rpt run build # 从根包开始
yarn workspaces foreach -it run dev # 交互式选择
# 条件执行
yarn workspaces foreach --include 'apps/*' run build
yarn workspaces foreach --exclude 'tools/*' run test
# 管道操作
yarn workspaces list | grep 'apps' | xargs yarn workspace
# 工作区依赖管理
yarn workspace @company/web-app add react-query
yarn add react-query -W # 添加到根包
yarn add react-query --packages @company/web-app # 添加到特定包
1.4.5 Yarn 高级特性与最佳实践
1.4.5.1 插件系统使用
安装和配置插件:
# 安装插件
yarn add @yarnpkg/plugin-constraints
yarn add @yarnpkg/plugin-interactive-tools
yarn add @yarnpkg/plugin-version
yarn add @yarnpkg/plugin-exec
# 手动添加插件到 .yarnrc.yml
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-constraints.cjs
spec: "@yarnpkg/plugin-constraints"
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
# 启用插件后可使用的新命令
yarn constraints # 依赖约束管理
yarn interactive # 交互式工具
yarn version # 版本管理
yarn exec # 执行命令
约束插件使用:
# .yarnrc.yml 中配置约束
constraints:
# 版本约束
- "react@^18.2.0"
- "typescript@^5.0.0"
# 依赖关系约束
- "workspace:*": { peerDependencies: { "react": "^18.0.0" } }
# 平台约束
- "engines": { "node": ">=18.0.0" }
# 安全约束
- "no-deprecated-packages"
# 验证约束
yarn constraints check
yarn constraints fix
交互式工具使用:
# 启动交互式界面
yarn interactive
# 交互式依赖管理
yarn add react --interactive
# 交互式版本升级
yarn upgrade-interactive
# 交互式工作区管理
yarn workspaces foreach --interactive run build
1.4.5.2 性能优化策略
缓存优化:
# 启用全局缓存
yarn config set enableGlobalCache true
# 配置缓存目录
yarn config set cacheFolder ~/.yarn/cache
# 清理缓存
yarn cache clean
yarn cache clean react
# 缓存分析
yarn cache list
yarn cache dir
并行构建:
// package.json 配置
{
"scripts": {
"build": "yarn workspaces foreach -pt run build",
"build:parallel": "yarn workspaces foreach -p -pt run build",
"test:parallel": "yarn workspaces foreach -p run test"
}
}
网络优化:
# 配置镜像
yarn config set npmRegistryServer https://registry.npmmirror.com
# 并发配置
yarn config set networkConcurrency 8
yarn config set childProcessConcurrency 8
# 超时配置
yarn config set networkTimeout 300000
1.4.5.3 CI/CD 集成
GitHub Actions 配置:
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
NODE_VERSION: '18'
YARN_VERSION: '3.6.4'
jobs:
install:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- name: Enable Corepack
run: corepack enable
- name: Set Yarn version
run: corepack prepare yarn@${{ env.YARN_VERSION }} --activate
- name: Install dependencies
run: |
if [ -f ".yarnrc.yml" ]; then
yarn config set enableGlobalCache true
yarn install --immutable --immutable-cache
else
yarn install --frozen-lockfile
fi
- name: Cache Yarn Berry
uses: actions/cache@v3
with:
path: |
~/.yarn/berry
~/.yarn/cache
key: ${{ runner.os }}-yarn-${{ env.YARN_VERSION }}-${{ hashFiles('**/yarn.lock', '**/.yarnrc.yml') }}
restore-keys: |
${{ runner.os }}-yarn-${{ env.YARN_VERSION }}-
test:
needs: install
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- name: Enable Corepack
run: corepack enable
- name: Set Yarn version
run: corepack prepare yarn@${{ env.YARN_VERSION }} --activate
- name: Restore dependencies
run: yarn install --immutable --immutable-cache
- name: Type check
run: yarn workspaces foreach run type-check
- name: Lint
run: yarn workspaces foreach run lint
- name: Test
run: yarn workspaces foreach run test:coverage
build:
needs: install
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- name: Enable Corepack
run: corepack enable
- name: Set Yarn version
run: corepack prepare yarn@${{ env.YARN_VERSION }} --activate
- name: Restore dependencies
run: yarn install --immutable --immutable-cache
- name: Build packages
run: yarn workspaces foreach -pt run build
- name: Build apps
run: yarn workspaces foreach -Apt run build
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: dist
path: |
apps/*/dist
packages/*/dist
通过这种完整的 Yarn + React + TypeScript 开发指南,你可以充分利用 Yarn Berry 的先进特性,构建高效、现代化的前端项目。
1.5 Rollup 打包详解 - 多种构建方式统一实践
1.5.1 Rollup 深度解析与核心概念
1.5.1.1 什么是 Rollup
Rollup 是一个现代化的 JavaScript 模块打包器,专注于 ES6 模块打包,生成更小、更高效的代码。它通过 ES6 模块的静态分析能力,实现 Tree Shaking,移除未使用的代码。
Rollup vs Webpack 对比:
| 特性 | Rollup | Webpack |
|---|---|---|
| 打包目标 | 库、组件库 | 应用、SPA |
| 模块处理 | ES6 原生支持 | 需要 loader |
| Tree Shaking | 原生支持 | 需要配置 |
| 输出文件 | 单文件(默认) | 多文件(chunks) |
| 学习曲线 | 简单 | 复杂 |
| 构建速度 | 快 | 中等 |
| 代码分割 | 有限 | 强大 |
| 热更新 | 需要插件 | 原生支持 |
1.5.1.2 Rollup 核心工作原理
ES6 模块静态分析:
// 输入代码示例
// utils.js
export const add = (a, b) => a + b
export const subtract = (a, b) => a - b
export const multiply = (a, b) => a * b
// main.js
import { add } from './utils.js'
console.log(add(2, 3))
// Rollup Tree Shaking 后的输出代码
// utils.js 中只有 add 函数被包含,subtract 和 multiply 被移除
const add = (a, b) => a + b
console.log(add(2, 3))
打包流程详解:
1.5.2 npm + Rollup 项目构建详解
1.5.2.1 基础 npm 项目配置
安装 Rollup 相关依赖:
# 创建项目
npm create vite@latest npm-rollup-app --template react-ts
cd npm-rollup-app
# 安装 Rollup 及相关插件
npm install -D rollup \
@rollup/plugin-typescript \
@rollup/plugin-node-resolve \
@rollup/plugin-commonjs \
@rollup/plugin-json \
@rollup/plugin-replace \
@rollup/plugin-terser \
rollup-plugin-dts \
@types/react \
@types/react-dom
# 安装运行时依赖
npm install react react-dom
package.json 配置:
{
"name": "npm-rollup-app",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "rollup -c",
"build:watch": "rollup -c -w",
"build:types": "rollup -c --configPlugin typescript --config rollup.config.d.ts",
"preview": "vite preview",
"clean": "rimraf dist"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-json": "^6.0.1",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-replace": "^5.0.5",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^11.1.5",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"rollup": "^4.4.0",
"rollup-plugin-dts": "^6.1.0",
"typescript": "^5.2.2"
}
}
rollup.config.js 配置:
// rollup.config.js
import typescript from '@rollup/plugin-typescript'
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import json from '@rollup/plugin-json'
import replace from '@rollup/plugin-replace'
import { terser } from '@rollup/plugin-terser'
import dts from 'rollup-plugin-dts'
import { readFileSync } from 'fs'
import { fileURLToPath } from 'url'
import { dirname, join } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
// 读取 package.json
const pkg = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf8'))
// 环境变量
const isProduction = process.env.NODE_ENV === 'production'
const isDevelopment = !isProduction
// 外部依赖(不打包到 bundle 中)
const external = [
'react',
'react-dom',
'react/jsx-runtime',
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {})
]
// 通用插件配置
const plugins = [
// 环境变量替换
replace({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || ''),
preventAssignment: true
}),
// TypeScript 支持
typescript({
tsconfig: './tsconfig.json',
declaration: false,
declarationMap: false,
exclude: ['**/*.test.tsx', '**/*.test.ts', '**/*.stories.tsx']
}),
// Node 模块解析
resolve({
browser: true,
preferBuiltins: false,
extensions: ['.mjs', '.js', '.json', '.node', '.ts', '.tsx']
}),
// CommonJS 模块支持
commonjs({
include: /node_modules/
}),
// JSON 支持
json()
]
// 生产环境额外插件
if (isProduction) {
plugins.push(
terser({
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log', 'console.info']
},
format: {
comments: false
}
})
)
}
// 配置导出
export default [
// JS 构建配置
{
input: 'src/index.tsx',
external,
output: [
// ES Module
{
file: pkg.exports?.['.']?.import || 'dist/index.esm.js',
format: 'esm',
sourcemap: true
},
// CommonJS
{
file: pkg.main || 'dist/index.cjs.js',
format: 'cjs',
sourcemap: true,
exports: 'named'
},
// UMD
{
file: pkg.browser || 'dist/index.umd.js',
format: 'umd',
sourcemap: true,
name: pkg.name?.replace(/^@.*\//, '').replace(/-/g, ''),
globals: {
react: 'React',
'react-dom': 'ReactDOM',
'react/jsx-runtime': 'ReactJSXRuntime'
}
}
],
plugins
},
// 类型定义文件构建
{
input: 'src/index.tsx',
output: {
file: pkg.types || 'dist/index.d.ts',
format: 'esm'
},
external,
plugins: [
dts({
compilerOptions: {
baseUrl: '.',
paths: {
'@/*': ['src/*']
}
}
})
]
}
]
1.5.3 pnpm + Rollup 项目构建详解
1.5.3.1 pnpm Monorepo Rollup 配置
项目结构:
pnpm-rollup-monorepo/
├── apps/
│ ├── web-app/ # Web 应用
│ └── admin-panel/ # 管理面板
├── packages/
│ ├── ui-components/ # UI 组件库
│ ├── utils/ # 工具函数库
│ └── types/ # 类型定义
├── tools/
│ └── rollup-config/ # 共享 Rollup 配置
├── rollup.config.js # 根 Rollup 配置
├── pnpm-workspace.yaml
└── package.json
共享 Rollup 配置:
// tools/rollup-config/index.js
import typescript from '@rollup/plugin-typescript'
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import json from '@rollup/plugin-json'
import { terser } from '@rollup/plugin-terser'
import dts from 'rollup-plugin-dts'
import { readFileSync } from 'fs'
import { join } from 'path'
// 通用配置工厂
export function createRollupConfig(options = {}) {
const {
input = 'src/index.ts',
external = [],
plugins = [],
output = {},
typescriptOptions = {},
isProduction = process.env.NODE_ENV === 'production'
} = options
const basePlugins = [
// TypeScript 插件
typescript({
tsconfig: './tsconfig.json',
declaration: false,
...typescriptOptions
}),
// 模块解析
resolve({
browser: true,
preferBuiltins: false,
extensions: ['.mjs', '.js', '.json', '.node', '.ts', '.tsx']
}),
// CommonJS 支持
commonjs(),
// JSON 支持
json()
]
// 生产环境插件
if (isProduction) {
basePlugins.push(
terser({
compress: {
drop_console: true,
drop_debugger: true
}
})
)
}
return {
input,
external,
plugins: [...basePlugins, ...plugins],
output: {
sourcemap: !isProduction,
...output
}
}
}
// 库构建配置
export function createLibraryConfig(pkgPath, options = {}) {
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
const external = [
'react',
'react-dom',
'react/jsx-runtime',
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {})
]
return createRollupConfig({
input: options.input || 'src/index.ts',
external,
output: [
// ES Module
{
file: pkg.exports?.['.']?.import || 'dist/index.esm.js',
format: 'esm'
},
// CommonJS
{
file: pkg.main || 'dist/index.cjs.js',
format: 'cjs'
},
// UMD (可选)
...(pkg.browser ? [{
file: pkg.browser,
format: 'umd',
name: pkg.name?.replace(/^@.*\//, '').replace(/-/g, ''),
globals: {
react: 'React',
'react-dom': 'ReactDOM'
}
}] : [])
],
...options
})
}
// 应用构建配置
export function createAppConfig(options = {}) {
return createRollupConfig({
input: options.input || 'src/main.tsx',
external: [], // 应用通常打包所有依赖
output: {
file: options.outputFile || 'dist/bundle.js',
format: 'iife',
name: 'App'
},
...options
})
}
包级别配置示例:
// packages/ui-components/rollup.config.js
import { createLibraryConfig } from '../../tools/rollup-config'
export default createLibraryConfig('./package.json', {
typescriptOptions: {
exclude: ['**/*.test.tsx', '**/*.stories.tsx']
}
})
pnpm scripts 配置:
{
"scripts": {
// 构建所有包
"build": "pnpm -r --filter './packages/*' build",
// 构建特定包
"build:ui": "pnpm --filter '@company/ui-components' build",
// 开发模式构建
"build:dev": "pnpm build -- --environment NODE_ENV:development",
// 生产模式构建
"build:prod": "pnpm build -- --environment NODE_ENV:production",
// 监听模式构建
"build:watch": "pnpm build -- --watch",
// 类型定义构建
"build:types": "pnpm -r --filter './packages/*' build:types",
// 应用构建
"build:apps": "pnpm -r --filter './apps/*' build"
}
}
1.5.4 Yarn + Rollup 项目构建详解
1.5.4.1 Yarn Berry + Rollup 集成
Yarn Berry 配置:
# .yarnrc.yml
yarnPath: .yarn/releases/yarn-3.6.4.cjs
nodeLinker: pnp
enableGlobalCache: true
# 工作区配置
workspaces:
- 'apps/*'
- 'packages/*'
- 'tools/*'
# 插件配置
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
spec: "@yarnpkg/plugin-typescript"
- path: .yarn/plugins/@yarnpkg/plugin-exec.cjs
spec: "@yarnpkg/plugin-exec"
# 缓存配置
enableImmutableCache: true
enableImmutableInstalls: true
Yarn Package Scripts:
{
"scripts": {
// Yarn Berry 原生命令
"build": "yarn workspaces foreach -pt run build",
"build:parallel": "yarn workspaces foreach -p -pt run build",
// 带环境变量的构建
"build:prod": "yarn workspaces foreach -pt run build:prod",
"build:dev": "yarn workspaces foreach -pt run build:dev",
// 条件构建
"build:changed": "yarn workspaces foreach --since origin/main run build",
"build:deps": "yarn workspaces foreach --include-dependents run build",
// 特定包构建
"build:ui": "yarn workspace @company/ui-components build",
"build:web": "yarn workspace @company/web-app build",
// 零安装构建
"build:zero-install": "yarn build --immutable --immutable-cache"
}
}
高级 Rollup 配置 for Yarn:
// rollup.config.js (Yarn Berry 优化版)
import typescript from '@rollup/plugin-typescript'
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import { terser } from '@rollup/plugin-terser'
import dts from 'rollup-plugin-dts'
import { readFileSync } from 'fs'
// Yarn Berry 特定的插件
const createYarnPlugins = () => {
const plugins = [
// PnP 支持
{
name: 'yarn-pnp-resolve',
resolveId(source, importer) {
// Yarn PnP 解析逻辑
try {
const pnp = require('.pnp.cjs')
const resolution = pnp.resolveToUnqualified(source, importer)
return resolution ? { id: resolution } : null
} catch (e) {
return null
}
}
},
// TypeScript 插件
typescript({
tsconfig: './tsconfig.json',
typescript: require('typescript'),
// Yarn Berry 特定配置
moduleResolution: 'node',
allowSyntheticDefaultImports: true
})
]
return plugins
}
// 配置导出
export default createYarnPlugins()
1.5.5 高级 Rollup 配置技巧
1.5.5.1 代码分割策略
动态导入分割:
// rollup.config.js - 代码分割配置
export default {
input: 'src/main.tsx',
output: {
dir: 'dist',
format: 'esm',
chunkFileNames: 'js/[name]-[hash].js',
entryFileNames: 'js/[name]-[hash].js',
manualChunks: {
// 第三方库分包
vendor: ['react', 'react-dom'],
router: ['react-router-dom'],
utils: ['lodash', 'date-fns'],
// 按功能分包
auth: ['src/pages/auth', 'src/components/auth'],
dashboard: ['src/pages/dashboard', 'src/components/dashboard']
},
// 动态导入
dynamicImportFunction: 'import'
},
plugins: [
// 处理动态导入
{
name: 'dynamic-import',
renderDynamicImport({ moduleId }) {
if (moduleId.includes('src/pages/')) {
return {
left: 'import(',
right: ')'
}
}
return null
}
}
]
}
1.5.5.2 插件开发实战
自定义插件示例:
// plugins/bundle-analyzer.js
export function bundleAnalyzerPlugin(options = {}) {
return {
name: 'bundle-analyzer',
generateBundle(outputOptions, bundle) {
const analysis = {}
for (const [fileName, chunk] of Object.entries(bundle)) {
if (chunk.type === 'chunk') {
analysis[fileName] = {
size: chunk.code.length,
imports: chunk.imports,
modules: Object.keys(chunk.modules || {}),
dependencies: chunk.importedBindings
}
}
}
// 输出分析结果
if (options.outputFile) {
require('fs').writeFileSync(
options.outputFile,
JSON.stringify(analysis, null, 2)
)
}
}
}
}
// 环境变量插件
export function envVarsPlugin(vars = {}) {
const replacements = Object.entries(vars).map(([key, value]) => ({
test: new RegExp(`process\\.env\\.${key}`, 'g'),
replace: JSON.stringify(value)
}))
return {
name: 'env-vars',
transform(code) {
return replacements.reduce(
(result, { test, replace }) => result.replace(test, replace),
code
)
}
}
}
1.5.5.3 性能优化配置
构建性能优化:
// rollup.config.js - 性能优化版
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
export default {
// 缓存配置
cache: true,
// 监听配置
watch: {
include: 'src/**',
exclude: 'node_modules/**',
clearScreen: false,
buildDelay: 100
},
// 外部依赖优化
external: (id) => {
// Node 内置模块
if (id.startsWith('node:')) return true
// 大型第三方库
const largeLibs = ['react', 'react-dom', 'lodash', 'moment']
if (largeLibs.some(lib => id.startsWith(lib))) return true
return false
},
// 插件性能优化
plugins: [
// 并行处理
{
name: 'parallel-processing',
buildStart() {
this.cache.set('parallel', true)
}
},
// 缓存优化
{
name: 'cache-optimization',
load(id) {
const cached = this.cache.get(id)
if (cached) return cached
},
generateBundle(code, id) {
this.cache.set(id, code)
}
}
]
}
1.5.5.4 多环境构建配置
环境配置管理:
// rollup.config.js - 多环境配置
const environments = {
development: {
output: {
sourcemap: true,
minify: false
},
plugins: [
// 开发环境插件
devServerPlugin()
]
},
production: {
output: {
sourcemap: false,
minify: true
},
plugins: [
// 生产环境插件
terserPlugin(),
compressionPlugin()
]
},
test: {
output: {
sourcemap: 'inline',
format: 'esm'
},
plugins: [
// 测试环境插件
coveragePlugin()
]
}
}
// 动态配置选择
const env = process.env.NODE_ENV || 'development'
const envConfig = environments[env]
export default [
{
input: 'src/index.ts',
output: {
file: 'dist/index.js',
format: 'esm',
...envConfig.output
},
plugins: [
...basePlugins,
...envConfig.plugins
]
}
]
1.5.6 CI/CD 集成与最佳实践
1.5.6.1 GitHub Actions 配置
# .github/workflows/rollup-build.yml
name: Rollup Build Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
packageManager: [npm, pnpm, yarn]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: '${{ matrix.packageManager }}'
- name: Setup ${{ matrix.packageManager }}
if: matrix.packageManager != 'npm'
run: |
if [ "${{ matrix.packageManager }}" = "pnpm" ]; then
corepack enable pnpm
elif [ "${{ matrix.packageManager }}" = "yarn" ]; then
corepack enable yarn
corepack prepare yarn@berry --activate
fi
- name: Install dependencies
run: |
if [ "${{ matrix.packageManager }}" = "npm" ]; then
npm ci
elif [ "${{ matrix.packageManager }}" = "pnpm" ]; then
pnpm install --frozen-lockfile
elif [ "${{ matrix.packageManager }}" = "yarn" ]; then
yarn install --immutable
fi
- name: Run tests
run: |
if [ "${{ matrix.packageManager }}" = "npm" ]; then
npm test
elif [ "${{ matrix.packageManager }}" = "pnpm" ]; then
pnpm test
elif [ "${{ matrix.packageManager }}" = "yarn" ]; then
yarn test
fi
- name: Build with Rollup
run: |
if [ "${{ matrix.packageManager }}" = "npm" ]; then
npm run build
elif [ "${{ matrix.packageManager }}" = "pnpm" ]; then
pnpm build
elif [ "${{ matrix.packageManager }}" = "yarn" ]; then
yarn build
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build-${{ matrix.packageManager }}
path: dist/
1.5.6.2 构建性能监控
// scripts/build-monitor.js
import { performance } from 'perf_hooks'
import { createWriteStream } from 'fs'
class BuildMonitor {
constructor() {
this.metrics = {
startTime: performance.now(),
phases: {},
bundles: {}
}
}
startPhase(name) {
this.metrics.phases[name] = {
start: performance.now()
}
}
endPhase(name) {
const phase = this.metrics.phases[name]
if (phase) {
phase.duration = performance.now() - phase.start
}
}
recordBundle(name, size) {
this.metrics.bundles[name] = {
size,
sizeKB: Math.round(size / 1024 * 100) / 100
}
}
generateReport() {
const totalDuration = performance.now() - this.metrics.startTime
const report = {
totalDuration: Math.round(totalDuration),
phases: this.metrics.phases,
bundles: this.metrics.bundles
}
// 输出到文件
const stream = createWriteStream('build-report.json')
stream.write(JSON.stringify(report, null, 2))
stream.end()
return report
}
}
export const buildMonitor = new BuildMonitor()
1.5.7 总结与最佳实践
1.5.7.1 选择合适的包管理器
决策矩阵:
| 场景 | npm | pnpm | Yarn |
|---|---|---|---|
| 小型项目 | ✅ 推荐 | ✅ 可选 | ✅ 可选 |
| 大型项目 | ❌ 不推荐 | ✅ 推荐 | ✅ 可选 |
| Monorepo | ❌ 复杂 | ✅ 最佳 | ✅ 良好 |
| 零安装需求 | ❌ 不支持 | ❌ 部分支持 | ✅ 原生支持 |
| CI/CD 简单性 | ✅ 最简单 | ✅ 简单 | ⚠️ 中等 |
1.5.7.2 Rollup 配置最佳实践
- 模块化配置:将配置分解为可复用的函数
- 环境区分:使用环境变量控制不同构建模式
- 缓存优化:启用 Rollup 缓存提升构建速度
- 类型安全:使用 TypeScript 编写配置文件
- 插件组合:合理选择和配置插件
1.5.7.3 项目结构建议
最佳实践结构:
project/
├── src/ # 源代码
├── dist/ # 构建输出
├── rollup.config.js # Rollup 配置
├── rollup.config.d.ts # 类型定义
├── scripts/ # 构建脚本
├── tools/ # 共享工具
├── package.json # 项目配置
├── tsconfig.json # TypeScript 配置
└── build-report.json # 构建报告
通过这种全面的多包管理器 + Rollup 打包指南,你可以根据项目需求选择最适合的构建方案,实现高效、可靠的前端工程化实践。
1.6 pnpm + React + TS + Monorepo + Turbo 详解
1.6.1 Turbo 深度解析与核心概念
1.6.1.1 什么是 Turbo
Turbo 是由 Vercel 开发的高性能构建系统,专门为 Monorepo 设计。它通过智能缓存、并行执行和依赖图分析,大幅提升大型项目的构建速度。
Turbo vs 传统构建工具对比:
| 特性 | Turbo | Lerna | Nx | Rush |
|---|---|---|---|---|
| 缓存机制 | 增量缓存 | 基础缓存 | 智能缓存 | 本地缓存 |
| 并行构建 | 原生支持 | 有限支持 | 原生支持 | 原生支持 |
| 依赖分析 | 动态依赖图 | 静态分析 | 智能分析 | 静态分析 |
| 远程缓存 | 原生支持 | 不支持 | 付费功能 | 有限支持 |
| 学习曲线 | 简单 | 中等 | 复杂 | 复杂 |
| 性能 | 最高 | 中等 | 高 | 高 |
| 配置复杂度 | 低 | 低 | 高 | 高 |
1.6.1.2 Turbo 核心架构
Turbo 工作原理:
Turbo 缓存系统详解:
# Turbo 缓存层次结构
turbo-cache/
├── local/ # 本地缓存
│ ├── builds/ # 构建缓存
│ ├── tests/ # 测试缓存
│ └── tasks/ # 任务缓存
├── remote/ # 远程缓存(可选)
│ ├── s3/ # AWS S3
│ ├── vercel/ # Vercel Remote Cache
│ └── custom/ # 自定义缓存
└── metadata/ # 缓存元数据
├── hashes.json # 文件哈希
└── dependencies.json # 依赖关系
1.6.2 pnpm + Turbo 环境搭建
1.6.2.1 项目初始化
创建 Turbo Monorepo:
# 方法一:使用 create-turbo 创建
npx create-turbo@latest my-turbo-monorepo
cd my-turbo-monorepo
# 方法二:手动创建
mkdir turbo-react-monorepo
cd turbo-react-monorepo
# 初始化 pnpm 工作区
pnpm init
# 创建目录结构
mkdir -p apps/{web-app,admin-panel}
mkdir -p packages/{ui-components,utils,types}
mkdir -p tools/{eslint-config,tsconfig}
# 创建 pnpm-workspace.yaml
cat > pnpm-workspace.yaml << 'EOF'
packages:
- 'apps/*'
- 'packages/*'
- 'tools/*'
catalog:
react18: 'react@18.2.0'
typescript: 'typescript@5.2.2'
turbo: 'turbo@1.10.14'
shared-workspace-lockfile: true
link-workspace-packages: true
prefer-workspace-packages: true
EOF
根 package.json 配置:
{
"name": "@company/turbo-monorepo",
"version": "1.0.0",
"private": true,
"packageManager": "pnpm@8.10.5",
"scripts": {
// Turbo 命令
"build": "turbo run build",
"dev": "turbo run dev",
"test": "turbo run test",
"lint": "turbo run lint",
"type-check": "turbo run type-check",
"clean": "turbo run clean && rm -rf .turbo",
// 高级 Turbo 命令
"build:force": "turbo run build --force",
"build:filter": "turbo run build --filter=@company/ui-components",
"build:graph": "turbo run build --graph=build-graph.json",
// 开发命令
"dev:web": "turbo run dev --filter=@company/web-app",
"dev:admin": "turbo run dev --filter=@company/admin-panel",
"dev:parallel": "turbo run dev --parallel",
// 缓存管理
"cache:clean": "turbo prune",
"cache:status": "turbo run status",
"cache:verify": "turbo run build --dry-run=json",
// 依赖管理
"upgrade": "pnpm update --interactive",
"sync": "turbo run sync",
"sync:force": "turbo run sync --force"
},
"devDependencies": {
"turbo": "catalog:turbo",
"typescript": "catalog:typescript",
"eslint": "^8.53.0",
"prettier": "^3.0.3",
"husky": "^8.0.3",
"lint-staged": "^15.1.0"
},
"engines": {
"node": ">=18.0.0",
"pnpm": ">=8.0.0"
}
}
1.6.2.2 Turbo 配置详解
turbo.json 核心配置:
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": [
"**/.env.*local",
".env",
"tsconfig.json",
"turbo.json"
],
"pipeline": {
// 构建任务
"build": {
"dependsOn": ["^build"],
"outputs": [
"dist/**",
".next/**",
"!.next/cache/**"
],
"cache": true,
"inputs": [
"src/**",
"package.json",
"tsconfig.json",
"vite.config.*",
"rollup.config.*"
],
"env": ["NODE_ENV", "PUBLIC_URL"],
"passThroughEnv": ["VERCEL", "AWS_*"]
},
// 开发任务
"dev": {
"cache": false,
"persistent": true,
"passThroughEnv": ["PORT", "HOST", "NODE_ENV"]
},
// 测试任务
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"],
"cache": true,
"inputs": [
"src/**",
"test/**",
"package.json",
"jest.config.*",
"vitest.config.*"
]
},
// 代码检查
"lint": {
"outputs": [],
"cache": true,
"inputs": [
"src/**",
"package.json",
".eslintrc.*",
".eslintignore"
]
},
// 类型检查
"type-check": {
"dependsOn": ["^build"],
"outputs": [],
"cache": true,
"inputs": [
"src/**",
"tsconfig.json",
"package.json"
]
},
// 清理任务
"clean": {
"cache": false,
"outputs": []
},
// 同步任务
"sync": {
"cache": false,
"outputs": [],
"passThroughEnv": ["SYNC_TARGET", "SYNC_SOURCE"]
}
},
"globalEnv": [
"NODE_ENV",
"CI",
"VERCEL",
"TURBO_TOKEN",
"TURBO_TEAM"
]
}
高级 Turbo 配置:
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": [
"**/.env.*local",
"pnpm-workspace.yaml",
"turbo.json"
],
"pipeline": {
// 复杂依赖链
"build:ui": {
"dependsOn": ["^build:utils", "^build:types"],
"outputs": ["dist/**", "storybook-static/**"],
"cache": true,
"env": ["NODE_ENV", "STORYBOOK_ENV"]
},
"build:web": {
"dependsOn": ["build:ui"],
"outputs": [".next/**", "dist/**"],
"cache": true
},
"deploy:web": {
"dependsOn": ["build:web", "test:web"],
"outputs": [],
"cache": false
},
// 并行任务
"test:unit": {
"dependsOn": [],
"outputs": ["coverage/unit/**"],
"cache": true
},
"test:e2e": {
"dependsOn": ["build:web"],
"outputs": ["coverage/e2e/**"],
"cache": true
},
// 条件执行
"publish": {
"dependsOn": ["build", "test"],
"outputs": [],
"cache": false,
"env": ["NPM_TOKEN", "NPM_REGISTRY"]
}
},
"remoteCache": {
"signature": true,
"teamId": "your-team-id",
"token": "$TURBO_TOKEN"
}
}
1.6.3 包级别配置实战
1.6.3.1 UI 组件库配置
packages/ui-components/package.json:
{
"name": "@company/ui-components",
"version": "1.0.0",
"private": true,
"main": "./dist/index.js",
"module": "./dist/index.esm.js",
"types": "./dist/index.d.ts",
"scripts": {
// 开发脚本
"dev": "vite build --watch",
"storybook": "storybook dev -p 6006",
// 构建脚本
"build": "turbo run build:lib && turbo run build:storybook",
"build:lib": "tsc && vite build",
"build:storybook": "build-storybook",
// 测试脚本
"test": "vitest run",
"test:watch": "vitest watch",
"test:ui": "vitest --ui",
"test:coverage": "vitest run --coverage",
// 代码质量
"lint": "eslint src --ext ts,tsx --max-warnings 0",
"lint:fix": "eslint src --ext ts,tsx --fix",
"type-check": "tsc --noEmit",
// 工具脚本
"clean": "rimraf dist storybook-static coverage",
"generate": "turbo run generate:types && turbo run generate:stories"
},
"dependencies": {
"react": "catalog:react18",
"react-dom": "catalog:react18",
"@company/utils": "workspace:*",
"@company/types": "workspace:*"
},
"devDependencies": {
"@types/react": "@types/react@18.2.37",
"@vitejs/plugin-react": "@vitejs/plugin-react@4.1.0",
"vite": "vite@4.5.0",
"typescript": "catalog:typescript",
"vitest": "^0.34.6",
"@storybook/react": "^7.5.3",
"@storybook/addon-essentials": "^7.5.3",
"rimraf": "^5.0.5"
},
"files": [
"dist",
"README.md",
"CHANGELOG.md"
]
}
packages/ui-components/vite.config.ts:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'
import dts from 'vite-plugin-dts'
export default defineConfig({
plugins: [
react(),
dts({
insertTypesEntry: true,
include: ['src/**/*'],
exclude: ['src/**/*.stories.*', 'src/**/*.test.*']
})
],
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'UIComponents',
formats: ['es', 'cjs', 'umd'],
fileName: (format) => `index.${format}.js`
},
rollupOptions: {
external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM'
}
}
},
sourcemap: true,
emptyOutDir: true
},
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@/components': resolve(__dirname, 'src/components'),
'@/utils': resolve(__dirname, '../utils/src'),
'@/types': resolve(__dirname, '../types/src')
}
}
})
1.6.3.2 Web 应用配置
apps/web-app/package.json:
{
"name": "@company/web-app",
"version": "1.0.0",
"private": true,
"scripts": {
// 开发脚本
"dev": "next dev --port 3000",
"dev:debug": "NODE_OPTIONS='--inspect' next dev --port 3000",
// 构建脚本
"build": "next build",
"build:analyze": "ANALYZE=true next build",
"build:export": "next build && next export",
// 测试脚本
"test": "vitest run",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
// 代码质量
"lint": "next lint",
"lint:fix": "next lint --fix",
"type-check": "tsc --noEmit",
// 部署脚本
"start": "next start",
"deploy": "turbo run build && turbo run deploy:vercel",
"deploy:vercel": "vercel --prod",
// 工具脚本
"clean": "rimraf .next out dist",
"sync": "turbo run sync:deps"
},
"dependencies": {
"react": "catalog:react18",
"react-dom": "catalog:react18",
"next": "^14.0.0",
"react-router-dom": "^6.17.0",
"@company/ui-components": "workspace:*",
"@company/utils": "workspace:*",
"@company/types": "workspace:*"
},
"devDependencies": {
"@types/react": "@types/react@18.2.37",
"@types/react-dom": "@types/react-dom@18.2.15",
"@next/eslint-config-next": "^14.0.0",
"typescript": "catalog:typescript",
"vitest": "^0.34.6",
"@playwright/test": "^1.40.0",
"@vercel/ncc": "^0.38.0",
"rimraf": "^5.0.5"
}
}
1.6.4 Turbo 高级特性应用
1.6.4.1 智能缓存策略
本地缓存配置:
// turbo.json - 高级缓存配置
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"],
"cache": true,
"inputs": [
"src/**",
"package.json",
{
"env": "NODE_ENV",
"passThroughEnv": false
}
]
},
// 自定义缓存策略
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"],
"cache": {
"strategy": "hash",
"key": ["test", "package.json", "src/**"],
"maxAge": 604800 // 7天
}
},
// 无缓存任务
"clean": {
"cache": false
},
// 敏感环境变量
"deploy": {
"dependsOn": ["build"],
"outputs": [],
"cache": false,
"passThroughEnv": [
"VERCEL_TOKEN",
"AWS_ACCESS_KEY_ID",
"AWS_SECRET_ACCESS_KEY"
]
}
}
}
远程缓存配置:
// turbo.json - 远程缓存
{
"remoteCache": {
"enabled": true,
"signature": true,
"teamId": "your-turbo-team-id",
"token": "$TURBO_TOKEN",
"url": "https://cache.turbo.build",
"timeout": 30000,
"compression": true,
"maxRetries": 3
}
}
环境变量配置:
# .env.local
TURBO_TOKEN=your_turbo_token
TURBO_TEAM=your_team_id
# CI/CD 环境
# .env.ci
TURBO_FORCE_RUN_TASKS=true
TURBO_API_URL=https://api.turbo.build
TURBO_REMOTE_CACHE_SIGNATURE=true
1.6.4.2 依赖图分析
依赖图生成:
# 生成依赖图
turbo run build --graph=dependency-graph.json
# 生成可视化图形
turbo run build --graph=dependency-graph.json --graphviz=dependency-graph.dot
# 分析特定包
turbo run build --filter=@company/ui-components --graph=ui-graph.json
依赖图分析工具:
// scripts/analyze-dependencies.js
import { readFileSync } from 'fs'
import { createWriteStream } from 'fs'
class DependencyAnalyzer {
constructor(graphPath) {
this.graph = JSON.parse(readFileSync(graphPath, 'utf8'))
this.analysis = {
totalTasks: 0,
parallelizableTasks: 0,
criticalPath: [],
bottlenecks: [],
levels: {}
}
}
analyze() {
this.countTasks()
this.findCriticalPath()
this.identifyBottlenecks()
this.calculateLevels()
return this.analysis
}
countTasks() {
this.analysis.totalTasks = Object.keys(this.graph.pipeline).length
}
findCriticalPath() {
// 找到最长的依赖链
const visited = new Set()
const longestPath = []
const dfs = (task, path) => {
if (visited.has(task)) return path
visited.add(task)
const deps = this.graph.pipeline[task]?.dependsOn || []
if (deps.length === 0) {
if (path.length > longestPath.length) {
longestPath.splice(0, longestPath.length, ...path)
}
} else {
for (const dep of deps) {
dfs(dep, [...path, dep])
}
}
}
Object.keys(this.graph.pipeline).forEach(task => {
dfs(task, [task])
visited.clear()
})
this.analysis.criticalPath = longestPath
}
identifyBottlenecks() {
// 找到被最多任务依赖的包
const dependencyCount = {}
Object.values(this.graph.pipeline).forEach(task => {
task.dependsOn?.forEach(dep => {
dependencyCount[dep] = (dependencyCount[dep] || 0) + 1
})
})
this.analysis.bottlenecks = Object.entries(dependencyCount)
.sort(([, a], [, b]) => b - a)
.slice(0, 5)
.map(([dep, count]) => ({ package: dep, dependentCount: count }))
}
generateReport() {
const report = this.analyze()
const stream = createWriteStream('dependency-analysis.json')
stream.write(JSON.stringify(report, null, 2))
stream.end()
console.log('📊 依赖分析报告:')
console.log(`总任务数: ${report.totalTasks}`)
console.log(`关键路径: ${report.criticalPath.join(' -> ')}`)
console.log('瓶颈包:', report.bottlenecks)
}
}
// 使用示例
if (require.main === module) {
const analyzer = new DependencyAnalyzer('./dependency-graph.json')
analyzer.generateReport()
}
1.6.4.3 性能监控与优化
构建性能监控:
// scripts/performance-monitor.js
import { performance } from 'perf_hooks'
import { createWriteStream } from 'fs'
class TurboPerformanceMonitor {
constructor() {
this.metrics = {
startTime: performance.now(),
tasks: {},
cacheHits: 0,
cacheMisses: 0,
totalTime: 0
}
}
startTask(taskName) {
this.metrics.tasks[taskName] = {
startTime: performance.now(),
status: 'running'
}
}
endTask(taskName, cacheHit = false) {
const task = this.metrics.tasks[taskName]
if (task) {
task.endTime = performance.now()
task.duration = task.endTime - task.startTime
task.status = 'completed'
if (cacheHit) {
this.metrics.cacheHits++
} else {
this.metrics.cacheMisses++
}
}
}
calculateEfficiency() {
const totalTasks = this.metrics.cacheHits + this.metrics.cacheMisses
const cacheEfficiency = (this.metrics.cacheHits / totalTasks) * 100
return {
cacheEfficiency: Math.round(cacheEfficiency * 100) / 100,
totalTasks,
cacheHits: this.metrics.cacheHits,
cacheMisses: this.metrics.cacheMisses
}
}
generateReport() {
this.metrics.totalTime = performance.now() - this.metrics.startTime
const efficiency = this.calculateEfficiency()
const report = {
totalTime: Math.round(this.metrics.totalTime),
efficiency,
tasks: this.metrics.tasks,
recommendations: this.getRecommendations(efficiency)
}
const stream = createWriteStream('performance-report.json')
stream.write(JSON.stringify(report, null, 2))
stream.end()
return report
}
getRecommendations(efficiency) {
const recommendations = []
if (efficiency.cacheEfficiency < 70) {
recommendations.push({
type: 'cache',
message: '缓存效率较低,建议检查 inputs 配置和文件变化模式'
})
}
const slowTasks = Object.entries(this.metrics.tasks)
.filter(([, task]) => task.duration > 10000)
.map(([name, task]) => ({ name, duration: task.duration }))
if (slowTasks.length > 0) {
recommendations.push({
type: 'performance',
message: '存在慢任务,建议优化构建配置',
tasks: slowTasks
})
}
return recommendations
}
}
// Turbo 包装器
export function createTurboWrapper() {
const monitor = new TurboPerformanceMonitor()
return {
run(command) {
console.log(`🚀 执行命令: ${command}`)
monitor.startTask(command)
const startTime = performance.now()
return new Promise((resolve, reject) => {
// 这里应该实际执行 turbo 命令
// 为了示例,我们模拟执行
setTimeout(() => {
const endTime = performance.now()
monitor.endTask(command, Math.random() > 0.5)
resolve({
command,
duration: endTime - startTime,
success: true
})
}, Math.random() * 5000)
})
},
generateReport() {
return monitor.generateReport()
}
}
}
1.6.5 CI/CD 集成与最佳实践
1.6.5.1 GitHub Actions 配置
# .github/workflows/turbo-ci.yml
name: Turbo CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
NODE_VERSION: '18'
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
apps: ${{ steps.changes.outputs.apps }}
packages: ${{ steps.changes.outputs.packages }}
should-deploy: ${{ steps.changes.outputs.should-deploy }}
steps:
- uses: actions/checkout@v3
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8.10.5
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- name: Setup pnpm cache
uses: actions/cache@v3
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Detect changes
id: changes
uses: dorny/paths-filter@v2
with:
filters: |
apps:
- 'apps/**'
packages:
- 'packages/**'
should-deploy:
- '.changeset/**'
quality-check:
if: needs.detect-changes.outputs.apps == 'true' || needs.detect-changes.outputs.packages == 'true'
runs-on: ubuntu-latest
needs: detect-changes
steps:
- uses: actions/checkout@v3
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8.10.5
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- name: Setup pnpm cache
uses: actions/cache@v3
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Setup Turbo
run: pnpm exec turbo --version
- name: Lint
run: pnpm turbo lint
- name: Type check
run: pnpm turbo type-check
- name: Test
run: pnpm turbo test
build:
if: needs.detect-changes.outputs.apps == 'true' || needs.detect-changes.outputs.packages == 'true'
runs-on: ubuntu-latest
needs: [detect-changes, quality-check]
outputs:
cache-hit: ${{ steps.cache.outputs.cache-hit }}
steps:
- uses: actions/checkout@v3
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8.10.5
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Setup Turbo cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build with Turbo
id: build
run: pnpm turbo build --force
- name: Cache build
id: cache
uses: actions/cache@v3
with:
path: |
apps/**/dist
apps/**/.next
packages/**/dist
key: ${{ runner.os }}-build-${{ github.sha }}
deploy:
if: needs.detect-changes.outputs.apps == 'true' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
needs: [detect-changes, build]
strategy:
matrix:
app: [web-app, admin-panel]
steps:
- uses: actions/checkout@v3
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8.10.5
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'pnpm'
- name: Restore Turbo cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
- name: Restore build cache
uses: actions/cache@v3
with:
path: |
apps/**/dist
apps/**/.next
packages/**/dist
key: ${{ runner.os }}-build-${{ github.sha }}
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Deploy ${{ matrix.app }}
run: |
echo "Deploying ${{ matrix.app }}..."
# 部署逻辑
pnpm turbo deploy --filter @company/${{ matrix.app }}
1.6.5.2 Docker 集成
# Dockerfile.turbo
FROM node:18-alpine AS base
WORKDIR /app
# 安装 pnpm
RUN corepack enable && corepack prepare pnpm@8.10.5 --activate
# 复制配置文件
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml turbo.json ./
COPY .npmrc ./
# 安装依赖
RUN pnpm install --frozen-lockfile
# 构建阶段
FROM base AS builder
COPY . .
# 使用 Turbo 构建
RUN pnpm turbo build
# 生产阶段
FROM base AS production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# 复制构建产物
COPY --from=builder --chown=nextjs:nodejs /app/apps/web-app/.next ./.next
COPY --from=builder --chown=nextjs:nodejs /app/apps/web-app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/apps/web-app/package.json ./package.json
USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
CMD ["pnpm", "start"]
1.6.5.3 监控和调试
Turbo 调试配置:
// turbo.json - 调试配置
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"],
"cache": true,
"inputs": ["src/**"]
},
"debug": {
"cache": false,
"passThroughEnv": ["DEBUG", "VERBOSE"]
}
},
"globalEnv": ["TURBO_DEBUG"],
"experimentalDefaults": {
"force": false,
"dryRun": false,
"logLevel": "debug"
}
}
调试脚本:
// scripts/turbo-debug.js
import { spawn } from 'child_process'
import { createWriteStream } from 'fs'
class TurboDebugger {
constructor() {
this.logFile = createWriteStream('turbo-debug.log')
}
async run(command, args = []) {
const process = spawn('pnpm', ['turbo', command, ...args], {
stdio: ['inherit', 'pipe', 'pipe'],
env: {
...process.env,
TURBO_DEBUG: 'true',
TURBO_LOG_LEVEL: 'debug'
}
})
process.stdout.on('data', (data) => {
const output = data.toString()
console.log(output)
this.logFile.write(`[STDOUT] ${output}`)
})
process.stderr.on('data', (data) => {
const output = data.toString()
console.error(output)
this.logFile.write(`[STDERR] ${output}`)
})
return new Promise((resolve, reject) => {
process.on('close', (code) => {
if (code === 0) {
resolve(code)
} else {
reject(new Error(`Process exited with code ${code}`))
}
})
})
}
async diagnose() {
console.log('🔍 Turbo 诊断开始...')
try {
// 检查配置
await this.run('lint')
// 检查依赖
await this.run('who', '--json')
// 分析缓存
await this.run('status')
// 生成依赖图
await this.run('build', ['--graph=debug-graph.json'])
console.log('✅ 诊断完成,查看 turbo-debug.log 获取详细信息')
} catch (error) {
console.error('❌ 诊断过程中发现问题:', error.message)
}
}
}
// 使用示例
const debugger = new TurboDebugger()
debugger.diagnose()
1.6.6 最佳实践总结
1.6.6.1 Turbo 配置最佳实践
-
缓存策略优化:
- 精确配置 inputs 和 outputs
- 使用环境变量隔离不同环境
- 启用远程缓存提升 CI/CD 速度
-
依赖管理:
- 合理设置 dependsOn 避免不必要等待
- 使用 ^ 和 ~ 符号精确控制依赖
- 定期清理过期缓存
-
性能优化:
- 并行执行独立任务
- 监控构建性能指标
- 分析依赖图识别瓶颈
1.6.6.2 Monorepo 架构建议
最佳实践结构:
turbo-monorepo/
├── apps/ # 应用层
│ ├── web-app/ # 主应用
│ ├── admin-panel/ # 管理面板
│ └── mobile-app/ # 移动应用
├── packages/ # 包层
│ ├── ui-components/ # UI 组件
│ ├── utils/ # 工具函数
│ ├── types/ # 类型定义
│ ├── hooks/ # 自定义 Hooks
│ └── config/ # 配置管理
├── tools/ # 工具层
│ ├── eslint-config/ # ESLint 配置
│ ├── tsconfig/ # TypeScript 配置
│ └── scripts/ # 构建脚本
├── .github/ # CI/CD 配置
├── turbo.json # Turbo 配置
├── pnpm-workspace.yaml # pnpm 配置
└── package.json # 根配置
通过这种完整的 pnpm + Turbo Monorepo 实践,你可以构建一个高性能、可扩展的前端工程体系,实现极速构建和智能缓存,大幅提升团队开发效率。
1.7 Prettier 代码格式化详解
1.7.1 Prettier 深度解析与核心概念
1.7.1.1 什么是 Prettier
Prettier 是一个固执的代码格式化工具,支持多种编程语言。它通过解析代码并重新打印,确保团队中每个人的代码风格都保持一致,从而减少代码审查中的样式讨论,提高开发效率。
Prettier 的核心理念:
// Prettier 的设计哲学
1. **固执但可配置**:有固定的格式化规则,但允许关键配置
2. **零配置启动**:开箱即用,无需复杂配置
3. **语言无关**:支持多种语言和框架
4. **自动化优先**:集成到开发工作流中
5. **团队协作友好**:消除代码风格争议
Prettier vs ESLint vs Linter 对比:
| 特性 | Prettier | ESLint | TSLint | Linter |
|---|---|---|---|---|
| 主要功能 | 代码格式化 | 代码质量检查 | TypeScript 检查 | 代码检查 |
| 修复能力 | 自动格式化 | 部分自动修复 | 部分自动修复 | 报告问题 |
| 配置复杂度 | 低 | 中等 | 中等 | 高 |
| 性能 | 快 | 中等 | 中等 | 慢 |
| 适用场景 | 格式化统一 | 代码质量 | TypeScript 专用 | 通用检查 |
1.7.1.2 Prettier 工作原理
解析-转换-输出流程:
支持的文件类型:
// Prettier 支持的语言列表
const supportedLanguages = {
// JavaScript/TypeScript
'JavaScript': ['.js', '.jsx', '.mjs', '.cjs'],
'TypeScript': ['.ts', '.tsx', '.mts', '.cts'],
// 样式文件
'CSS': ['.css', '.scss', '.sass', '.less'],
'Styled Components': ['*.js', '*.jsx', '*.ts', '*.tsx'],
// 模板语言
'HTML': ['.html', '.htm', '.xhtml'],
'Vue': ['.vue'],
'Angular': ['.component.html', '.component.ts'],
// 配置文件
'JSON': ['.json', '.jsonc', '.json5'],
'YAML': ['.yaml', '.yml'],
'TOML': ['.toml'],
'INI': ['.ini'],
// 其他语言
'Markdown': ['.md', '.mdx'],
'GraphQL': ['.graphql', '.gql'],
'Handlebars': ['.hbs', '.handlebars'],
'PHP': ['.php', '.phtml'],
'Python': ['.py'],
'Ruby': ['.rb'],
'Java': ['.java'],
'Go': ['.go'],
'Rust': ['.rs'],
'Swift': ['.swift'],
'Kotlin': ['.kt', '.kts']
}
1.7.2 Prettier 安装与配置
1.7.2.1 多种安装方式
项目级安装(推荐):
# npm 安装
npm install --save-dev prettier
# pnpm 安装
pnpm add -D prettier
# yarn 安装
yarn add -D prettier
# yarn berry 安装
yarn add prettier --dev
全局安装:
# npm 全局
npm install -g prettier
# pnpm 全局
pnpm add -g prettier
# yarn 全局
yarn global add prettier
# 验证安装
prettier --version
Docker 安装:
# Dockerfile
FROM node:18-alpine
# 安装 Prettier
RUN npm install -g prettier
# 或者作为开发依赖
COPY package*.json ./
RUN npm install --only=dev
WORKDIR /app
# 使用 Prettier
CMD ["prettier", "--write", "src/**/*.{js,ts,jsx,tsx}"]
1.7.2.2 Prettier 配置详解
prettierrc.json 基础配置:
{
// 基础格式化选项
"printWidth": 80, // 每行最大字符数
"tabWidth": 2, // 缩进宽度
"useTabs": false, // 使用空格缩进
"semi": true, // 语句末尾分号
"singleQuote": true, // 使用单引号
"quoteProps": "as-needed", // 对象属性引号
"trailingComma": "es5", // 尾随逗号
"bracketSpacing": true, // 对象括号空格
"bracketSameLine": false, // JSX 括号位置
"arrowParens": "avoid", // 箭头函数参数括号
// JSX 配置
"jsxSingleQuote": false, // JSX 使用双引号
"jsxBracketSameLine": false, // JSX 括号换行
// HTML/Vue 配置
"htmlWhitespaceSensitivity": "css", // HTML 空白敏感度
"vueIndentScriptAndStyle": false, // Vue 脚本样式缩进
// 其他配置
"endOfLine": "lf", // 行结束符
"embeddedLanguageFormatting": "auto", // 嵌入语言格式化
"insertPragma": false, // 插入 pragma
"proseWrap": "preserve", // 文本换行
"requirePragma": false, // 需要 pragma
"rangeStart": 0, // 格式化起始位置
"rangeEnd": Infinity, // 格式化结束位置
// 插件配置
"plugins": [
"prettier-plugin-tailwindcss",
"prettier-plugin-organize-imports",
"prettier-plugin-sort-json"
]
}
.prettierrc.js 动态配置:
// .prettierrc.js
const isDevelopment = process.env.NODE_ENV === 'development'
module.exports = {
printWidth: isDevelopment ? 120 : 80,
tabWidth: 2,
useTabs: false,
semi: true,
singleQuote: true,
trailingComma: isDevelopment ? 'all' : 'es5',
bracketSpacing: true,
bracketSameLine: false,
arrowParens: 'avoid',
// 文件特定配置
overrides: [
{
files: '*.json',
options: {
printWidth: 120,
tabWidth: 2
}
},
{
files: '*.md',
options: {
printWidth: 100,
proseWrap: 'always'
}
},
{
files: ['*.test.{js,ts,jsx,tsx}'],
options: {
printWidth: 120,
tabWidth: 2
}
},
{
files: '*.css',
options: {
singleQuote: false
}
},
{
files: '*.html',
options: {
printWidth: 120,
tabWidth: 2,
singleQuote: false
}
}
],
// 插件配置
plugins: [
require.resolve('prettier-plugin-tailwindcss'),
require.resolve('prettier-plugin-organize-imports')
]
}
.prettierrc.mjs ES Module 配置:
// .prettierrc.mjs
import { defineConfig } from 'prettier'
const config = defineConfig({
// 使用 TypeScript 类型检查
printWidth: 80,
tabWidth: 2,
useTabs: false,
semi: true,
singleQuote: true,
trailingComma: 'es5',
bracketSpacing: true,
arrowParens: 'avoid',
// 条件配置
overrides: [
{
files: ['**/*.component.{js,ts,jsx,tsx}'],
options: {
printWidth: 100
}
},
{
files: ['**/*.story.{js,ts,jsx,tsx}'],
options: {
printWidth: 120
}
}
]
})
export default config
1.7.2.3 忽略文件配置
.prettierignore 配置:
# 依赖和构建产物
node_modules/
dist/
build/
out/
.next/
coverage/
# 配置文件
package-lock.json
yarn.lock
pnpm-lock.yaml
turbo.json
# 自动生成文件
*.min.js
*.min.css
*.d.ts
generated/
# 日志文件
*.log
npm-debug.log*
yarn-debug.log*
pnpm-debug.log*
# 环境文件
.env.*
!.env.example
# 文档文件
CHANGELOG.md
LICENSE.md
README.md
# 测试覆盖率
coverage/
test-results/
# 临时文件
*.tmp
*.temp
.cache/
# 特定文件格式
*.svg
*.png
*.jpg
*.jpeg
*.gif
*.pdf
*.zip
*.tar.gz
# IDE 文件
.vscode/
.idea/
*.swp
*.swo
# 操作系统文件
.DS_Store
Thumbs.db
# 特定项目忽略
storybook-static/
.turbo/
1.7.3 Prettier 命令行使用详解
1.7.3.1 基础命令
# 检查格式(只检查不修改)
prettier --check "src/**/*.{js,ts,jsx,tsx}"
# 格式化文件(直接修改)
prettier --write "src/**/*.{js,ts,jsx,tsx}"
# 格式化并输出到标准输出
prettier "src/index.js" > formatted-index.js
# 检查特定文件
prettier --check file1.js file2.ts file3.jsx
# 从配置文件读取
prettier --config .prettierrc.js --write src/
# 使用特定配置
prettier --config-path ./configs/.prettierrc --write src/
1.7.3.2 高级命令选项
# 详细输出
prettier --write --verbose "src/**/*.{js,ts,jsx,tsx}"
# 显示调试信息
prettier --debug --write "src/**/*.{js,ts,jsx,tsx}"
# 指定文件列表
prettier --write --file-list files.txt
# 范围格式化
prettier --write --range-start 10 --range-end 20 src/index.js
# 忽略配置文件
prettier --write --no-config src/index.js
# 使用编辑器配置
prettier --write --editorconfig src/index.js
# 插入 pragma 标记
prettier --write --insert-pragma src/index.js
# 只格式化带 pragma 的文件
prettier --write --require-pragma src/index.js
# 处理未尾随换行符的文件
prettier --write --end-of-line crlf src/index.js
# 设置日志级别
prettier --write --log-level warn src/
1.7.3.3 文件模式匹配
# Glob 模式匹配
prettier --write "src/**/*.js" # 所有 JS 文件
prettier --write "src/**/*.{js,ts,jsx,tsx}" # 多种文件类型
prettier --write "!(test)/**/*.{js,ts}" # 排除 test 目录
prettier --write "{src,lib}/**/*.js" # 多个目录
# 使用 .gitignore 风格
prettier --write --ignore-path .gitignore "src/**/*"
# 递归处理
prettier --write --recursive src/
# 处理隐藏文件
prettier --write "src/**/.*.{js,ts}"
1.7.4 Prettier 插件生态系统
1.7.4.1 实用插件推荐
1. Tailwind CSS 插件:
# 安装
npm install --save-dev prettier-plugin-tailwindcss
pnpm add -D prettier-plugin-tailwindcss
yarn add -D prettier-plugin-tailwindcss
// prettier.config.js
module.exports = {
plugins: [require('prettier-plugin-tailwindcss')],
// Prettier 会自动格式化 Tailwind 类名
// <div className="flex items-center justify-between p-4 bg-white rounded-lg shadow-sm">
// 会变成:
// <div className="flex items-center justify-between p-4 bg-white rounded-lg shadow-sm">
}
2. 排序导入插件:
npm install --save-dev prettier-plugin-organize-imports
// prettier.config.js
module.exports = {
plugins: [require('prettier-plugin-organize-imports')],
// 自动排序导入语句
// import React from 'react';
// import { useEffect } from 'react';
// import { Button } from '@/components';
// import { formatDate } from '@/utils';
}
3. JSON/JSON5 排序插件:
npm install --save-dev prettier-plugin-sort-json
// prettier.config.js
module.exports = {
plugins: [require('prettier-plugin-sort-json')],
overrides: [
{
files: '*.json',
options: {
jsonSortOrder: {
// 自定义排序规则
name: 'first',
version: 'second',
dependencies: 'after:scripts',
devDependencies: 'after:dependencies'
}
}
}
]
}
1.7.4.2 自定义插件开发
开发简单格式化插件:
// prettier-plugin-custom.js
const { format } = require('prettier')
module.exports = {
languages: [
{
name: 'Custom Format',
parsers: ['custom-parser'],
extensions: ['.custom'],
linguistLanguageId: 632378804
}
],
parsers: {
'custom-parser': {
parse: (text) => {
// 自定义解析逻辑
return {
type: 'File',
program: text.split('\n').map(line => ({
type: 'Line',
value: line
}))
}
},
astFormat: 'custom-ast'
}
},
printers: {
'custom-ast': {
print: (path, options, print) => {
const node = path.getValue()
if (node.type === 'File') {
return node.program.map(line => line.value).join('\n')
}
return ''
}
}
},
defaultOptions: {
printWidth: 80,
tabWidth: 2
}
}
使用自定义插件:
// prettier.config.js
module.exports = {
plugins: [require('./prettier-plugin-custom')],
overrides: [
{
files: '*.custom',
options: {
printWidth: 100,
customOption: true
}
}
]
}
1.7.5 Prettier 与工具链集成
1.7.5.1 ESLint 集成
安装和配置:
# 安装配置包
npm install --save-dev eslint-config-prettier eslint-plugin-prettier
pnpm add -D eslint-config-prettier eslint-plugin-prettier
yarn add -D eslint-config-prettier eslint-plugin-prettier
ESLint 配置:
// .eslintrc.js
module.exports = {
extends: [
// 其他 ESLint 规则
'eslint:recommended',
'@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
// Prettier 集成
'prettier' // 使用 Prettier 的格式化规则
],
plugins: [
'prettier' // 启用 Prettier 插件
],
rules: [
// Prettier 规则
'prettier/prettier': 'error', // Prettier 格式错误
// 禁用与 Prettier 冲突的规则
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'react/prop-types': 'off',
'react/jsx-uses-react': 'off'
]
}
VS Code 集成配置:
// .vscode/settings.json
{
// 启用 ESLint 和 Prettier
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.formatOnType": true,
// ESLint 集成
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.organizeImports": true
},
// 文件关联
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
1.7.5.2 Git Hooks 集成
使用 Husky 和 lint-staged:
# 安装依赖
npm install --save-dev husky lint-staged
pnpm add -D husky lint-staged
yarn add -D husky lint-staged
# 初始化 Husky
npx husky install
npx husky add .husky/pre-commit "npx lint-staged"
package.json 配置:
{
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"prettier --write",
"eslint --fix",
"git add"
],
"*.{css,scss,sass,less}": [
"prettier --write",
"stylelint --fix",
"git add"
],
"*.{json,md,yml,yaml}": [
"prettier --write",
"git add"
]
}
}
使用 simple-git-hooks:
# 安装
npm install --save-dev simple-git-hooks
pnpm add -D simple-git-hooks
yarn add -D simple-git-hooks
# package.json
{
"simple-git-hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
},
"scripts": {
"prepare": "simple-git-hooks"
}
}
1.7.5.3 CI/CD 集成
GitHub Actions 配置:
# .github/workflows/prettier.yml
name: Prettier Check
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
prettier:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
# 获取完整历史用于正确的文件比较
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Check Prettier formatting
run: |
# 检查未格式化的文件
npx prettier --check --log-level warn "src/**/*.{js,jsx,ts,tsx,css,scss,json,md}"
- name: Format changed files
if: github.event_name == 'pull_request'
run: |
# 只格式化变更的文件
CHANGED_FILES=$(git diff --name-only origin/main...HEAD | grep -E '\.(js|jsx|ts|tsx|css|scss|json|md)$' || true)
if [ -n "$CHANGED_FILES" ]; then
echo "$CHANGED_FILES" | xargs npx prettier --write
fi
- name: Check for changes
if: github.event_name == 'pull_request'
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "Prettier formatting required for changed files:"
git diff --name-only
exit 1
fi
Jenkins 配置:
// Jenkinsfile
pipeline {
agent any
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Install Dependencies') {
steps {
sh 'npm ci'
}
}
stage('Check Prettier') {
steps {
sh 'npx prettier --check "src/**/*.{js,jsx,ts,tsx,css,scss,json,md}" || exit 1'
}
}
stage('Format Files') {
when {
anyOf {
branch 'main'
branch 'develop'
}
}
steps {
script {
try {
sh 'npx prettier --write "src/**/*.{js,jsx,ts,tsx,css,scss,json,md}"'
// 检查是否有文件被修改
def hasChanges = sh(
script: 'git diff --quiet || echo "changed"',
returnStdout: true
).trim()
if (hasChanges == 'changed') {
sh 'git add .'
sh 'git commit -m "ci: format code with prettier"'
sh 'git push'
}
} catch (e) {
echo "Formatting completed"
}
}
}
}
}
}
1.7.6 高级配置与最佳实践
1.7.6.1 团队配置策略
共享配置包:
// packages/prettier-config/index.js
module.exports = {
printWidth: 80,
tabWidth: 2,
useTabs: false,
semi: true,
singleQuote: true,
trailingComma: 'es5',
bracketSpacing: true,
arrowParens: 'avoid',
overrides: [
{
files: '*.test.{js,ts,jsx,tsx}',
options: {
printWidth: 120
}
},
{
files: '*.config.{js,ts}',
options: {
singleQuote: false
}
}
],
plugins: [
require('prettier-plugin-tailwindcss'),
require('prettier-plugin-organize-imports')
]
}
// 项目中使用
const config = require('@company/prettier-config')
module.exports = config
多环境配置:
// prettier.config.js
const { execSync } = require('child_process')
// 检测环境
const isCI = process.env.CI === 'true'
const isProduction = process.env.NODE_ENV === 'production'
const isDevelopment = !isCI && !isProduction
// Git 分支检测
const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim()
module.exports = {
// 基础配置
printWidth: isDevelopment ? 120 : 80,
tabWidth: 2,
useTabs: false,
semi: true,
singleQuote: true,
trailingComma: isProduction ? 'none' : 'es5',
bracketSpacing: true,
arrowParens: 'avoid',
// 分支特定配置
overrides: [
{
files: '*.{js,ts,jsx,tsx}',
options: branch === 'main' ? {
printWidth: 80,
trailingComma: 'es5'
} : {
printWidth: 120,
trailingComma: 'all'
}
}
]
}
1.7.6.2 性能优化配置
缓存配置:
// prettier.config.js
module.exports = {
// 基础配置
printWidth: 80,
tabWidth: 2,
useTabs: false,
semi: true,
singleQuote: true,
// 性能优化选项
overrides: [
{
files: ['**/*.min.js', '**/*.bundle.js'],
options: {
// 跳过压缩文件
parser: 'babel',
requirePragma: true
}
},
{
files: ['**/vendor/**/*', '**/node_modules/**/*'],
options: {
// 忽略第三方文件
requirePragma: true
}
}
]
}
增量格式化脚本:
#!/bin/bash
# scripts/format-changed.sh
# 获取变更的文件
CHANGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|jsx|ts|tsx|css|scss|json|md)$')
if [ -n "$CHANGED_FILES" ]; then
echo "Formatting changed files:"
echo "$CHANGED_FILES"
# 格式化文件
echo "$CHANGED_FILES" | xargs npx prettier --write
# 添加回暂存区
echo "$CHANGED_FILES" | xargs git add
echo "Files formatted and added to staging area"
else
echo "No files to format"
fi
并行格式化脚本:
// scripts/parallel-format.js
const { execSync } = require('child_process')
const { readFileSync, writeFileSync } = require('fs')
const { join } = require('path')
const os = require('os')
const CPU_COUNT = os.cpus().length
class ParallelFormatter {
constructor() {
this.fileQueue = []
this.processedFiles = 0
this.errors = []
}
async getFileList(pattern) {
try {
const files = execSync(`npx prettier --list-different "${pattern}"`, {
encoding: 'utf8'
})
return files.trim().split('\n').filter(Boolean)
} catch (error) {
return []
}
}
async formatFiles(files, workers = CPU_COUNT) {
const chunkSize = Math.ceil(files.length / workers)
const chunks = []
for (let i = 0; i < files.length; i += chunkSize) {
chunks.push(files.slice(i, i + chunkSize))
}
const promises = chunks.map(chunk => this.formatChunk(chunk))
await Promise.all(promises)
}
async formatChunk(files) {
if (files.length === 0) return
try {
const fileList = files.join(' ')
execSync(`npx prettier --write ${fileList}`, { stdio: 'inherit' })
this.processedFiles += files.length
} catch (error) {
this.errors.push({ files, error: error.message })
}
}
async format(pattern) {
const files = await this.getFileList(pattern)
if (files.length === 0) {
console.log('No files need formatting')
return
}
console.log(`Formatting ${files.length} files with ${CPU_COUNT} workers...`)
const startTime = Date.now()
await this.formatFiles(files)
const endTime = Date.now()
console.log(`Formatted ${this.processedFiles} files in ${endTime - startTime}ms`)
if (this.errors.length > 0) {
console.error('Errors occurred during formatting:')
this.errors.forEach(({ files, error }) => {
console.error(`Files: ${files.join(', ')}`)
console.error(`Error: ${error}`)
})
process.exit(1)
}
}
}
// 使用示例
if (require.main === module) {
const formatter = new ParallelFormatter()
const pattern = process.argv[2] || 'src/**/*.{js,jsx,ts,tsx,css,scss,json,md}'
formatter.format(pattern)
}
1.7.7 故障排除与调试
1.7.7.1 常见问题解决
问题1:Prettier 与 ESLint 冲突
// 解决方案:配置冲突规则覆盖
// .eslintrc.js
module.exports = {
extends: [
'eslint:recommended',
'@typescript-eslint/recommended',
'prettier', // 必须放在最后
],
rules: {
'prettier/prettier': 'error',
// 禁用与 prettier 冲突的规则
'@typescript-eslint/no-unused-vars': 'off',
'no-unused-vars': 'off',
'react/prop-types': 'off'
}
}
问题2:格式化不一致
// 解决方案:统一配置文件
// 项目根目录创建统一的 prettier.config.js
module.exports = {
// 明确指定所有选项,避免隐式默认值
printWidth: 80,
tabWidth: 2,
useTabs: false,
semi: true,
singleQuote: true,
trailingComma: 'es5',
bracketSpacing: true,
arrowParens: 'avoid',
endOfLine: 'lf'
}
问题3:性能问题
# 解决方案:优化文件过滤
# 1. 使用 .prettierignore 排除不需要的文件
# 2. 使用文件列表而非 Glob 模式
npx prettier --write $(git ls-files '*.js' '*.ts' '*.jsx' '*.tsx')
# 3. 并行处理
npx prettier --write --list-different | xargs -P $(nproc) prettier --write
1.7.7.2 调试工具和技巧
调试配置:
// debug-prettier.js
const prettier = require('prettier')
const { readFileSync } = require('fs')
async function debugFormat(filePath) {
try {
const source = readFileSync(filePath, 'utf8')
// 获取解析信息
const options = prettier.resolveConfig.sync(filePath)
const fileInfo = prettier.getFileInfo.sync(filePath)
console.log(`File: ${filePath}`)
console.log(`Parser: ${fileInfo.inferredParser}`)
console.log(`Options:`, JSON.stringify(options, null, 2))
// 格式化
const formatted = prettier.format(source, {
...options,
filePath
})
console.log('\n--- Original ---')
console.log(source)
console.log('\n--- Formatted ---')
console.log(formatted)
// 检查是否有差异
if (source !== formatted) {
console.log('\n--- Differences ---')
console.log('File needs formatting')
} else {
console.log('\n--- Result ---')
console.log('File is already formatted')
}
} catch (error) {
console.error(`Error formatting ${filePath}:`, error.message)
}
}
// 使用
if (require.main === module) {
const filePath = process.argv[2]
if (!filePath) {
console.error('Please provide a file path')
process.exit(1)
}
debugFormat(filePath)
}
配置验证脚本:
// validate-prettier.js
const prettier = require('prettier')
const { readdirSync, statSync } = require('fs')
const { join, extname } = require('path')
class PrettierValidator {
constructor(rootDir = 'src') {
this.rootDir = rootDir
this.errors = []
this.warnings = []
}
walkDir(dir) {
const files = []
for (const file of readdirSync(dir)) {
const fullPath = join(dir, file)
const stat = statSync(fullPath)
if (stat.isDirectory()) {
files.push(...this.walkDir(fullPath))
} else {
files.push(fullPath)
}
}
return files
}
async validateFiles() {
const files = this.walkDir(this.rootDir)
const supportedExts = ['.js', '.jsx', '.ts', '.tsx', '.css', '.scss', '.json', '.md']
const targetFiles = files.filter(file =>
supportedExts.includes(extname(file))
)
console.log(`Validating ${targetFiles.length} files...`)
for (const file of targetFiles) {
try {
await this.validateFile(file)
} catch (error) {
this.errors.push({ file, error: error.message })
}
}
this.report()
}
async validateFile(filePath) {
const options = prettier.resolveConfig.sync(filePath)
const fileInfo = prettier.getFileInfo.sync(filePath)
if (!fileInfo.inferredParser) {
this.warnings.push({
file: filePath,
message: 'No parser inferred'
})
return
}
// 尝试解析
const source = require('fs').readFileSync(filePath, 'utf8')
try {
prettier.format(source, { ...options, filePath })
} catch (error) {
this.errors.push({
file: filePath,
error: error.message
})
}
}
report() {
console.log('\n=== Prettier Validation Report ===')
if (this.errors.length > 0) {
console.log(`\n❌ ${this.errors.length} errors found:`)
this.errors.forEach(({ file, error }) => {
console.log(` ${file}: ${error}`)
})
}
if (this.warnings.length > 0) {
console.log(`\n⚠️ ${this.warnings.length} warnings:`)
this.warnings.forEach(({ file, message }) => {
console.log(` ${file}: ${message}`)
})
}
if (this.errors.length === 0 && this.warnings.length === 0) {
console.log('\n✅ All files are valid!')
}
process.exit(this.errors.length > 0 ? 1 : 0)
}
}
// 使用
if (require.main === module) {
const validator = new PrettierValidator()
validator.validateFiles()
}
1.7.8 总结与最佳实践
1.7.8.1 Prettier 最佳实践总结
-
配置管理:
- 使用统一的配置文件
- 版本控制配置文件
- 定期更新 Prettier 版本
-
团队协作:
- 集成到 CI/CD 流程
- 使用 Git Hooks 自动格式化
- 提供统一的编辑器配置
-
性能优化:
- 合理使用 .prettierignore
- 增量格式化
- 并行处理大型项目
-
工具链集成:
- 与 ESLint 协同工作
- 与构建工具集成
- 与 IDE 插件配合
-
错误处理:
- 定期验证配置
- 监控格式化错误
- 提供调试工具
通过这种全面的 Prettier 配置和使用指南,你可以实现代码风格统一,提升团队开发效率,减少代码审查中的格式争议。
1.8 ESLint 代码质量检查详解
ESLint 是 JavaScript/TypeScript 代码质量检查工具,帮助开发者发现和修复代码问题,维持代码质量标准。
1.8.1 ESLint 基础概念与原理
1.8.1.1 ESLint 工作机制
静态代码分析流程:
ESLint 核心组件:
// 1. Parser(解析器)
const parser = {
// 将代码转换为 AST
parse(code, options) {
return esprima.parse(code, options)
}
}
// 2. Rules(规则)
const rules = {
'no-unused-vars': {
meta: {
type: 'problem',
docs: { description: '禁止未使用的变量' }
},
create(context) {
return {
VariableDeclaration(node) {
// 检查未使用的变量
}
}
}
}
}
// 3. Processors(处理器)
const processors = {
'.md': {
preprocess: (text, filename) => extractCodeBlocks(text),
postprocess: (messages, filename) => messages.flat()
}
}
1.8.1.2 ESLint 配置层次结构
配置优先级(从高到低):
1. .eslintrc.js # 项目级配置
2. .eslintrc.yaml # YAML 配置
3. .eslintrc.yml # YAML 配置
4. .eslintrc.json # JSON 配置
5. package.json eslintConfig # package.json 中配置
6. .eslintrc # 旧版配置
7. 默认配置
1.8.2 React 项目中的 ESLint 配置
1.8.2.1 基础 React ESLint 配置
安装 ESLint 及相关插件:
# 核心依赖
npm install eslint @eslint/js --save-dev
# TypeScript 支持
npm install @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev
# React 支持
npm install eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-jsx-a11y --save-dev
# 测试相关
npm install eslint-plugin-jest eslint-plugin-testing-library --save-dev
# 工具集成
npm install eslint-import-resolver-typescript eslint-plugin-import --save-dev
基础 .eslintrc.js 配置:
module.exports = {
// 环境配置
env: {
browser: true, // 浏览器环境
es2022: true, // ES2022 语法
node: true, // Node.js 环境
jest: true, // Jest 测试环境
},
// 解析器配置
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true, // JSX 支持
},
ecmaVersion: 'latest', // 最新 ECMAScript 版本
sourceType: 'module', // ES Module
project: './tsconfig.json', // TypeScript 项目配置
},
// 插件配置
plugins: [
'@typescript-eslint',
'react',
'react-hooks',
'jsx-a11y',
'import',
'jest',
],
// 扩展配置
extends: [
'eslint:recommended', // ESLint 推荐规则
'@typescript-eslint/recommended', // TypeScript 推荐规则
'@typescript-eslint/recommended-requiring-type-checking', // 类型检查规则
'plugin:react/recommended', // React 推荐规则
'plugin:react-hooks/recommended', // React Hooks 规则
'plugin:jsx-a11y/recommended', // 无障碍访问规则
'plugin:import/recommended', // 导入规则
'plugin:import/typescript', # TypeScript 导入规则
'plugin:jest/recommended', // Jest 测试规则
],
// React 特定配置
settings: {
react: {
version: 'detect', // 自动检测 React 版本
},
'import/resolver': {
typescript: {
alwaysTryTypes: true,
project: './tsconfig.json',
},
},
},
// 自定义规则
rules: {
// TypeScript 规则
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/explicit-function-return-type': 'warn',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/prefer-const': 'error',
'@typescript-eslint/no-non-null-assertion': 'warn',
// React 规则
'react/prop-types': 'off', // 关闭 PropTypes 检查
'react/react-in-jsx-scope': 'off', // React 17+ 不需要导入 React
'react/jsx-uses-react': 'off', // React 17+ 不需要导入 React
'react/jsx-uses-vars': 'error', // 检查 JSX 中使用的变量
'react/jsx-no-bind': 'warn', // 警告使用 bind
'react/jsx-key': 'error', // 要求 key 属性
'react/jsx-no-duplicate-props': 'error', // 禁止重复属性
'react/no-children-prop': 'error', // 禁止使用 children prop
'react/no-danger-with-children': 'error', // 禁止 dangerouslySetInnerHTML
// React Hooks 规则
'react-hooks/rules-of-hooks': 'error', // Hooks 规则
'react-hooks/exhaustive-deps': 'warn', // 依赖项检查
// 导入规则
'import/order': [
'error',
{
groups: [
'builtin', // Node.js 内置模块
'external', // 外部依赖
'internal', // 内部模块
'parent', // 父目录
'sibling', // 同级目录
'index', // index 文件
'object', // 对象属性
'type', // 类型导入
],
'newlines-between': 'always',
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
},
],
'import/no-duplicates': 'error',
'import/no-unresolved': 'error',
'import/named': 'error',
// 代码质量规则
'no-console': ['warn', { allow: ['warn', 'error'] }],
'no-debugger': 'error',
'no-alert': 'error',
'no-eval': 'error',
'no-implied-eval': 'error',
'no-new-func': 'error',
'prefer-const': 'error',
'no-var': 'error',
'object-shorthand': 'error',
'prefer-arrow-callback': 'error',
'prefer-template': 'error',
'template-curly-spacing': ['error', 'never'],
'arrow-spacing': 'error',
'comma-dangle': ['error', 'always-multiline'],
'semi': ['error', 'always'],
'quotes': ['error', 'single', { avoidEscape: true }],
// 无障碍访问规则
'jsx-a11y/anchor-is-valid': 'warn',
'jsx-a11y/alt-text': 'error',
'jsx-a11y/aria-role': 'error',
'jsx-a11y/click-events-have-key-events': 'warn',
'jsx-a11y/no-static-element-interactions': 'warn',
},
// 文件全局变量
globals: {
React: 'readonly', // React 全局只读
JSX: 'readonly', # JSX 全局只读
},
// 忽略模式
ignorePatterns: [
'dist/',
'build/',
'coverage/',
'*.config.js',
'*.config.ts',
'node_modules/',
'.next/',
'.nuxt/',
'.storybook/',
],
};
1.8.2.2 TypeScript ESLint 配置
tsconfig.json 配置优化:
{
"compilerOptions": {
"target": "ES2022",
"lib": ["DOM", "DOM.Iterable", "ES6"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "ESNext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
// ESLint 相关配置
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@/components/*": ["src/components/*"],
"@/hooks/*": ["src/hooks/*"],
"@/utils/*": ["src/utils/*"],
"@/types/*": ["src/types/*"]
},
// 类型检查增强
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true
},
"include": [
"src/**/*",
"tests/**/*"
],
"exclude": [
"node_modules",
"dist",
"build"
]
}
ESLint TypeScript 特定规则:
// .eslintrc.js 中的 TypeScript 规则配置
rules: {
// 类型检查规则
'@typescript-eslint/no-floating-promises': 'error', // 未处理的 Promise
'@typescript-eslint/await-thenable': 'error', // await 非 Promise
'@typescript-eslint/no-misused-promises': 'error', // Promise 误用
'@typescript-eslint/require-await': 'error', // async 函数无 await
'@typescript-eslint/no-for-in-array': 'error', // for-in 遍历数组
'@typescript-eslint/no-unnecessary-type-assertion': 'error', // 不必要的类型断言
'@typescript-eslint/prefer-nullish-coalescing': 'error', // 使用 ?? 运算符
'@typescript-eslint/prefer-optional-chain': 'error', // 使用可选链
// 代码风格规则
'@typescript-eslint/array-type': ['error', { default: 'array' }],
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
'@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
'@typescript-eslint/member-ordering': 'error',
'@typescript-eslint/naming-convention': [
'error',
{
selector: 'variable',
format: ['camelCase', 'UPPER_CASE'],
leadingUnderscore: 'allow',
},
{
selector: 'typeLike',
format: ['PascalCase'],
},
{
selector: 'interface',
format: ['PascalCase'],
prefix: ['I'],
},
],
}
1.8.3 高级 ESLint 配置
1.8.3.1 Monorepo ESLint 配置
根目录 ESLint 配置:
// .eslintrc.js
module.exports = {
root: true,
env: {
browser: true,
es2022: true,
node: true,
},
extends: [
'eslint:recommended',
'@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: { jsx: true },
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json', './packages/*/tsconfig.json'],
},
plugins: ['@typescript-eslint', 'react', 'react-hooks'],
settings: {
react: { version: 'detect' },
},
rules: {
// 共享规则
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'error',
'react/prop-types': 'off',
'react/react-in-jsx-scope': 'off',
},
overrides: [
{
files: ['*.js'],
rules: {
'@typescript-eslint/no-var-requires': 'off',
},
},
{
files: ['**/__tests__/**/*', '**/*.test.*', '**/*.spec.*'],
env: { jest: true },
extends: ['plugin:jest/recommended'],
plugins: ['jest'],
},
],
};
包级别配置继承:
// packages/ui-components/.eslintrc.js
const rootConfig = require('../../.eslintrc.js');
module.exports = {
...rootConfig,
extends: [
...rootConfig.extends,
'plugin:jsx-a11y/recommended',
],
rules: {
...rootConfig.rules,
// 组件库特定规则
'@typescript-eslint/explicit-function-return-type': 'error',
'react/prop-types': 'error', // 组件库要求 PropTypes
'react/default-props-match-prop-types': 'error',
'react/require-default-props': 'error',
},
};
1.8.3.2 自定义 ESLint 规则
创建自定义规则:
// eslint-rules/no-direct-state-mutation.js
module.exports = {
meta: {
type: 'problem',
docs: {
description: '禁止直接修改 state',
category: 'Best Practices',
recommended: true,
},
fixable: null,
schema: [],
messages: {
noDirectMutation: '直接修改 state 是不允许的,请使用 setState。',
},
},
create(context) {
return {
AssignmentExpression(node) {
// 检查 this.state.xxx = yyy 形式的赋值
if (
node.left.type === 'MemberExpression' &&
node.left.object.type === 'MemberExpression' &&
node.left.object.object.type === 'ThisExpression' &&
node.left.object.property.name === 'state'
) {
context.report({
node,
messageId: 'noDirectMutation',
});
}
},
};
},
};
规则注册使用:
// .eslintrc.js
const noDirectStateMutation = require('./eslint-rules/no-direct-state-mutation');
module.exports = {
// ... 其他配置
rules: {
'no-direct-state-mutation': 'error', // 使用自定义规则
},
};
1.8.4 ESLint 工具集成
1.8.4.1 VSCode 集成配置
.vscode/settings.json 配置:
{
"eslint.enable": true,
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"eslint.run": "onType",
"eslint.format.enable": true,
"eslint.codeAction.showDocumentation": {
"enable": true
},
"eslint.packageManager": "pnpm",
"eslint.workingDirectories": [
".",
"packages/*",
"apps/*"
],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.organizeImports": true
},
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
推荐 VSCode 扩展:
// .vscode/extensions.json
{
"recommendations": [
"esbenp.prettier-vscode", // Prettier
"dbaeumer.vscode-eslint", // ESLint
"bradlc.vscode-tailwindcss", // Tailwind CSS
"ms-vscode.vscode-typescript-next", // TypeScript
"formulahendry.auto-rename-tag", // 自动重命名标签
"christian-kohler.path-intellisense", // 路径智能提示
"ms-vscode.vscode-json", // JSON 支持
"yzhang.markdown-all-in-one", // Markdown 支持
"ms-vscode.test-adapter-converter" // 测试适配器
]
}
1.8.4.2 Git Hooks 集成
使用 Husky + lint-staged:
# 安装依赖
npm install husky lint-staged --save-dev
npx husky install
package.json 配置:
{
"scripts": {
"lint": "eslint . --ext .ts,.tsx,.js,.jsx",
"lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx --fix",
"lint:check": "eslint . --ext .ts,.tsx,.js,.jsx --max-warnings 0",
"prepare": "husky install"
},
"lint-staged": {
"*.{ts,tsx,js,jsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md,yml,yaml}": [
"prettier --write"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}
}
Git Hooks 配置:
# .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# 运行 lint-staged
npx lint-staged
# 运行类型检查
npm run type-check
# 运行测试
npm run test:run
1.8.5 ESLint 性能优化
1.8.5.1 缓存配置
ESLint 缓存设置:
// .eslintrc.js
module.exports = {
// ... 其他配置
cache: true,
cacheLocation: '.eslintcache',
cacheStrategy: 'content',
};
package.json 脚本优化:
{
"scripts": {
"lint": "eslint . --cache --cache-location .eslintcache --max-warnings 0",
"lint:fix": "eslint . --cache --cache-location .eslintcache --fix",
"lint:changed": "eslint --cache --cache-location .eslintcache $(git diff --name-only --diff-filter=ACMRTUXB HEAD~1 | grep -E '\\.(ts|tsx|js|jsx)$' | xargs)"
}
}
1.8.5.2 并行处理优化
使用 ESLint 并行处理:
# 安装并行处理工具
npm install @rushstack/eslint-batch --save-dev
配置并行执行:
// eslint.config.js 或 scripts/lint.js
const { ESLint } = require('eslint');
const { cpus } = require('os');
async function lint() {
const eslint = new ESLint({
cache: true,
cacheLocation: '.eslintcache',
maxWarnings: 0,
concurrency: cpus().length, // 使用所有 CPU 核心
});
const results = await eslint.lintFiles(['src/**/*.{ts,tsx,js,jsx}']);
// 处理结果...
}
1.8.6 ESLint 与其他工具集成
1.8.6.1 与 Prettier 集成
解决冲突规则:
# 安装 Prettier 集成包
npm install eslint-config-prettier eslint-plugin-prettier --save-dev
集成配置:
// .eslintrc.js
module.exports = {
extends: [
// ... 其他扩展
'prettier', // 必须放在最后,覆盖之前的格式化规则
],
plugins: [
// ... 其他插件
'prettier',
],
rules: {
'prettier/prettier': 'error', // Prettier 规则作为 ESLint 错误
},
};
.prettierrc.js 配置:
// .prettierrc.js
module.exports = {
semi: true,
trailingComma: 'es5',
singleQuote: true,
printWidth: 80,
tabWidth: 2,
useTabs: false,
bracketSpacing: true,
arrowParens: 'avoid',
endOfLine: 'lf',
// 与 ESLint 保持一致的配置
};
1.8.6.2 与 TypeScript 集成优化
类型性能优化:
// .eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: true, // 启用类型检查
tsconfigRootDir: __dirname,
projectFolderIgnoreList: ['/dist', '/node_modules'],
},
// 对于大型项目,可以禁用部分类型检查规则
rules: {
'@typescript-eslint/no-floating-promises': 'off', // 在大型项目中可能影响性能
'@typescript-eslint/no-misused-promises': 'off',
'@typescript-eslint/await-thenable': 'off',
},
overrides: [
{
files: ['*.test.*', '*.spec.*'],
rules: {
'@typescript-eslint/no-explicit-any': 'off', // 测试文件允许 any
},
},
],
};
1.8.7 团队协作与规范
1.8.7.1 代码质量门禁
CI/CD 集成:
# .github/workflows/lint.yml
name: Code Quality
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- 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 ESLint
run: pnpm lint
- name: Run type checking
run: pnpm type-check
- name: Upload ESLint results
if: failure()
uses: actions/upload-artifact@v3
with:
name: eslint-results
path: eslint-report.json
GitHub Actions 报告:
# 生成 ESLint 报告
npx eslint . --ext .ts,.tsx --format json --output-file eslint-report.json
# 检查 CI 环境
if [ "$CI" = "true" ]; then
npx eslint . --ext .ts,.tsx --format github --max-warnings 0
fi
1.8.7.2 规范文档与培训
团队规范文档结构:
docs/
├── coding-standards/
│ ├── eslint-rules.md # ESLint 规则说明
│ ├── code-style.md # 代码风格指南
│ ├── react-best-practices.md # React 最佳实践
│ └── troubleshooting.md # 常见问题解决
├── setup/
│ ├── environment-setup.md # 环境搭建
│ └── ide-configuration.md # IDE 配置
└── guides/
├── contributing.md # 贡献指南
└── review-guidelines.md # 代码审查指南
自动化检查工具:
// scripts/check-eslint-config.js
const fs = require('fs');
const path = require('path');
function checkESLintConfig() {
const configPath = path.join(process.cwd(), '.eslintrc.js');
if (!fs.existsSync(configPath)) {
console.error('❌ .eslintrc.js not found');
process.exit(1);
}
const config = require(configPath);
// 检查必需的扩展
const requiredExtends = [
'eslint:recommended',
'@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
];
requiredExtends.forEach(ext => {
if (!config.extends.includes(ext)) {
console.error(`❌ Missing required extension: ${ext}`);
process.exit(1);
}
});
console.log('✅ ESLint configuration is valid');
}
checkESLintConfig();
1.8.8 常见问题与解决方案
1.8.8.1 性能问题
问题诊断:
# 检查 ESLint 性能
TIMING=1 npx eslint . --ext .ts,.tsx
# 使用 cache 优化
npx eslint . --ext .ts,.tsx --cache --cache-location .eslintcache
# 排除不必要的文件
npx eslint . --ext .ts,.tsx --ignore-pattern dist/ --ignore-pattern build/
解决方案:
// .eslintignore
dist/
build/
coverage/
*.config.js
*.config.ts
node_modules/
.next/
.storybook/
public/
# 性能优化配置
.cache/
.turbo/
1.8.8.2 规则冲突解决
常见冲突:
// 解决 Prettier 与 ESLint 冲突
module.exports = {
extends: [
'eslint:recommended',
'@typescript-eslint/recommended',
'plugin:react/recommended',
'prettier', // 必须最后加载
],
rules: {
// 禁用与 Prettier 冲突的规则
'max-len': 'off',
'indent': 'off',
'quotes': 'off',
'comma-dangle': 'off',
},
};
自定义规则示例:
// 允许 console.warn 和 console.error
'no-console': ['error', { allow: ['warn', 'error'] }],
// 允许特定的 any 类型
'@typescript-eslint/no-explicit-any': ['error', {
fixToUnknown: false,
ignoreRestArgs: true
}],
// 自定义导入分组规则
'import/order': [
'error',
{
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
'newlines-between': 'always',
alphabetize: { order: 'asc', caseInsensitive: true },
pathGroups: [
{
pattern: 'react',
group: 'external',
position: 'before',
},
{
pattern: '@/**',
group: 'internal',
},
],
pathGroupsExcludedImportTypes: ['react'],
},
],
通过这种全面的 ESLint 配置和使用指南,你可以建立严格的代码质量检查体系,提升团队代码质量,减少运行时错误,并为项目提供持续的代码质量保障。

浙公网安备 33010602011771号