构建速度直接影响开发体验和上线效率。一个中等规模的 Vue 项目,页面几十个、依赖几十个,如果不做任何优化,冷启动可能要十几秒,生产打包可能要三五分钟,CI/CD 流水线上的等待时间更是让人焦虑。好消息是,Vite 的插件机制天生就是为这类问题设计的,从打包分析、按需加载到代码分割,都有成熟的插件方案可以拿来就用。本文按照实际优化的操作顺序,带你走完一条完整的提速路径。

先做打包分析:搞清楚体积到底花在哪
优化的第一步永远是测量,而不是瞎猜。很多开发者一上来就删依赖、换组件库,折腾半天发现瓶颈根本不在那里。正确的做法是先引入打包分析工具,用数据说话。rollup-plugin-visualizer 是目前 Vite 生态里最常用的分析插件,它会在构建结束后生成一份可视化的体积报告,把每个模块占用的空间以矩形树图的形式展示出来,谁占大头一目了然。
接入方式很简单,先安装依赖,然后在 vite.config.js 中注册:
// npm i rollup-plugin-visualizer -D
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
plugins: [
vue(),
visualizer({
open: true, // 构建完成后自动打开报告页面
gzipSize: true, // 显示 gzip 压缩后的体积
brotliSize: true, // 显示 brotli 压缩后的体积
filename: 'stats.html',
}),
],
});
拿到报告之后重点关注三类内容:第一类是体积异常大的第三方库,比如 moment.js 连同语言包动辄几百 KB,lodash 未按需引入时也会整体打进包里;第二类是重复打包,同一个库被多个 chunk 各自引用一份;第三类是本应被分割却挤在主包里的业务代码。分析结果会直接决定后续采用哪种优化手段,比如发现组件库全量引入,就去做按需加载;发现大工具库整体打包,就换成按路径引入或寻找替代品。
按需引入:用插件砍掉不必要的代码
按需引入是投入产出比最高的优化手段。Element Plus、Ant Design Vue 这类组件库,全量引入会让主包膨胀几百 KB,而实际用到的组件可能只有十几个。unplugin-vue-components 配合 unplugin-auto-import 可以做到完全无感:模板里直接写组件标签,插件在编译阶段自动完成注册和样式引入,不需要手动 import,也不需要全局注册。
// npm i unplugin-vue-components unplugin-auto-import -D
import AutoImport from 'unplugin-auto-import/vite';
import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
export default defineConfig({
plugins: [
vue(),
AutoImport({
imports: ['vue', 'vue-router', 'pinia'], // 自动导入 ref、computed 等 API
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()], // 模板中的组件自动按需引入并加载样式
}),
],
});
这套方案的优势在于零心智负担:写代码时和全量引入的体验完全一致,但产物里只包含真正用到的部分。以 Element Plus 为例,按需引入后主包通常能减少 200KB 以上。类似的思路也适用于图标库,unplugin-icons 配合自动导入,可以让 element-plus 的图标按需编译,避免一次性引入上千个 SVG。
除了组件库,工具函数库同样值得处理。lodash 改为从 lodash-es 按路径引入,或者干脆用原生方法替代;日期处理如果只需要基础格式化,用 dayjs 替换 moment 能省下绝大部分体积。另外要提醒一点:tree-shaking 依赖于 ES 模块的静态结构,如果某个包只提供 CommonJS 版本,Vite 就无法有效摇树,这时要么寻找 ESM 版本,要么用 Vite 的 optimizeDeps.include 强制预构建后再处理。
代码分割:把大包拆成按需加载的小块
按需引入解决的是「打进来的代码少」,代码分割解决的是「打进来之后怎么分发」。Vite 底层使用 Rollup 打包,通过配置 build.rollupOptions.output.manualChunks 可以精细控制拆包策略。核心思路是把变动频率不同的代码分开:第三方依赖基本不变,可以长期缓存;业务代码频繁改动,单独拆出避免每次发版都让用户重新下载所有资源。
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
if (id.includes('element-plus') || id.includes('@element-plus')) {
return 'element-plus';
}
if (id.includes('echarts')) {
return 'echarts'; // echarts 体积大,独立分包便于缓存
}
return 'vendor'; // 其余第三方统一放到 vendor
}
},
},
},
},
});
除了手动分包,路由级别的动态加载也必须做。Vue Router 配合动态 import,可以把每个页面的代码分割成独立 chunk,用户访问到哪个页面才加载哪个文件:
const routes = [
{
path: '/report',
// 路由懒加载:report 页面会被拆成单独的 chunk
component: () => import('@/views/Report.vue'),
},
];
对于特别重的组件,比如富文本编辑器、图表库、JSON 预览器,同样建议用动态 import 包一层,在用户真正打开对应弹窗或切换到对应标签页时再加载。另外,echarts 这类库还可以借助按需注册进一步瘦身,只引入用到的折线图、柱状图等组件和对应的渲染器,体积能从接近 1MB 压缩到两三百 KB。
进阶手段:压缩、预构建与产物优化
分包和按需引入做完之后,还可以从产物层面继续压榨空间。vite-plugin-compression 可以在构建时生成 gzip 或 brotli 预压缩文件,配合 Nginx 的静态压缩配置,省去服务器实时压缩的 CPU 开销,响应速度也会明显提升:
// npm i vite-plugin-compression -D
import viteCompression from 'vite-plugin-compression';
export default defineConfig({
plugins: [
vue(),
viteCompression({
verbose: true,
algorithm: 'gzip',
threshold: 10240, // 超过 10KB 的文件才压缩
}),
],
});
图片是另一个容易被忽视的大头。vite-plugin-imagemin 可以在构建阶段自动压缩 PNG、JPEG 等静态图片;如果项目允许,把大图转为 WebP 格式通常能再省一半体积。小图标则建议做成雪碧图或直接使用 SVG。
开发阶段的体验优化主要靠依赖预构建。Vite 启动时会把 CommonJS 依赖转换为 ESM 并缓存到 node_modules 下的 .vite 目录,默认只有依赖变化时才重新预构建。遇到启动慢的情况,可以检查 optimizeDeps.include 配置是否遗漏了高频依赖,同时用 vite --debug optimize-deps 观察预构建行为。如果某些冷门依赖总是触发重新预构建导致页面刷新,明确将其加入 include 列表通常能解决。
最后给一份可落地的检查清单:先用 visualizer 生成基线报告;接着配置组件库和 API 的自动按需引入;然后为路由和重组件开启懒加载,通过 manualChunks 把稳定依赖拆到独立 chunk;再加上产物压缩与图片优化;最后再跑一次分析报告对比前后差异。整个流程下来,多数项目的主包体积可以下降一半以上,冷启动时间控制在几秒内,生产构建时间也会有可观的缩短。构建优化没有银弹,但 Vite 插件体系提供的手牌足够多,按这个顺序逐项落地,基本都能拿到满意的结果。