
为什么 Vite 构建性能至关重要
Vite 的开发服务器以速度著称,但生产构建则另当别论。配置不当的 Vite 项目可能产生数兆字节的包,拖累 Lighthouse 评分,并在应用加载前就赶走用户。到 2026 年,用户会放弃加载时间超过 3 秒的页面,而 Google 的 Core Web Vitals 直接影响搜索排名。
本指南深入探讨 Vite 的生产构建流水线,并展示能将 Lighthouse 评分从 60 提升到 95+ 的具体配置。
理解 Vite 的构建流水线
Vite 在生产构建中底层使用 Rollup。Rollup 中可用的所有优化技术都可以通过 build.rollupOptions 在 Vite 中使用。Vite 的开发模式使用 esbuild 实现近乎即时的转换;生产模式使用 Rollup 进行深度优化。它们是根本不同的流水线。
代码分割策略
手动分块——影响最大的改动
Vite 的默认策略是将所有 node_modules 内容放入一个单一的 vendor 块。对于中等规模的应用,这会产生一个 500KB+ 的块,必须在任何内容渲染之前下载并解析。
// vite.config.ts
import { defineConfig } from 'vite'
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
// React 运行时——很少更改,永久缓存
'react-vendor': ['react', 'react-dom'],
// UI 组件库——体积大但稳定
'ui-vendor': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
// 路由
'router': ['react-router-dom'],
// 工具库
'utils': ['lodash-es', 'date-fns', 'zod'],
},
},
},
},
})
当你分发独立的块时,返回的用户在应用小更新后只需重新下载实际更改的块。react-vendor 块永远不会改变,并会从浏览器缓存中即时提供。
基于函数的分块(适用于大型应用)
function manualChunks(id: string) {
if (id.includes('/src/pages/')) {
const match = id.match(/\/pages\/([^/]+)/)
if (match) return `page-${match[1]}`
}
if (id.includes('node_modules')) {
if (id.includes('recharts') || id.includes('d3')) return 'charts-vendor'
if (id.includes('@codemirror') || id.includes('monaco-editor')) return 'editor-vendor'
if (id.includes('framer-motion')) return 'animation-vendor'
return 'vendor'
}
}
export default defineConfig({
build: { rollupOptions: { output: { manualChunks } } },
})
基于路由的代码分割(配合 React.lazy)
// App.tsx
import { lazy, Suspense } from 'react'
import { Routes, Route } from 'react-router-dom'
const Dashboard = lazy(() => import('./pages/Dashboard'))
const Settings = lazy(() => import('./pages/Settings'))
const Analytics = lazy(() => import('./pages/Analytics'))
export function App() {
return (
<Suspense fallback={<PageSkeleton />}>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
<Route path="/analytics" element={<Analytics />} />
</Routes>
</Suspense>
)
}
从未访问 /analytics 的用户永远不会下载 280KB 的图表代码。
Tree Shaking:让它真正生效
Tree shaking 可以消除死代码,但前提是依赖项使用 ES 模块且导出无副作用。
lodash 问题及解决方案
// 糟糕:导入整个 lodash(~70KB gzipped)
import _ from 'lodash'
const result = _.groupBy(items, 'category')
// 良好:lodash-es 支持 tree shaking
import { groupBy } from 'lodash-es'
const result = groupBy(items, 'category')
使用 rollup-plugin-visualizer 进行包分析
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
visualizer({
filename: 'dist/bundle-analysis.html',
gzipSize: true,
brotliSize: true,
open: true, // 构建后自动打开
}),
],
})
运行 vite build,可视化工具会自动打开,显示交互式包树图。每个矩形代表一个模块;大小对应字节。通过它你可以发现隐藏在日期选择器中的 moment.js 及其完整语言包。
Rollup 高级配置
压缩与 esbuild 选项
export default defineConfig({
build: {
// esbuild 是默认选项——快速且集成良好
minify: 'esbuild',
// 在生产环境中移除 console/debugger 语句
esbuildOptions: {
drop: ['console', 'debugger'],
},
// 针对现代浏览器——更少的转译意味着更小的输出
target: 'es2020',
},
})
资源配置
export default defineConfig({
build: {
assetsInlineLimit: 4096, // 小于 4KB 的资源内联为 base64
cssCodeSplit: true, // 每个块独立 CSS,实现并行加载
sourcemap: 'hidden', // 用于错误监控的 source map,对用户不可见
rollupOptions: {
output: {
chunkFileNames: 'assets/[name]-[hash].js',
entryFileNames: 'assets/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash].[ext]',
},
},
},
})
构建性能:更快的 CI 流水线
export default defineConfig({
optimizeDeps: {
include: ['react', 'react-dom', 'react-router-dom'],
exclude: ['@vite/client', '@vite/env'],
},
build: {
rollupOptions: {
maxParallelFileOps: 20,
},
},
})
环境特定配置
import { defineConfig, loadEnv } from 'vite'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '')
return {
define: {
__APP_VERSION__: JSON.stringify(env.npm_package_version),
__ENABLE_ANALYTICS__: mode === 'production',
},
build: {
sourcemap: mode === 'staging' ? true : 'hidden',
chunkSizeWarningLimit: 500, // KB — 对大块发出警告
},
}
})
衡量你的成果
# 构建并分析
vite build --mode production
# 启动服务并运行 Lighthouse
npm install -g serve
serve dist &
npx lighthouse http://localhost:3000 --view
需要跟踪的关键 Lighthouse 指标:
- FCP(首次内容绘制):目标 < 1.8s
- LCP(最大内容绘制):目标 < 2.5s
- TBT(总阻塞时间):目标 < 200ms
- CLS(累积布局偏移):目标 < 0.1
实际效果:仪表盘应用案例研究
| 指标 | 优化前 | 优化后 | 变化 |
|---|---|---|---|
| 包大小 (gzip) | 847 KB | 312 KB | −63% |
| 块数量 | 1 | 8 | +7 |
| LCP | 4.2s | 1.8s | −57% |
| TBT | 680ms | 120ms | −82% |
| Lighthouse 评分 | 61 | 94 | +33 分 |
最大的单一胜利:将 recharts 和 d3 移到它们自己的块中,并懒加载分析页面。
总结
Vite 构建优化在于理解浏览器如何加载代码。按稳定性分割 vendor 依赖,对用户可能永远不会访问的路由使用动态导入,通过可视化插件验证 tree shaking,并在每次重大更改后使用 Lighthouse 进行测量。十分钟的配置就能让你从“尚可”变为“快速”。