学完这篇,你能拿到一套可直接照抄的 Vite + Vue 3 配置,把打包产物从几 MB 压到几百 KB,并且知道每一步为什么要做。
第一步:先量体积,别瞎猜
优化前先看清楚钱花在哪。装可视化分析插件:
npm i -D rollup-plugin-visualizer
// vite.config.js
import { visualizer } from 'rollup-plugin-visualizer'
export default {
plugins: [visualizer({ gzipSize: true, open: true })]
}
打包后会自动弹出 stats.html,按 gzip 体积排序,哪个包最大一目了然。后面所有优化都围绕这张图展开。
注意:看 gzip 后的数字,不要看原始体积,浏览器实际传输的是压缩后的。
第二步:依赖瘦身(方法 2-4)
方法 2:组件库按需引入。 全量引入 Element Plus / Ant Design Vue 通常占 500KB+。用:
npm i -D unplugin-vue-components unplugin-auto-import
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
plugins: [Components({ resolvers: [ElementPlusResolver()] })]
同时删掉 main.js 里的 app.use(ElementPlus) 和整包 CSS 引入。
方法 3:替换体积过大的旧库。 moment 是重灾区(带语言包 200KB+),换 dayjs(2KB)。lodash 换成 lodash-es,只 import 用到的函数:import { debounce } from 'lodash-es'。
方法 4:砍掉没用到的功能。 import * as echarts from 'echarts' 会全量引入图表类型,改成按需注册:
import * as echarts from 'echarts/core'
import { LineChart } from 'echarts/charts'
import { GridComponent } from 'echarts/components'
echarts.use([LineChart, GridComponent])
体积能从 1MB 降到 300KB 左右。
第三步:代码分割(方法 5-7)
方法 5:路由懒加载。 所有页面级组件都别静态 import:
const routes = [
{ path: '/about', component: () => import('@/views/About.vue') }
]
方法 6:大组件异步加载。 富文本编辑器、图表弹窗、地图这类只在特定交互出现的组件,用 defineAsyncComponent 包一层:
const Editor = defineAsyncComponent(() => import('@/components/Editor.vue'))
方法 7:手动拆 vendor。 把不常变的第三方库拆出独立 chunk,利用浏览器长期缓存:
build: {
rollupOptions: {
output: {
manualChunks: {
vue: ['vue', 'vue-router', 'pinia'],
ui: ['element-plus']
}
}
}
}
注意:manualChunks 拆太碎会产生大量小文件,反而增加请求数,控制在 3-5 个 vendor 组比较合适。
第四步:确保 Tree-shaking 真正生效(方法 8-9)
方法 8:检查模块格式。 Tree-shaking 只对 ESM 有效。确认依赖的 package.json 里有 "module" 或 "exports" 指向 ESM 版本;自己的 package.json 加上 "sideEffects": ["*.css"],让打包器敢删无用代码。避免 require() 和动态拼接的 import 路径,那会让静态分析失效。
方法 9:提高 build.target。 Vite 默认要兼容较老的浏览器,会做大量降级转换:
build: { target: 'es2015' } // 或 es2020
如果能放弃 IE 和很老的机型,target 提到 es2020 能显著减少 helper 注入。
注意:@vitejs/plugin-legacy 会额外生成一套 polyfill 产物,如果不需要兼容老浏览器,直接不装。
第五步:资源与输出压缩(方法 10-12)
方法 10:关闭 sourcemap、剥掉调试代码。
build: {
sourcemap: false,
minify: 'terser',
terserOptions: { compress: { drop_console: true, drop_debugger: true } }
}
用 esbuild 压缩更快,但要完整移除 console 建议用 terser。
方法 11:图片优化。 小图用 base64 内联(默认 4KB 阈值,可调 assetsInlineLimit),大图压成 WebP,图标统一走 SVG 或 iconfont,别塞 PNG 精灵图。
方法 12:开启 gzip / brotli 预压缩。
npm i -D vite-plugin-compression
import viteCompression from 'vite-plugin-compression'
plugins: [viteCompression({ algorithm: 'brotliCompress', threshold: 10240 })]
服务端同步开启对应压缩模块(Nginx 的 ngx_brotli 或 gzip_static on)。
注意:插件生成的是 .br / .gz 静态文件,服务端必须配置才能命中,否则等于白压。
小结
- 先跑 visualizer,按 gzip 体积排序决定先动谁,别凭感觉优化。
- 收益最大的三件事:组件库按需引入、路由懒加载、大库替换(moment → dayjs、echarts 按需)。
- Tree-shaking 不是自动生效的,要 ESM +
sideEffects 声明配合,CJS 依赖是拦路虎。
- vendor 拆分控制在 3-5 组,拆太碎反而拖慢加载。
- 压缩插件只负责生成
.gz/.br 文件,服务端不配置就完全无效。
- 每次改完重新跑一次 visualizer 对比,优化要能看到具体数字。