构建产物为什么重要

开发时 Vite 用原生 ES Module 秒级启动,但上线跑的是生产构建产物。产物体积直接决定首屏速度:一个 3MB 的 JS 包在弱网下要下载好几秒,用户早就走了。构建优化就是在上线前把产物压到合理范围。

Vite 生产构建底层是 Rollup,负责打包、摇树、压缩。理解构建产物,核心是理解三件事:文件拆得够不够细(分包)、代码删得够不够净(摇树)、文件压得够不够小(压缩)。

优化的衡量标准不是「总包越小越好」,而是「首屏实际加载的字节越少越好」。把首屏不用的代码拆出去、推迟加载,比单纯压缩更有价值。一个 2MB 的包如果拆成首屏 300KB 加按需 1.7MB,体验天差地别。

优化的顺序也很重要:先分包(结构问题),再压缩(体积问题),最后才是缓存(传输问题)。结构没拆好,压缩和缓存都治标不治本。

构建优化的核心目标:首屏只加载首屏需要的字节。
本文核心知识点
  • 动态导入路由级代码分割。
  • manualChunksvendor 分包策略。
  • 压缩gzip / brotli 与 tree-shaking。
  • 指纹contenthash 实现长期缓存。
Vite 构建产物优化配图:蓝调渐变背景上立方体模块拆分重组流程的扁平科技插画
大包拆小包,按需加载,首屏自然快。

代码分割与懒加载

代码分割(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 看体积分布,优先处理最大的几个块。