723 lines
16 KiB
Markdown
723 lines
16 KiB
Markdown
---
|
|
tags: [DEV, VITE, performance, optimization]
|
|
create time: 2026-04-18
|
|
---
|
|
|
|
# Vite 优化实践
|
|
|
|
## 性能优化总览
|
|
|
|
```mermaid
|
|
graph TD
|
|
A[Vite 性能优化] --> B[开发环境]
|
|
A --> C[生产构建]
|
|
A --> D[运行时优化]
|
|
|
|
B --> B1[依赖预构建]
|
|
B --> B2[智能缓存]
|
|
B --> B3[热更新优化]
|
|
|
|
C --> C1[代码分割]
|
|
C --> C2[资源优化]
|
|
C --> C3[构建配置]
|
|
|
|
D --> D1[按需加载]
|
|
D --> D2[缓存策略]
|
|
D --> D3[监控分析]
|
|
```
|
|
|
|
## 开发环境优化
|
|
|
|
### 依赖预构建优化
|
|
|
|
#### 智能依赖识别
|
|
```typescript
|
|
import { defineConfig } from 'vite'
|
|
|
|
export default defineConfig({
|
|
optimizeDeps: {
|
|
// 手动指定需要预构建的依赖
|
|
include: [
|
|
'react',
|
|
'react-dom',
|
|
'antd',
|
|
'lodash'
|
|
],
|
|
// 排除不需要预构建的模块
|
|
exclude: [
|
|
// 已是 ESM 格式的包
|
|
'vue',
|
|
// 开发时频繁修改的包
|
|
'./src/common/*'
|
|
],
|
|
// 强制重新构建
|
|
force: process.env.NODE_ENV === 'development'
|
|
}
|
|
})
|
|
```
|
|
|
|
#### 预构建性能调优
|
|
```typescript
|
|
export default defineConfig({
|
|
optimizeDeps: {
|
|
// esbuild 配置
|
|
esbuildOptions: {
|
|
target: 'es2020',
|
|
// 保持类名和函数名,便于调试
|
|
keepNames: true,
|
|
// 启用打包内联
|
|
bundle: true,
|
|
// 外部化某些依赖
|
|
external: ['some-large-library']
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
### 服务器配置优化
|
|
|
|
#### 高效的代理配置
|
|
```typescript
|
|
export default defineConfig({
|
|
server: {
|
|
proxy: {
|
|
// API 代理
|
|
'/api': {
|
|
target: 'http://localhost:8080',
|
|
changeOrigin: true,
|
|
// 路径重写
|
|
rewrite: (path) => path.replace(/^\/api/, ''),
|
|
// 超时设置
|
|
timeout: 30000,
|
|
// 代理错误处理
|
|
configure: (proxy, _options) => {
|
|
proxy.on('error', (err, _req, res) => {
|
|
res.end(JSON.stringify({ error: err.message }))
|
|
})
|
|
}
|
|
},
|
|
// 静态资源代理
|
|
'/static': {
|
|
target: 'http://localhost:9000',
|
|
changeOrigin: true
|
|
}
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
#### 开发服务器性能
|
|
```typescript
|
|
export default defineConfig({
|
|
server: {
|
|
// 禁用自动浏览器打开
|
|
open: false,
|
|
|
|
// 严格的端口模式
|
|
strictPort: false,
|
|
|
|
// 仅监听 localhost
|
|
host: '127.0.0.1',
|
|
|
|
// HMR 配置
|
|
hmr: {
|
|
// 覆盖层配置
|
|
overlay: {
|
|
runtimeErrors: false, // 不显示运行时错误
|
|
errors: true, // 显示编译错误
|
|
warnings: false // 不显示警告
|
|
}
|
|
},
|
|
|
|
// 文件监听优化
|
|
watch: {
|
|
usePolling: false, // 禁用轮询(默认)
|
|
interval: 100, // 轮询间隔
|
|
ignored: [
|
|
'**/node_modules/**', // 忽略 node_modules
|
|
'**/.git/**', // 忽略 .git 目录
|
|
'**/dist/**', // 忽略构建输出
|
|
'coverage/**' // 忽略测试覆盖率
|
|
]
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
### 缓存策略
|
|
|
|
#### 依赖缓存优化
|
|
```typescript
|
|
export default defineConfig({
|
|
optimizeDeps: {
|
|
// 缓存目录
|
|
cacheDir: 'node_modules/.vite',
|
|
|
|
// 锁文件
|
|
lockfile: true,
|
|
|
|
// 禁用缓存调试
|
|
// force: true
|
|
}
|
|
})
|
|
|
|
// 清理依赖缓存
|
|
// npm run dev -- --force
|
|
```
|
|
|
|
#### 模块缓存配置
|
|
```typescript
|
|
export default defineConfig({
|
|
build: {
|
|
// 模块预加载
|
|
modulePreload: {
|
|
polyfill: false, // 禁用预加载 polyfill
|
|
resolveDependencies: false // 禁用依赖解析
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
## 生产构建优化
|
|
|
|
### 代码分割策略
|
|
|
|
#### 智能代码分割
|
|
```typescript
|
|
export default defineConfig({
|
|
build: {
|
|
rollupOptions: {
|
|
output: {
|
|
// 手动代码分割配置
|
|
manualChunks: (id) => {
|
|
// 节点模块分割
|
|
if (id.includes('node_modules')) {
|
|
if (id.includes('react')) {
|
|
return 'vendor-react'
|
|
}
|
|
if (id.includes('antd')) {
|
|
return 'vendor-ui'
|
|
}
|
|
if (id.includes('lodash') || id.includes('dayjs')) {
|
|
return 'vendor-utils'
|
|
}
|
|
return 'vendor-other'
|
|
}
|
|
|
|
// 应用代码分割
|
|
if (id.includes('pages/')) {
|
|
const pageName = id.split('/pages/')[1].split('/')[0]
|
|
return `pages/${pageName}`
|
|
}
|
|
|
|
// 组件分割
|
|
if (id.includes('components/')) {
|
|
const componentName = id.split('/components/')[1].split('/')[0]
|
|
return `components/${componentName}`
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
#### 按路由懒加载
|
|
```typescript
|
|
// React Router 懒加载示例
|
|
import { lazy, Suspense } from 'react'
|
|
|
|
// 路由组件懒加载
|
|
const HomePage = lazy(() => import(/* webpackChunkName: "home" */ '@/pages/Home'))
|
|
const AboutPage = lazy(() => import(/* webpackChunkName: "about" */ '@/pages/About'))
|
|
const DashboardPage = lazy(() => import(/* webpackChunkName: "dashboard" */ '@/pages/Dashboard'))
|
|
|
|
// 使用 Suspense 包裹
|
|
function App() {
|
|
return (
|
|
<Suspense fallback={<div>Loading...</div>}>
|
|
<Routes>
|
|
<Route path="/" element={<HomePage />} />
|
|
<Route path="/about" element={<AboutPage />} />
|
|
<Route path="/dashboard" element={<DashboardPage />} />
|
|
</Routes>
|
|
</Suspense>
|
|
)
|
|
}
|
|
```
|
|
|
|
### 资源优化
|
|
|
|
#### CSS 代码分割
|
|
```typescript
|
|
export default defineConfig({
|
|
build: {
|
|
cssCodeSplit: true, // 启用 CSS 代码分割
|
|
cssTarget: 'chrome80', // CSS 目标浏览器
|
|
cssMinify: 'lightningcss' // CSS 压缩器
|
|
},
|
|
css: {
|
|
devSourcemap: false, // 开发环境不生成 Source Map
|
|
|
|
// PostCSS 配置
|
|
postcss: './postcss.config.js',
|
|
|
|
preprocessorOptions: {
|
|
scss: {
|
|
api: 'modern-compiler' // 使用现代编译器
|
|
}
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
#### 静态资源优化
|
|
```typescript
|
|
export default defineConfig({
|
|
build: {
|
|
// 资源处理
|
|
assetsInlineLimit: 4096, // 小于 4kb 的资源内联
|
|
|
|
rollupOptions: {
|
|
output: {
|
|
// 资源文件命名
|
|
assetFileNames: (assetInfo) => {
|
|
const info = assetInfo.name?.split('.') ?? []
|
|
const extType = info[info.length - 1]
|
|
|
|
if (/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/i.test(assetInfo.name ?? '')) {
|
|
return `media/[name]-[hash].[ext]`
|
|
}
|
|
if (/\.(png|jpe?g|gif|svg|webp|avif)(\?.*)?$/i.test(assetInfo.name ?? '')) {
|
|
return `images/[name]-[hash].[ext]`
|
|
}
|
|
if (/\.(woff2?|eot|ttf|otf)(\?.*)?$/i.test(assetInfo.name ?? '')) {
|
|
return `fonts/[name]-[hash].[ext]`
|
|
}
|
|
return `assets/[name]-[hash].[ext]`
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
### 压缩与优化
|
|
|
|
#### 代码压缩配置
|
|
```typescript
|
|
export default defineConfig({
|
|
build: {
|
|
minify: 'terser', // 压缩器选择: terser | esbuild
|
|
terserOptions: {
|
|
compress: {
|
|
// 移除 console
|
|
drop_console: true,
|
|
drop_debugger: true,
|
|
|
|
// 纯函数优化
|
|
pure_funcs: [
|
|
'console.log',
|
|
'console.info',
|
|
'console.debug'
|
|
],
|
|
|
|
// 代码优化
|
|
ecma: 2020,
|
|
arguments: true,
|
|
dead_code: true,
|
|
side_effects: true
|
|
},
|
|
mangle: {
|
|
// 变量名混淆
|
|
toplevel: true,
|
|
properties: {
|
|
regex: /^_/ // 混淆以下划线开头的属性
|
|
},
|
|
keep_classnames: false,
|
|
keep_fnames: false
|
|
},
|
|
format: {
|
|
// 保留版权注释
|
|
comments: false,
|
|
// 不移除代码
|
|
preserveAnnotations: false
|
|
}
|
|
},
|
|
|
|
// 不显示压缩后大小
|
|
reportCompressedSize: false
|
|
}
|
|
})
|
|
```
|
|
|
|
#### 构建产物分析
|
|
```typescript
|
|
import { visualizer } from 'rollup-plugin-visualizer'
|
|
import { defineConfig } from 'vite'
|
|
|
|
export default defineConfig({
|
|
plugins: [
|
|
visualizer({
|
|
filename: './dist/stats.html',
|
|
open: true,
|
|
gzipSize: true,
|
|
brotliSize: true,
|
|
template: 'treemap' // treemap | sunburst | network
|
|
})
|
|
]
|
|
})
|
|
|
|
// 构建后分析
|
|
// npm run build
|
|
```
|
|
|
|
## 运行时优化
|
|
|
|
### 按需导入
|
|
|
|
#### UI 组件按需导入
|
|
```typescript
|
|
// Ant Design 按需导入
|
|
import { ConfigProvider } from 'antd'
|
|
import Button from 'antd/es/button'
|
|
import Input from 'antd/es/input'
|
|
|
|
// 或者使用自动导入插件
|
|
import Components from 'unplugin-vue-components/vite'
|
|
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
|
|
|
|
export default defineConfig({
|
|
plugins: [
|
|
react(),
|
|
Components({
|
|
resolvers: [
|
|
AntDesignVueResolver({
|
|
importStyle: 'less' // 按需导入样式
|
|
})
|
|
]
|
|
})
|
|
]
|
|
})
|
|
```
|
|
|
|
#### 工具库按需导入
|
|
```typescript
|
|
// 原始导入方式(导入整个库)
|
|
// import _ from 'lodash'
|
|
// const result = _.map(arr, (item) => item.value)
|
|
|
|
// 按需导入方式
|
|
import { map } from 'lodash-es'
|
|
const result = map(arr, (item) => item.value)
|
|
|
|
// 或者使用 lodash-unified-plugin
|
|
```
|
|
|
|
### 缓存策略
|
|
|
|
#### HTTP 缓存配置
|
|
```typescript
|
|
// Nginx 配置示例
|
|
location / {
|
|
try_files $uri $uri/ /index.html;
|
|
|
|
# 关键资源缓存策略
|
|
location ~* \.(js|css)$ {
|
|
expires 1y;
|
|
add_header Cache-Control "public, immutable";
|
|
}
|
|
|
|
# 图片资源缓存
|
|
location ~* \.(jpg|jpeg|png|gif|webp|svg)$ {
|
|
expires 6M;
|
|
add_header Cache-Control "public, max-age=15552000";
|
|
}
|
|
|
|
# HTML 不缓存
|
|
location ~* \.html$ {
|
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 浏览器缓存优化
|
|
```typescript
|
|
// 预加载关键资源
|
|
// 在 index.html 中添加
|
|
<link rel="modulepreload" href="/assets/index-[hash].js">
|
|
<link rel="prefetch" href="/assets/lazy-chunk-[hash].js">
|
|
|
|
// React 应用中的预加载
|
|
const preloadComponent = (path: string) => {
|
|
const link = document.createElement('link')
|
|
link.rel = 'prefetch'
|
|
link.href = path
|
|
document.head.appendChild(link)
|
|
}
|
|
|
|
// 根据路由预加载
|
|
const routes = [
|
|
{ path: '/dashboard', preload: () => preloadComponent('/assets/dashboard-[hash].js') }
|
|
]
|
|
```
|
|
|
|
### 性能监控
|
|
|
|
#### 运行时性能监控
|
|
```typescript
|
|
// 性能监控工具
|
|
class PerformanceMonitor {
|
|
private metrics: Map<string, number> = new Map()
|
|
|
|
measure(name: string, fn: () => void) {
|
|
const start = performance.now()
|
|
fn()
|
|
const duration = performance.now() - start
|
|
|
|
this.metrics.set(name, duration)
|
|
|
|
// 开发环境打印性能指标
|
|
if (import.meta.env.DEV) {
|
|
console.log(`[Performance] ${name}: ${duration.toFixed(2)}ms`)
|
|
}
|
|
}
|
|
|
|
getMetrics() {
|
|
return Object.fromEntries(this.metrics)
|
|
}
|
|
}
|
|
|
|
// 使用示例
|
|
const monitor = new PerformanceMonitor()
|
|
monitor.measure('app-init', () => {
|
|
initializeApp()
|
|
})
|
|
```
|
|
|
|
#### 构建 time analysis
|
|
```typescript
|
|
import { defineConfig } from 'vite'
|
|
|
|
export default defineConfig({
|
|
build: {
|
|
// 构建时间限制
|
|
chunkSizeWarningLimit: 1000,
|
|
|
|
// 构建并行化
|
|
parallel: true,
|
|
|
|
// 构建统计信息
|
|
reportCompressedSize: false
|
|
}
|
|
})
|
|
|
|
// 使用 time 命令测量构建时间
|
|
// time npm run build
|
|
```
|
|
|
|
## 实际案例分析
|
|
|
|
### 大型 React 项目优化
|
|
|
|
#### 问题场景
|
|
- 🔧 项目包含 200+ 组件
|
|
- 📦 构建后的 main.js 超过 2MB
|
|
- 🚀 首屏加载时间 5-8 秒
|
|
|
|
#### 优化方案
|
|
```typescript
|
|
// vite.config.ts
|
|
export default defineConfig({
|
|
build: {
|
|
rollupOptions: {
|
|
output: {
|
|
manualChunks: {
|
|
// 核心框架
|
|
'react-core': ['react', 'react-dom', 'react-router-dom'],
|
|
|
|
// UI 组件库
|
|
'antd-core': ['antd'],
|
|
'antd-icons': ['@ant-design/icons'],
|
|
|
|
// 状态管理
|
|
'state-management': ['zustand', 'immer'],
|
|
|
|
// 工具库
|
|
'utils': ['lodash-es', 'dayjs', 'axios'],
|
|
|
|
// 业务模块
|
|
'business-user': [/src\/modules\/user/],
|
|
'business-order': [/src\/modules\/order/],
|
|
'business-product': [/src\/modules\/product/]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
#### 优化结果
|
|
- ✅ main.js 减少到 800KB
|
|
- ✅ 首屏加载时间降低到 2-3 秒
|
|
- ✅ 构建时间从 3 分钟减少到 1 分钟
|
|
|
|
### TypeScript 项目编译优化
|
|
|
|
#### 编译速度优化
|
|
```typescript
|
|
// tsconfig.json
|
|
{
|
|
"compilerOptions": {
|
|
"incremental": true, // 增量编译
|
|
"tsBuildInfoFile": ".tsbuildinfo",
|
|
"skipLibCheck": true, // 跳过类型声明文件检查
|
|
"skipDefaultLibCheck": true
|
|
},
|
|
|
|
"references": [ // 项目引用
|
|
{ "path": "./packages/component" }
|
|
]
|
|
}
|
|
```
|
|
|
|
#### 类型检查性能
|
|
```typescript
|
|
// 按需类型检查
|
|
// .githooks/pre-commit
|
|
#!/bin/bash
|
|
git diff --cached --name-only | grep '\.tsx?$' | xargs npx tsc --noEmit
|
|
|
|
// 或者在开发环境只检查当前文件
|
|
// IDE 配置 TypeScript Server 模式
|
|
```
|
|
|
|
### 组件库开发优化
|
|
|
|
#### 库模式配置
|
|
```typescript
|
|
// vite.config.ts (组件库)
|
|
export default defineConfig({
|
|
build: {
|
|
lib: {
|
|
entry: path.resolve(__dirname, 'src/index.ts'),
|
|
name: 'MyComponentLibrary',
|
|
fileName: (format) => `my-component-library.${format}.js`,
|
|
formats: ['es', 'umd', 'cjs']
|
|
},
|
|
rollupOptions: {
|
|
// 外部化 react
|
|
external: ['react', 'react-dom'],
|
|
output: {
|
|
globals: {
|
|
react: 'React',
|
|
'react-dom': 'ReactDOM'
|
|
}
|
|
}
|
|
},
|
|
// 压缩
|
|
minify: 'terser',
|
|
sourcemap: true
|
|
}
|
|
})
|
|
```
|
|
|
|
## 监控与调试
|
|
|
|
### 开发环境监控
|
|
|
|
```typescript
|
|
import { defineConfig } from 'vite'
|
|
|
|
export default defineConfig({
|
|
plugins: [
|
|
// 性能监控插件
|
|
{
|
|
name: 'performance-monitor',
|
|
transform(code, id) {
|
|
// 监控大文件
|
|
if (code.length > 100000) {
|
|
console.warn(`[Large File] ${id}: ${code.length} bytes`)
|
|
}
|
|
return null
|
|
}
|
|
}
|
|
]
|
|
})
|
|
```
|
|
|
|
### 生产环境调试
|
|
|
|
```typescript
|
|
// Source Map 配置
|
|
export default defineConfig({
|
|
build: {
|
|
sourcemap: true, // 生产环境生成 Source Map
|
|
sourceMapDebug: false
|
|
}
|
|
})
|
|
|
|
// 部署环境配置错误的 Source Map
|
|
// 设置为 true 便于问题追踪,false 提高安全性
|
|
```
|
|
|
|
## 最佳实践总结
|
|
|
|
### ✅ 推荐实践
|
|
|
|
1. **依赖管理**
|
|
- 使用 `include` 明确指定预构建依赖
|
|
- 定期清理依赖缓存
|
|
- 优先选择 ESM 格式的包
|
|
|
|
2. **代码组织**
|
|
- 按路由和功能模块进行代码分割
|
|
- 使用动态导入实现懒加载
|
|
- 合理组织组件和工具函数
|
|
|
|
3. **性能优化**
|
|
- 启用 Tree-shaking 移除死代码
|
|
- 使用现代压缩工具
|
|
- 优化资源加载策略
|
|
|
|
4. **监控分析**
|
|
- 定期分析构建产物
|
|
- 监控应用运行性能
|
|
- 持续优化配置
|
|
|
|
### ❌ 避免问题
|
|
|
|
1. **过度配置**
|
|
- 不要过度使用配置插件
|
|
- 避免不必要的依赖
|
|
- 保持配置简洁
|
|
|
|
2. **忽略缓存**
|
|
- 不要忽略依赖缓存的清理
|
|
- 注意缓存失效策略
|
|
- 合理配置缓存时间
|
|
|
|
3. **盲目优化**
|
|
- 不要在没有性能问题时过度优化
|
|
- 优先解决真正的性能瓶颈
|
|
- 基于数据驱动的优化决策
|
|
|
|
## 工具推荐
|
|
|
|
### 构建分析工具
|
|
- **vite-bundle-visualizer** - 构建产物可视化
|
|
- **rollup-plugin-visualizer** - 深度构建分析
|
|
- **source-map-explorer** - Source Map 分析
|
|
|
|
### 性能测试工具
|
|
- **Lighthouse** - 页面性能评估
|
|
- **WebPageTest** - 多地性能测试
|
|
- **Chrome DevTools Performance** - 运行时性能分析
|
|
|
|
优化是一个持续的过程,需要根据项目的具体情况和性能数据来调整策略。通过合理的配置和分析工具,可以让 Vite 项目在各种场景下表现优异。
|
|
|
|
相关文档:
|
|
- [[DEV/VITE/配置详解.md]] - 详细配置说明
|
|
- [[DEV/VITE/架构原理.md]] - 理解优化原理
|
|
- [[DEV/VITE/README.md]] - 快速入门指南
|