构建产物为什么重要
开发时 Vite 用原生 ES Module 秒级启动,但上线跑的是生产构建产物。产物体积直接决定首屏速度:一个 3MB 的 JS 包在弱网下要下载好几秒,用户早就走了。构建优化就是在上线前把产物压到合理范围。
Vite 生产构建底层是 Rollup,负责打包、摇树、压缩。理解构建产物,核心是理解三件事:文件拆得够不够细(分包)、代码删得够不够净(摇树)、文件压得够不够小(压缩)。
优化的衡量标准不是「总包越小越好」,而是「首屏实际加载的字节越少越好」。把首屏不用的代码拆出去、推迟加载,比单纯压缩更有价值。一个 2MB 的包如果拆成首屏 300KB 加按需 1.7MB,体验天差地别。
优化的顺序也很重要:先分包(结构问题),再压缩(体积问题),最后才是缓存(传输问题)。结构没拆好,压缩和缓存都治标不治本。
构建优化的核心目标:首屏只加载首屏需要的字节。
- 动态导入路由级代码分割。
- manualChunksvendor 分包策略。
- 压缩gzip / brotli 与 tree-shaking。
- 指纹contenthash 实现长期缓存。
代码分割与懒加载
代码分割(Code Splitting)是构建优化的第一刀。Vite 对动态 import() 天然支持:把 import() 写的模块单独打成一个 chunk,用户访问到对应路由才下载。
// 路由级懒加载(Vue 示例)
const routes = [
{
path: "/dashboard",
component: () => import("@/views/Dashboard.vue")
},
{
path: "/reports",
component: () => import("@/views/Reports.vue")
}
];
// 组件级懒加载:弹窗等低频组件
const RichEditor = defineAsyncComponent(
() => import("@/components/RichEditor.vue")
);
React 用 React.lazy + Suspense 实现同样效果。原则是:首屏不出现的组件一律动态导入,图标库按需引入,第三方库能拆则拆。
vendor 分包策略
默认情况下 Vite 会把 node_modules 里的依赖打进 vendor chunk。如果依赖很多,vendor 可能一两 MB。更精细的做法是用 manualChunks 把大依赖单独拆出来,让它们各得其所,互不影响缓存。
// vite.config.js
export default {
build: {
rollupOptions: {
output: {
manualChunks: {
vue: ["vue", "vue-router", "pinia"],
echarts: ["echarts"],
lodash: ["lodash-es"]
}
}
}
}
};
分包的收益在缓存:vue 升级只换 vue chunk,其他 chunk 指纹不变,用户不用重新下载全部。注意别把包拆得过于零碎,几百个 2KB 的小文件反而增加请求数,得不偿失。
压缩与体积治理
Rollup 默认用 esbuild 或 terser 压缩 JS,删注释、缩变量、去死代码。CSS 用 lightningcss 或 postcss 压缩。服务端再开 gzip / brotli 传输压缩,JS 体积通常能再降 60% 以上。
// vite.config.js
export default {
build: {
minify: "esbuild", // 快;terser 更彻底
cssCodeSplit: true,
target: "es2018",
reportCompressedSize: true,
}
};
体积治理的常见手段还有:用 dayjs 替代 moment(省几百 KB)、lodash 按需导入、图片转 webp、字体子集化。大依赖用 source-map-explorer 或 rollup-plugin-visualizer 看体积分布,优先处理最大的几个块。
缓存指纹与长期缓存
构建产物文件名带 contenthash(内容哈希),内容变了文件名就变。配好 HTTP 缓存头,浏览器就能长期缓存不变的资源,只重新下载变化的部分。
// vite.config.js
export default {
build: {
rollupOptions: {
output: {
entryFileNames: "assets/[name]-[hash].js",
chunkFileNames: "assets/[name]-[hash].js",
assetFileNames: "assets/[name]-[hash].[ext]"
}
}
}
};
服务器端配合设置:带 hash 的资源设 Cache-Control: immutable,一年不过期;index.html 设 no-cache,每次都要校验。这样发布新版本时,用户只下载变更的 chunk,其余全部命中缓存,二次访问几乎秒开。
构建分析与监控
| 工具 | 作用 |
|---|---|
| rollup-plugin-visualizer | 看各 chunk 体积分布图 |
| vite-bundle-visualizer | 依赖关系与体积可视化 |
| web-vitals | 上线后测真实加载指标 |
| CI 体积预算 | 超阈值构建失败拦截 |
# CI 里跑构建体积检查,超预算直接失败
node scripts/check-bundle-size.mjs --max-gzip 300KB
构建优化要建立基线,改了代码能看出是变好还是变坏。
构建优化实战清单
- 路由级、组件级懒加载,首屏只加载首屏代码。
- 大依赖用 manualChunks 单独分包。
- esbuild 压缩 + 服务端 gzip / brotli。
- contenthash 文件名 + 长期缓存头。
- CI 里设体积预算,防止回退。
构建产物干净,首屏体验才有保障。