Vite已成为现代前端项目的首选构建工具,基于ESM的Dev Server提供了极致的开发体验。但在生产环境构建中,默认配置往往无法满足性能要求——打包体积过大、首屏加载缓慢、依赖未正确外部化等问题在生产环境会放大。本文围绕Vite生产构建配置,从代码分割、依赖优化、Tree Shaking到体积分析,给出完整的优化方案。
Vite构建原理与Rollup打包机制
Vite在开发环境使用esbuild做依赖预构建和ESM原生加载,生产环境则切换到Rollup做打包。Rollup的优势在于Tree Shaking的精确性和输出代码的精简度。理解这个切换机制对排查构建问题很重要——开发环境正常的代码在生产构建时可能因为Rollup的处理方式不同而报错。
// vite.config.ts 基础配置
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
build: {
target: 'es2020', // 生成代码的目标ES版本
cssCodeSplit: true, // CSS代码分割
sourcemap: false, // 生产环境关闭sourcemap
minify: 'esbuild', // 使用esbuild压缩,比terser快20倍
chunkSizeWarningLimit: 500, // chunk体积告警阈值(KB)
rollupOptions: {
output: {
// 静态资源分类输出
chunkFileNames: 'assets/js/[name]-[hash].js',
entryFileNames: 'assets/js/[name]-[hash].js',
assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
}
}
}
})
minify选择esbuild而非terser,压缩速度提升约20倍,压缩率仅低几个百分点。对于大型项目,构建时间的缩短非常可观。如果对压缩率有极致要求,可以先用esbuild构建再用terser做最终压缩。
代码分割与动态导入配置
代码分割是优化首屏加载的核心手段。Vite基于Rollup的manualChunks实现自定义分割策略:
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks(id) {
// node_modules依赖按包分割
if (id.includes('node_modules')) {
if (id.includes('react')) return 'vendor-react'
if (id.includes('lodash')) return 'vendor-lodash'
if (id.includes('antd')) return 'vendor-antd'
if (id.includes('@tanstack')) return 'vendor-query'
return 'vendor'
}
// 业务代码按功能模块分割
if (id.includes('/src/pages/')) {
const match = id.match(/\/src\/pages\/(\w+)/)
if (match) return `page-${match[1]}`
}
}
}
}
}
})
路由级别的动态导入配合React.lazy实现按需加载:
// App.tsx
import { lazy, Suspense } from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
// 动态导入路由组件,Vite自动代码分割
const Home = lazy(() => import('./pages/Home'))
const Dashboard = lazy(() => import('./pages/Dashboard'))
const Settings = lazy(() => import('./pages/Settings'))
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>加载中...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
</BrowserRouter>
)
}
每个lazy导入的路由会被打包成独立chunk,用户访问对应路由时才加载。这显著减少了首屏需要下载的JS体积。关键路径组件(如导航栏、布局组件)不要做动态导入,否则首屏会闪烁。
依赖预构建与外部化处理
Vite开发环境的依赖预构建通过optimizeDeps配置。对于大型依赖,预构建可以大幅提升开发服务器启动速度:
// vite.config.ts
export default defineConfig({
optimizeDeps: {
include: [
'react',
'react-dom',
'react-router-dom',
'antd',
'@tanstack/react-query',
'lodash-es'
],
exclude: ['@puppeteer/browsers'] // 不需要预构建的包
}
})
生产环境中,某些大型依赖如果不常更新,可以外部化处理,通过CDN加载:
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
external: {
'react': 'React',
'react-dom': 'ReactDOM',
'lodash-es': 'lodash'
},
output: {
globals: {
'react': 'React',
'react-dom': 'ReactDOM',
'lodash-es': 'lodash'
}
}
}
}
})
// index.html 中添加CDN引用(带integrity校验)
// <script crossorigin src="https://unpkg.com/react@18.3.1/umd/react.production.min.js"></script>
// <script crossorigin src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"></script>
外部化处理的判断标准:库体积大、版本更新不频繁、CDN可及性稳定。React、Vue这类框架库适合外部化,业务频繁更新的依赖不适合。
Tree Shaking与压缩配置
Tree Shaking依赖ESM的静态导入特性。确保package.json中设置正确的sideEffects字段:
// package.json
{
"sideEffects": [
"*.css",
"*.scss",
"*.less",
"./src/polyfills.ts"
]
}
sideEffects为false表示所有文件都是纯ESM,无副作用,可以安全Tree Shaking。但CSS文件和polyfill有副作用,必须显式列出。设置错误会导致CSS被错误Tree Shaking掉。
按需导入大型UI库是减小体积的有效手段:
// antd 按需导入(Vite不需要babel-plugin-import)
// 直接从antd导入组件,Vite的Tree Shaking会自动处理
import { Button, Input, Select } from 'antd'
// lodash-es按需导入
import { debounce, throttle } from 'lodash-es'
// 不要使用 import _ from 'lodash-es' ,即使Tree Shaking也可能包含多余代码
// 日期库dayjs按需加载locale
import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn'
dayjs.locale('zh-cn')
对于CSS框架,使用Tailwind CSS时通过content配置确保只生成用到的样式类:
// tailwind.config.js
export default {
content: [
'./index.html',
'./src/**/*.{js,ts,jsx,tsx}',
],
// 确保purge范围覆盖所有使用Tailwind类的文件
}
打包体积分析与Bundle优化实战
rollup-plugin-visualizer是分析打包体积的必备工具:
// vite.config.ts
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
react(),
visualizer({
filename: 'dist/stats.html',
template: 'treemap',
gzipSize: true,
brotliSize: true,
})
]
})
构建后在dist/stats.html中查看可视化报告,定位体积异常的依赖。常见优化操作:
替换体积大的依赖:用dayjs替换moment.js(280KB减到7KB),用lodash-es的按需导入替换完整lodash,用轻量级图标库替换完整iconfont。
图片资源优化:
// vite.config.ts
import { defineConfig } from 'vite'
import viteImagemin from 'vite-plugin-imagemin'
export default defineConfig({
plugins: [
viteImagemin({
gifsicle: { optimizationLevel: 3 },
optipng: { optimizationLevel: 7 },
mozjpeg: { quality: 80 },
pngquant: { quality: [0.65, 0.8] },
svgo: { plugins: [{ removeViewBox: false }] }
})
]
})
// 或使用unplugin-vue-components自动导入SVG为组件
// 小于4KB的图片自动转base64
export default defineConfig({
build: {
assetsInlineLimit: 4096
}
})
Gzip/Brotli预压缩:
// vite.config.ts
import { gzip } from 'vite-plugin-compression'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
gzip({
algorithm: 'gzip',
ext: '.gz',
threshold: 10240, // 仅压缩大于10KB的文件
}),
// Brotli压缩率更高,但压缩速度慢
// 适合静态资源长期缓存场景
]
})
开启Gzip压缩后,100KB的JS文件压缩到约30KB,配合CDN的Brotli压缩可以进一步降到20KB左右。Nginx配置gzip_static指令直接发送预压缩文件,避免实时压缩的CPU开销。
优化效果量化:某中大型React项目(30+路由页面)优化前首次加载体积2.8MB,通过代码分割+依赖外部化+按需导入后降至首屏420KB,交互加载830KB。首屏FCP从3.2s降到1.1s,LCP从4.5s降到1.8s。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vite-gou-jian-gong-ju-pei-zhi-you-hua-shi-zhan-yu-sheng/