Vite 打包优化终极指南:从 30MB 到 800KB 的性能飞跃
📦 涉及组件清单
| 组件名称 | GitHub 链接 | Star 数量 | 功能描述 | 推荐理由 |
|---|---|---|---|---|
rollup-plugin-visualizer |
GitHub | 4.2k+ | 打包体积可视化分析 | Vite/Rollup 场景最常用,定位瓶颈直观 |
unplugin-vue-components |
GitHub | 5k+ | Vue 组件自动按需导入 | 显著降低 UI 库引入成本,配置简洁 |
unplugin-auto-import |
GitHub | 4k+ | API 自动导入 | 减少样板 import,增强开发效率 |
vite-plugin-inspect |
GitHub | 2k+ | 查看 Vite 插件变换链路 | 排查插件冲突和 transform 成本很有帮助 |
vite-plugin-compression |
GitHub | 2k+ | 生成 gzip/br 压缩产物 | 服务端配合后对传输体积收益明显 |
vite-plugin-image-optimizer |
GitHub | 500+ | 基于 Sharp.js 的图片压缩 | 更现代,支持缓存,安装友好 |
vite-plugin-cdn-import |
GitHub | 500+ | 外部依赖 CDN 化 | 快速完成 external + HTML 注入 |
unplugin-imagemin |
GitHub | 300+ | 图片压缩插件 | 基于 squoosh/sharp,支持多种格式 |
vite-plugin-perfsee |
GitHub | 600+ | 性能综合检测工具 | 结合 Lighthouse 打分、构建体积分析 |
speed-measure-vite-plugin |
GitHub | 500+ | 构建耗时分析 | 精准定位哪个插件拖慢构建速度 |
vite-plugin-remove-console |
GitHub | 200+ | 清除 console 语句 | 专用插件,比 terser 配置更简单 |
vite-plugin-html |
GitHub | 1k+ | HTML 压缩与动态注入 | 支持 EJS 模板、HTML 压缩、多页面注入 |
引言
Vite 作为新一代前端构建工具,凭借其基于 ES Module 的开发时快速启动和基于 Rollup 的生产时高效打包能力,已经成为现代前端项目的主流选择。然而,即使使用 Vite,默认配置下的项目仍可能出现以下问题:
- 构建速度慢:大量依赖解析耗时、编译任务繁重、插件链过长
- 打包体积大:全量引入大型库、重复模块未合并、无用代码未清除
- 缓存命中差:分包策略不合理导致浏览器缓存频繁失效
- 用户体验不佳:FCP、LCP、TTI 等核心指标表现不佳
一、核心概念:Vite 为什么快,又为什么会慢
1.1 Vite 的速度来源
要理解 Vite 的优化方向,首先需要了解 Vite 为什么快。Vite 的快速体验来自以下几个核心机制:
开发阶段:原生 ESM 与按需编译
传统的打包工具(如 Webpack)在开发环境下需要将所有模块打包成 bundle,然后才能启动开发服务器。这个过程在大型项目中可能需要几十秒甚至几分钟。
Vite 则采用了完全不同的策略:利用浏览器原生支持 ES Module 的特性,Vite 在开发环境下不做整体打包,而是让浏览器直接请求源文件。对于 import 语句,Vite 充当一个"按需编译"的服务器------只有当浏览器请求某个文件时,Vite 才会对该文件进行转换和压缩。
这种模式的优点是启动速度极快,因为不需要遍历整个依赖图。启动时间从 O(n) 降到了 O(1),只与当前访问的页面相关,与项目整体规模无关。
依赖预构建:解决 CommonJS 兼容性问题
虽然浏览器原生支持 ESM,但 node_modules 中大量库仍然使用 CommonJS 格式。Vite 通过预构建(Pre-bundling)机制,将这些 CommonJS 依赖转换为 ESM 格式,并整合成少量文件,减少浏览器请求数量。
生产阶段:Rollup 高效打包
Vite 在生产环境使用 Rollup 进行打包。Rollup 相比 Webpack,設計上更专注于 ES Module 的静态分析,能够生成更干净的 bundle,并且天然支持高质量的 Tree Shaking。
1.2 速度瓶颈的常见来源
理解了 Vite 为什么快,我们再来看它为什么会慢。常见的瓶颈来源包括:
依赖层面的问题
- 大型依赖全量引入(如 lodash、echarts、moment)
- 依赖解析路径过深(路径别名配置不当)
- 重复依赖未合并(多个包引用了同一库的不同版本)
配置层面的问题
- 插件链过长,许多插件在生产构建中不必要
sourcemap生成开启,增加构建时间- 未配置依赖预构建或预构建范围不当
- 开发环境插件未区分,生产环境仍在运行
资源层面的问题
- 图片等静态资源未经压缩
- 资源内联阈值设置不当(过大或过小)
- 静态资源未分类,浏览器难以并行加载
分包层面的问题
- 所有代码打包成单一 bundle
- 分包粒度不合理(过细导致请求碎片化,过粗导致缓存失效)
- 第三方库与业务代码未分离
二、打包诊断:优化从了解开始
做任何优化之前,首先要清楚当前项目的打包状态------哪些模块占用了大量体积?哪些依赖是主要性能瓶颈?盲目优化不仅浪费时间,还可能适得其反。
2.1 rollup-plugin-visualizer:打包体积可视化分析
rollup-plugin-visualizer 是 Vite 项目中最经典的打包分析插件,功能对标 Webpack 的 webpack-bundle-analyzer。它可以生成交互式的可视化报告,帮助开发者直观定位体积瓶颈。
核心特性:
- 支持多种视图模式:treemap(矩形层次图)、sunburst(循环层次图)、network(网格图)、flamegraph(火焰图)
- 显示 gzip 与 brotli 压缩后的体积
- 支持 JSON、YAML 等格式导出,便于自动化集成
安装与配置:
bash
npm install rollup-plugin-visualizer -D
typescript
// vite.config.ts
import { defineConfig } from 'vite'
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
visualizer({
open: true,
gzipSize: true,
brotliSize: true,
filename: 'dist/stats.html',
template: 'treemap'
})
]
})
执行 npm run build 后,会在 dist 目录生成 stats.html 文件。打开后可以清晰看到各个模块的体积占比,通常 node_modules 中的第三方库会占据最大比例。
2.2 vite-plugin-inspect:插件链路分析
vite-plugin-inspect 用于查看 Vite 插件的执行流程和模块依赖关系,排查构建卡顿环节。当构建过程异常缓慢时,这个工具能帮你精准定位是哪个插件在"拖后腿"。
安装与配置:
bash
npm install vite-plugin-inspect -D
typescript
// vite.config.ts
import Inspect from 'vite-plugin-inspect'
export default defineConfig({
plugins: [Inspect()]
})
启动开发服务器后,访问 http://localhost:5173/__inspect/,可以查看插件的 transform 顺序、每个插件的执行耗时,以及模块的依赖关系图。
2.3 speed-measure-vite-plugin:构建耗时分析
speed-measure-vite-plugin 专门用于测量每个插件的执行耗时。当构建时间过长时,使用此插件可以精准定位问题环节。
安装与配置:
bash
npm install speed-measure-vite-plugin -D
typescript
// vite.config.ts
import SpeedMeasurePlugin from 'speed-measure-vite-plugin'
const smp = new SpeedMeasurePlugin()
export default defineConfig({
plugins: smp.wrap({
vue()
})
})
执行构建后,终端会输出每个插件的耗时信息,帮助你识别慢插件。
2.4 vite-plugin-perfsee:综合性能检测
vite-plugin-perfsee 是 Perfsee 团队出品的性能分析工具,结合了 Lighthouse 打分、构建体积分析和热更新性能分析。适合进行构建性能的综合检测,也可在提交 PR 时做性能回归检测。
安装与配置:
bash
npm install vite-plugin-perfsee -D
typescript
// vite.config.ts
import { perfsee } from 'vite-plugin-perfsee'
export default defineConfig({
plugins: [
perfsee({
// 可配置项
})
]
})
2.5 建立性能基线
在使用诊断工具之前,建议先建立性能基线,记录以下关键指标:
| 指标类型 | 具体指标 | 测量方法 |
|---|---|---|
| 产物体积 | dist 总大小、最大 JS 文件大小 | du -sh dist/ |
| 构建性能 | 冷启动时间、热更新时间 | CLI 输出或手动计时 |
| 加载性能 | FCP、LCP、TTI | Lighthouse 或 Chrome DevTools |
| 网络性能 | 请求数量、资源大小 | Network 面板 |
示例记录:
diff
优化前基线:
- dist 总大小:30 MB
- 最大 JS 文件:7 MB
- 构建耗时:45s
- 首屏加载:8.5s
- 资源请求数:156 个
有了基线数据,后续的优化效果才能量化对比。
三、构建速度优化:让打包快起来
Vite 构建速度慢的核心原因是「依赖解析耗时久、编译任务繁重、资源处理低效」。以下 10 个技巧从依赖、编译、缓存、配置四个层面帮助提升构建速度。
3.1 依赖预构建优化
原理解析 :Vite 开发环境会预构建依赖(将 CommonJS 依赖转为 ES Module),但预构建的范围默认只包含一小部分常用依赖。通过 optimizeDeps 精细控制预构建范围,可以减少开发服务器启动时的重复解析工作。
配置:
typescript
// vite.config.ts
export default defineConfig({
optimizeDeps: {
include: [
'vue',
'vue-router',
'pinia',
'axios',
'lodash-es'
],
exclude: ['some-large-library'],
cacheDir: '.vite'
}
})
效果 :冷启动速度可提升 30%-50%。对于大型项目,建议将所有高频使用的依赖都加入 include 列表。
3.2 开启构建缓存
原理解析:Vite 支持构建缓存机制,将编译后的结果缓存到本地文件系统。下次打包时,Vite 会比较文件时间戳和内容哈希,只重新编译有变化的部分。
配置:
typescript
// vite.config.ts
export default defineConfig({
build: {
cache: {
type: 'filesystem',
key: (config) => `${config.mode}-${process.env.npm_package_version}`
}
}
})
CI/CD 优化建议:
bash
# 在 CI/CD 中缓存 .vite 和 node_modules
pnpm install --frozen-lockfile
pnpm build
# 缓存 node_modules/.vite 目录
效果:二次构建速度可提升 50%-70%。
3.3 精简插件配置
原理解析:过多的 Vite 插件会增加构建耗时,尤其是一些仅在开发环境有用的插件。生产环境应该禁用与构建无关的插件。
区分开发/生产环境插件:
typescript
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import visualizer from 'rollup-plugin-visualizer'
import eslintPlugin from 'vite-plugin-eslint'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd())
const isProduction = mode === 'production'
return {
plugins: [
vue(),
!isProduction && eslintPlugin(),
isProduction && visualizer({
open: true,
gzipSize: true,
brotliSize: true
})
].filter(Boolean)
}
})
建议在生产环境禁用的插件:
- vite-plugin-eslint(代码检查,开发时已运行)
- vite-plugin-stylelint(样式检查,开发时已运行)
- vite-plugin-vue-inspector(Vue 调试工具)
效果:构建速度可提升 10%-20%。
3.4 优化 HMR 热更新
原理解析 :热更新(HMR)延迟会严重影响开发效率。在 Windows 或 Docker 环境下,文件系统监听可能不灵敏,需要通过配置 usePolling: true 来使用轮询方式。
配置:
typescript
// vite.config.ts
export default defineConfig({
server: {
watch: {
usePolling: true,
ignored: ['**/node_modules/**', '**/.git/**', '**/dist/**'],
interval: 100
},
open: true,
port: 3000,
strictPort: true
}
})
效果:热更新延迟从 3-5s 降至 1s 以内。
3.5 关闭生产环境 sourceMap
原理解析 :sourceMap 用于代码调试,生产环境无需生成。关闭 sourceMap 可以同时减少打包体积和构建时间。
配置:
typescript
// vite.config.ts
export default defineConfig({
build: {
sourcemap: false
}
})
如果需要在生产环境保留错误追踪能力,可以使用 'hidden' 模式,这样会生成 sourceMap 但不在产物中引用:
typescript
sourcemap: 'hidden'
效果:构建速度提升 20%-30%,产物体积减少 50%+。
3.6 路径别名优化
原理解析:配置路径别名不仅方便开发,还能减少 Vite 的路径查找时间。合理的别名配置可以避免相对路径的多次解析。
配置:
typescript
// vite.config.ts
import { defineConfig } from 'vite'
import { resolve } from 'path'
export default defineConfig({
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'~': resolve(__dirname, 'node_modules')
}
}
})
3.7 多页面应用(MPA)优化
原理解析:多页面应用默认会构建所有页面。通过配置多入口,可以让每个页面独立构建,未访问的页面不参与当前构建。
配置:
typescript
// vite.config.ts
import { resolve } from 'path'
export default defineConfig({
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
admin: resolve(__dirname, 'admin.html'),
mobile: resolve(__dirname, 'mobile.html')
}
}
}
})
效果:大型 MPA 项目的冷启动速度可提升 50% 以上。
3.8 合理设置 chunk 大小警告阈值
原理解析:Vite 默认的 chunk 大小警告阈值是 500KB。对于大型应用或特定场景(如管理后台),这个阈值可能过于严格。适当调整可以减少无意义的警告干扰。
配置:
typescript
// vite.config.ts
export default defineConfig({
build: {
chunkSizeWarningLimit: 1500
}
})
3.9 减少 resolve 解析
原理解析 :每次 import 时 Vite 都需要解析模块路径。减少 resolve.extensions 列表长度、避免过多自定义 alias 可以加快解析速度。
配置建议:
typescript
// vite.config.ts
export default defineConfig({
resolve: {
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json'],
mainFields: ['module', 'jsnext:main', 'jsnext']
}
})
3.10 虚拟模块实现真正的按需打包
对于"同仓多发行版本"的场景(如 full/member/lite 版本),可以通过虚拟模块在构建时决定路由入口,实现真正的编译期裁剪。
步骤 1:创建独立的路由配置文件
typescript
// src/router/routes.full.ts
export const getRoutes = async () => {
const [
{ homeRoutes },
{ mineRoutes },
{ orderRoutes }
] = await Promise.all([
import('@/router/modules/home'),
import('@/router/modules/mine'),
import('@/router/modules/order')
])
return [
...homeRoutes,
...mineRoutes,
...orderRoutes
]
}
步骤 2:创建虚拟模块插件
typescript
// vite.config.ts
import path from 'path'
import fs from 'fs'
const virtualRoutesPlugin = (routeType) => {
const virtualModuleId = 'virtual:routes'
const resolvedVirtualModuleId = '\0' + virtualModuleId
const routesFile = path.resolve(__dirname, `src/router/routes.${routeType}.ts`)
return {
name: 'virtual-routes',
enforce: 'pre',
resolveId(id) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId
}
},
load(id) {
if (id === resolvedVirtualModuleId) {
return fs.readFileSync(routesFile, 'utf-8')
}
}
}
}
步骤 3:配置打包脚本
bash
# Member 专属版(只打包 member 相关路由)
VITE_ROUTE_TYPE=member npm run build
# 完整版(打包所有路由)
npm run build
效果对比:
| 指标 | 完整版 | Member 版 |
|---|---|---|
| JS 文件数量 | 265 个 | 20 个 |
| 打包时间 | 31.49s | 11.85s |
| 核心 JS 大小 | 2.2MB | ~2.0MB |
四、打包体积优化:让产物小下去
相比构建速度,打包体积优化更能直接影响用户体验。以下从代码分割、资源压缩、按需引入、CDN 加速四个维度展开。
4.1 代码分割:合理拆分管好缓存
代码分割(Code Splitting)是优化首屏加载的关键策略。通过 manualChunks 将大体积依赖和业务代码分离,可以实现更好的缓存控制和按需加载。
策略一:按依赖类型基础分包
typescript
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
'vue-vendor': ['vue', 'vue-router', 'pinia'],
'ui-vendor': ['element-plus', 'ant-design-vue'],
'utils-vendor': ['lodash-es', 'dayjs', 'axios']
}
}
}
}
})
策略二:逐库精细分包
每个 node_modules 下的包单独打包成独立文件,缓存控制更精细:
typescript
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
return id.toString()
.split('node_modules/')[1]
.split('/')[0]
.toString()
}
}
}
}
}
})
注意:这种方式会产生大量小文件,增加 HTTP 请求数,建议仅在 HTTP/2 环境下使用。
策略三:针对大体积库的详细分包
对于 ant-design-vue 这类大型 UI 库,可以进一步细分:
typescript
// vite.config.ts
const antdSplitArr = [
'node_modules/ant-design-vue/es/table',
'node_modules/ant-design-vue/es/select',
'node_modules/ant-design-vue/es/form',
'node_modules/ant-design-vue/es/menu',
'node_modules/ant-design-vue/es/vc-picker',
'node_modules/ant-design-vue/es/_util'
]
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks(id) {
for (const path of antdSplitArr) {
if (id.includes(path)) {
return 'antd-split'
}
}
if (id.includes('node_modules/echarts')) return 'echarts'
if (id.includes('node_modules/ant-design-vue/es/')) return 'antd'
if (id.includes('node_modules/vue') ||
id.includes('node_modules/pinia') ||
id.includes('node_modules/vue-router')) return 'vue'
}
}
}
}
})
⚠️ 注意事项:id 分包方式可能导致依赖引用异常,建议先在开发环境测试,确认无报错再使用。
静态资源分类输出
将 JS、CSS、图片等资源分类到不同文件夹:
typescript
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
chunkFileNames: 'static/js/[name]-[hash].js',
entryFileNames: 'static/js/[name]-[hash].js',
assetFileNames: 'static/[ext]/[name]-[hash].[ext]'
}
}
}
})
输出效果:
perl
dist/
├── static/
│ ├── js/
│ │ ├── vue-vendor-a1b2c3d4.js
│ │ └── index-e5f6g7h8.js
│ ├── css/
│ │ └── index-i9j0k1l2.css
│ └── img/
│ └── logo-m3n4o5p6.png
4.2 Tree Shaking:自动清除无用代码
原理解析:Tree Shaking 是 Rollup 提供的特性,通过静态分析 ES Module 的导入导出关系,自动剔除未使用的代码。Tree Shaking 有效的前提是模块必须使用 ES Module 格式。
esbuild vs terser 压缩对比
| 特性 | esbuild | terser |
|---|---|---|
| 压缩速度 | 快 20-40 倍 | 较慢 |
| 压缩率 | 略低 1-2% | 更高 |
| 配置复杂度 | 简单 | 较复杂 |
| Tree Shaking | 基础 | 更彻底 |
| 兼容性 | 现代浏览器 | 所有浏览器 |
esbuild 压缩配置
typescript
// vite.config.ts
export default defineConfig({
esbuild: {
pure: ['console.log', 'console.info', 'console.debug'],
drop: ['debugger']
}
})
terser 压缩配置
bash
npm install terser -D
typescript
// vite.config.ts
export default defineConfig({
build: {
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log', 'console.info']
}
}
}
})
lodash 的 Tree Shaking 问题
lodash 存在一个著名的 Tree Shaking 问题:完整版的 lodash 是 CommonJS 格式,无法被 Tree Shaking。
typescript
// ❌ 错误:全量引入,约 70KB
import _ from 'lodash'
const result = _.cloneDeep(obj)
// ✅ 正确:使用 lodash-es,约 13KB
import { cloneDeep } from 'lodash-es'
const result = cloneDeep(obj)
体积对比:
| 库 | 完整版体积 | 按需引入体积 | 节省比例 |
|---|---|---|---|
| lodash | 78.64 KB | 13.23 KB | 83% |
| moment.js | 200KB+ | 10KB (dayjs) | 95% |
| echarts | 600KB+ | 100KB | 83% |
4.3 按需引入:不打包未使用的代码
UI 组件按需引入
使用 unplugin-vue-components 自动按需导入 Vue 组件:
bash
npm install unplugin-vue-components -D
typescript
// vite.config.ts
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver, AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
Components({
dirs: ['src/components'],
extensions: ['vue', 'jsx'],
dts: 'src/components.d.ts',
resolvers: [ElementPlusResolver()]
})
]
})
支持的 UI 库解析器:Element Plus、Ant Design Vue、Vant、Naive UI、Prime Vue、Quasar、TDesign、View UI、Arco Design Vue 等 20+ 主流 UI 库。
效果对比:
| 库 | 全量引入 | 按需引入 | 节省比例 |
|---|---|---|---|
| Element Plus | 300KB+ | 30KB | 90% |
| Ant Design Vue | 500KB+ | 50KB | 90% |
API 自动导入
使用 unplugin-auto-import 自动导入 Vue、Vue Router 等 API,无需手动编写 import 语句:
bash
npm install unplugin-auto-import -D
typescript
// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'
export default defineConfig({
plugins: [
AutoImport({
imports: ['vue', 'vue-router', 'pinia', '@vueuse/core'],
dts: 'src/auto-import.d.ts',
vueTemplate: true
})
]
})
使用效果:
typescript
// ❌ 引入前
import { ref, computed, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useStore } from 'pinia'
const count = ref(0)
const doubled = computed(() => count.value * 2)
// ✅ 引入后(无需手动 import)
const count = ref(0)
const doubled = computed(() => count.value * 2)
ECharts 按需引入
ECharts 完整引入体积巨大,通过按需引入可以显著减少体积:
typescript
// src/utils/echarts.ts
import * as echarts from 'echarts/core'
import { BarChart, LineChart, PieChart, GaugeChart } from 'echarts/charts'
import {
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent,
ToolboxComponent
} from 'echarts/components'
import { LabelLayout, UniversalTransition } from 'echarts/features'
import { CanvasRenderer } from 'echarts/renderers'
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent,
ToolboxComponent,
BarChart,
LineChart,
PieChart,
GaugeChart,
LabelLayout,
UniversalTransition,
CanvasRenderer
])
export default echarts
4.4 路由与组件懒加载
路由级懒加载
typescript
// router/index.ts
const routes = [
{
path: '/home',
name: 'Home',
component: () => import('@/views/Home.vue')
},
{
path: '/about',
name: 'About',
component: () => import('@/views/About.vue')
}
]
组件级懒加载
对于体积较大的组件(如富文本编辑器、复杂图表),可使用 defineAsyncComponent 实现懒加载:
typescript
// 组件中使用
<template>
<div>
<button @click="showEditor = true">显示编辑器</button>
<RichEditor v-if="showEditor" />
</div>
</template>
<script setup>
import { ref, defineAsyncComponent } from 'vue'
const showEditor = ref(false)
const RichEditor = defineAsyncComponent(() => import('@/components/RichEditor.vue'))
</script>
4.5 资源压缩:减少传输体积
Gzip/Brotli 压缩原理
代码压缩后,传输体积可减少 60%-80%。当请求静态资源时,服务器识别到 .gz 或 .br 后缀的文件,自动返回压缩版本,浏览器解压后使用。
压缩率对比:
| 压缩方式 | 压缩率 | 说明 |
|---|---|---|
| 无压缩 | 0% | 原始体积 |
| Gzip | 60%-70% | 通用性强,所有浏览器支持 |
| Brotli | 70%-80% | 更高压缩率,需要服务器支持 |
vite-plugin-compression 配置
bash
npm install vite-plugin-compression -D
typescript
// vite.config.ts
import viteCompression from 'vite-plugin-compression'
export default defineConfig({
plugins: [
viteCompression({
verbose: true,
threshold: 10240,
algorithm: 'brotliCompress',
ext: '.br'
}),
viteCompression({
verbose: true,
threshold: 10240,
algorithm: 'gzip',
ext: '.gz'
})
]
})
Nginx 配置(配合压缩使用)
nginx
server {
gzip on;
gzip_types text/plain text/css application/javascript application/json text/xml;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_vary on;
brotli on;
brotli_types text/plain text/css application/javascript application/json text/xml;
brotli_comp_level 6;
}
图片压缩
方案一:unplugin-imagemin(推荐)
bash
npm install unplugin-imagemin -D
typescript
// vite.config.ts
import imagemin from 'unplugin-imagemin/vite'
export default defineConfig({
plugins: [
imagemin({
mode: 'sharp',
compress: {
jpeg: { quality: 25 },
png: { quality: 25 },
webp: { quality: 25 }
},
conversion: [
{ from: 'png', to: 'webp' },
{ from: 'jpeg', to: 'png' }
]
})
]
})
方案二:vite-plugin-image-optimizer
bash
npm install vite-plugin-image-optimizer -D
npm install sharp svgo -D
typescript
// vite.config.ts
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
export default defineConfig({
plugins: [
ViteImageOptimizer({
logStats: true,
test: /\.(jpe?g|png|gif|tiff|webp|svg|avif)$/i,
svg: {
multipass: true,
plugins: [
{ name: 'preset-default' },
'sort-attrs'
]
},
png: { quality: 80 },
jpeg: { quality: 80 },
webp: { lossless: true },
cache: true
})
]
})
4.6 CDN 加速:减少打包体积
原理解析:将大体积依赖通过 CDN 加载,利用浏览器缓存实现加速。同时,主包体积减小,首屏加载速度提升。
CDN 服务商对比
| 服务商 | 特点 | 访问速度 | 生态丰富度 |
|---|---|---|---|
| jsDelivr | 免费、开源、GitHub/CDN | 全球 CDN | 非常丰富 |
| unpkg | NPM 包直接访问 | 全球 CDN | 丰富 |
| cdnjs | 社区维护 | 全球 CDN | 一般 |
vite-plugin-cdn-import 配置
bash
npm install vite-plugin-cdn-import -D
typescript
// vite.config.ts
import { Plugin as importToCDN, autoComplete } from 'vite-plugin-cdn-import'
export default defineConfig({
plugins: [
importToCDN({
modules: [
autoComplete('vue'),
autoComplete('vue-router'),
autoComplete('pinia'),
autoComplete('axios'),
{
name: 'echarts',
var: 'echarts',
path: 'https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js'
}
]
})
]
})
CDN 注意事项
| 注意点 | 说明 |
|---|---|
| 可靠性 | 依赖第三方 CDN 可用性,建议使用知名 CDN |
| 版本控制 | 锁定具体版本号,避免升级导致兼容问题 |
| 安全风险 | 引入外部脚本存在 XSS 风险,需评估来源可信度 |
| 调试困难 | 生产环境问题定位难度增加 |
实际案例 :某管理后台项目通过 CDN + 分包 + 按需引入,包体积从 1.7MB 优化至 899KB,减少 47%。
五、完整配置示例
将以上所有优化策略整合为一个完整的配置:
typescript
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import AutoImport from 'unplugin-auto-import/vite'
import viteCompression from 'vite-plugin-compression'
import { visualizer } from 'rollup-plugin-visualizer'
import { Plugin as importToCDN, autoComplete } from 'vite-plugin-cdn-import'
import viteImagemin from 'vite-plugin-imagemin'
import removeConsole from 'vite-plugin-remove-console'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd())
const isProduction = mode === 'production'
return {
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
},
optimizeDeps: {
include: ['vue', 'vue-router', 'pinia', 'axios', 'lodash-es'],
exclude: ['echarts']
},
server: {
host: '0.0.0.0',
port: 3000,
open: true,
watch: {
usePolling: true,
ignored: ['**/node_modules/**', '**/.git/**', '**/dist/**']
}
},
plugins: [
vue(),
AutoImport({
imports: ['vue', 'vue-router', 'pinia', '@vueuse/core'],
dts: 'src/auto-imports.d.ts'
}),
Components({
dirs: ['src/components'],
extensions: ['vue', 'jsx'],
dts: 'src/components.d.ts',
resolvers: [ElementPlusResolver({ importStyle: 'sass' })]
}),
isProduction && visualizer({
open: true,
gzipSize: true,
brotliSize: true,
filename: 'dist/stats.html'
}),
isProduction && viteCompression({
algorithm: 'brotliCompress',
threshold: 10240,
ext: '.br'
}),
isProduction && viteCompression({
algorithm: 'gzip',
threshold: 10240,
ext: '.gz'
}),
isProduction && importToCDN({
modules: [
autoComplete('vue'),
autoComplete('vue-router'),
autoComplete('pinia'),
autoComplete('axios'),
autoComplete('lodash')
]
}),
isProduction && viteImagemin({
gifsicle: { optimizationLevel: 7 },
optipng: { optimizationLevel: 7 },
mozjpeg: { quality: 20 },
pngquant: { quality: [0.8, 0.9] }
}),
isProduction && removeConsole()
].filter(Boolean),
build: {
minify: 'terser',
sourcemap: false,
chunkSizeWarningLimit: 1500,
assetsInlineLimit: 4096,
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
},
rollupOptions: {
output: {
chunkFileNames: 'static/js/[name]-[hash].js',
entryFileNames: 'static/js/[name]-[hash].js',
assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
manualChunks: {
vue: ['vue', 'vue-router', 'pinia'],
elementPlus: ['element-plus'],
utils: ['lodash-es', 'dayjs', 'axios']
},
experimentalMinChunkSize: 20 * 1024
}
}
}
}
})
六、方案对比与选型建议
6.1 优化策略对比
| 优化策略 | 收益 | 复杂度 | 风险点 | 建议优先级 |
|---|---|---|---|---|
| 打包分析定位瓶颈 | 中 | 低 | 无 | ⭐⭐⭐ 必做 |
| 依赖预构建优化 | 高 | 低 | 无 | ⭐⭐⭐ 必做 |
| 构建缓存 | 高 | 低 | CI 配置 | ⭐⭐⭐ 必做 |
| 关闭 sourcemap | 中 | 低 | 无 | ⭐⭐ 推荐 |
| 精简插件配置 | 中 | 低 | 可能遗漏检查 | ⭐⭐ 推荐 |
| manualChunks 分包 | 高 | 中 | 拆分不当影响缓存 | ⭐⭐⭐ 必做 |
| UI/工具按需引入 | 高 | 低 | 少量样式漏引 | ⭐⭐⭐ 必做 |
| Gzip/Brotli 压缩 | 高 | 低 | 服务端未配则无效 | ⭐⭐⭐ 必做 |
| CDN external | 中-高 | 中 | 版本与可用性风险 | ⭐⭐ 按需 |
| 图片压缩 | 中 | 中 | 质量损失 | ⭐⭐ 推荐 |
| 路由懒加载 | 高 | 低 | 无 | ⭐⭐⭐ 必做 |
| 虚拟模块按需构建 | 高 | 高 | 构建链路复杂 | ⭐ 按需 |
6.2 项目规模选型建议
| 项目规模 | 核心优化项 | 可选优化项 |
|---|---|---|
| 小型项目 (< 100KB) | 按需引入、路由懒加载、terser | 缓存配置 |
| 中型项目 (100KB - 1MB) | + 分包策略、Gzip 压缩 | CDN、图片压缩 |
| 大型项目 (> 1MB) | + CDN external、虚拟模块 | 详细分包、CI 集成 |
6.3 优化效果预期
| 优化策略 | 预期效果 |
|---|---|
| 打包分析定位瓶颈 | 精准识别优化方向 |
| 合理分包 | 减少首屏加载体积,提高缓存命中率 |
| 清除 console/debugger | 减小包体积约 5-10% |
| Gzip/Brotli 压缩 | 减少传输体积约 60%-80% |
| UI 组件按需引入 | Element Plus 从 300KB+ 降至 30KB |
| ECharts 按需引入 | 从 600KB 降至 100KB 左右 |
| 图片压缩 | 根据图片数量可减少 30%-50% |
| CDN 加速 | 减少主包体积,利用浏览器缓存 |
| 路由懒加载 | 按需加载,减少首屏 JS 体积 |
| Tree Shaking | 配合按需引入可减少 80%+ 无用代码 |
七、常见问题与解决方案
问题 1:rollupOptions 配置不生效
排查步骤:
- 删除
node_modules/.vite缓存目录后重新构建 - 检查配置是否被多份
defineConfig覆盖 - 确认当前命令使用的 mode 与条件判断一致
- 检查 Vite 版本,确保配置语法正确
问题 2:CDN 引入后出现全局变量未定义
排查步骤:
- 核对 external 映射变量名(如
vue->Vue) - 确认 HTML 注入顺序(先依赖后业务)
- 检查依赖的二级依赖(如
vue-demi也需要 CDN 引入)
typescript
// vue-demi 也需要 CDN
{
name: 'vue-demi',
var: 'VueDemi',
path: 'https://cdn.jsdelivr.net/npm/vue-demi@0.14.6/lib/index.iife.js'
}
问题 3:按需引入后样式丢失
解决方案:部分 UI 库的样式需要单独引入:
bash
npm install vite-plugin-style-import -D
typescript
// vite.config.ts
import { createStyleImportPlugin, ElementPlusResolve } from 'vite-plugin-style-import'
export default defineConfig({
plugins: [
createStyleImportPlugin({
resolves: [ElementPlusResolve()]
})
]
})
问题 4:vite-plugin-imagemin 安装失败
国内解决方案:
bash
npm install vite-plugin-imagemin -D --registry=https://registry.npmmirror.com
或在 package.json 中添加:
json
{
"resolutions": {
"bin-wrapper": "npm:bin-wrapper-china"
}
}
问题 5:分包后报初始化顺序错误
解决方案:
- 避免过于激进的
manualChunks(id)字符串规则 - 先使用对象分组法(更稳定)
- 对高耦合库做"整组同包"
问题 6:构建后 echarts 样式异常
解决方案:
- 使用完整版 echarts.js 而非按需引入版本
- 确保 echarts 实例不重复初始化
typescript
onMounted(() => {
if (!myChart) {
myChart = echarts.init(container.value)
}
myChart.setOption(option)
})
onUnmounted(() => {
myChart?.dispose()
myChart = null
})
八、优化落地路线图
以下是一套可直接执行的优化路径,建议按顺序实施:
第一阶段:诊断与基线(1-2 天)
- 加入
rollup-plugin-visualizer,完成瓶颈定位并记录基线 - 使用
vite-plugin-inspect检查插件链路
第二阶段:基础优化(2-3 天)
- 上线
manualChunks分域策略 + 路由懒加载 - 落地
unplugin-vue-components与lodash-es按需引入 - 启用
sourcemap: false+drop_console+gzip/br压缩
第三阶段:进阶优化(3-5 天)
- 针对图片密集页引入压缩流程
- 评估是否需要 CDN external
- 配置构建缓存和依赖预构建
第四阶段:持续保障
- 把构建体积检查接入 CI,防止性能回退
- 定期使用 visualizer 检查新引入的依赖
总结
Vite 打包优化是一个系统工程,需要从多个维度综合考虑。本文介绍了三大优化方向:
构建速度优化 :依赖预构建、缓存配置、插件精简、HMR 优化等 10 个技巧,可使构建速度提升 30%-70%。 打包体积优化 :代码分割、Tree Shaking、按需引入等核心策略,可使包体积减少 60%-90%。 资源传输优化:Gzip/Brotli 压缩、图片压缩、CDN 加速等,可使传输体积进一步减少 50%-80%。