李涛自留地
回到顶部
首页 > 前端 > vite打包优化实战:分包策略、依赖预构建与产物体积排查

vite打包优化实战:分包策略、依赖预构建与产物体积排查

文章目录

vite打包优化真正难的地方不在配置本身,而在判断哪一步值得改。构建慢、首屏白、chunk 碎得厉害,症状不同,该动的位置也不同。这篇按分包、预构建、产物体积排查三条线走一遍,给出能直接跑的命令与判断依据,也会写清哪些结论依赖项目形态,不能照搬。

先量一遍再动手

打开构建日志之前,先确认自己在解决什么问题。构建耗时、首屏体积、缓存命中率是三个不同指标,对应的手段几乎不重叠,混在一起谈容易得出错误结论。

机箱内 M.2 槽位上的固态硬盘与散热片,vite打包优化

把 vite build 的耗时拆成 transform、chunk 渲染、产物写入几段来看。--debug 打印的是解析后的配置与 resolve 细节,没有按插件汇总的耗时表,输出量很大,先重定向到文件再读。

npx vite build --debug > build-debug.log 2>&1

要看单个插件的耗时,得打开 Rollup 的 perf:把 build.rollupOptions.perf 设为 true,构建结束会打印各 hook 的耗时,几百毫秒以上的条目通常就是大头。改配置之前留一份基线,否则无法判断改动是否有效。

产物体积要看 treemap,而不是终端里那一串数字。装上 rollup-plugin-visualizer,在 vite.config 里 import { visualizer } 后加进 plugins,参数写 visualizer({ filename: 'stats.html', gzipSize: true, template: 'treemap' }),构建结束就生成 stats.html,打开即可按 chunk 展开模块占比。被整包引入的库在图上非常显眼。这份文件本地产出即可,不必提交仓库。

单页应用与多入口项目的看法不同。前者盯首屏 chunk 覆盖了多少字节,后者看入口之间的公共依赖有没有被重复拉取。量法不同,结论也不同,这也是 vite打包优化没有通用模板的原因。

基线要落在同一台机器、同一块盘上。SSD 读写快,重复构建时磁盘等待不会混进耗时;换成慢盘,构建被 I/O 等待拉长,容易把磁盘开销误算到插件和 transform 头上。

到这里,vite打包优化的第一步算是有了可比较的基线。后面每一次改动,都拿同一份基线对照,比较容易判断收益来自哪里。

manualChunks 分包:按变更频率切,不按目录切

manualChunks 是 vite打包优化里最常被改动的一处。它的作用是告诉 Rollup 哪些模块合并进同一个 chunk,切分依据应该是变更频率与加载时机,而不是 src 下的目录结构。按目录切,经常切出一堆几百字节的碎片,请求数反而变多。

// vite.config.js
export default {
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          const p = id.replace(/\\/g, '/')
          if (!p.includes('/node_modules/')) return
          if (p.includes('/echarts/')) return 'vendor-charts'
          return 'vendor'
        },
      },
    },
  },
}

函数式写法比对象写法灵活,但 id 是绝对路径,纯字符串匹配容易误伤。示例先把反斜杠统一成正斜杠,Windows 与 macOS 都能命中;直接 includes('node_modules') 还可能撞上包名里的同名子串。

第三方库不宜切得太细。一个 vendor 大块,加一到两个体积大、更新少的库单独成块,多数项目够用。切得越碎,并发请求的收益越难抵消额外的解析开销。

依赖节点收拢成分组色块的分层示意,vite打包优化

路由级拆分是另一层决策,靠动态 import 触发,不是 manualChunks 配出来的。用户通常只走两三条主链路,把每条路由都切成独立 chunk,缓存命中率反而下降,文件名一变旧缓存就整片失效。

分包改完要回到构建日志确认 chunk 数量与总体积,而不是只看配置有没有生效。数量翻倍、总体积不变,说明只是把同一批代码挪了位置。

依赖预构建与产物分析:两个容易混淆的环节

依赖预构建常被当成 vite打包优化的万能药,其实它发生在开发阶段。esbuild 把 CommonJS 依赖转成 ESM 并合并请求,缓存落在 node_modules/.vite,影响的是 dev server 冷启动与页面刷新,不改变生产构建的 chunk 划分。

遇到依赖改了不生效、或者报 exports 找不到,先删缓存目录再重建。Windows 下可以用 rd /s /q node_modules\.vite,force 参数会让 Vite 忽略已有缓存重新预构建。

npx vite --force

optimizeDeps.include 用来强制预构建那些被动态引入、扫描不到的包。加进去之前先确认它确实没被入口链路引用,否则只是把问题推迟到运行时报错。

预构建产物不参与生产构建,把它当成压体积的抓手就跑偏了。要减体积,仍得回到 output 配置和模块引入方式,这两处才决定最终 bundle 的形状。

产物分析针对的则是 build 结果。先看 dist/assets 下最大的几个文件,再回到 treemap 里定位来源模块。体积异常通常来自三处:整包引入、同一依赖被打进多个 chunk、sourcemap 误入产物。

重复打包常见于依赖树里出现两个版本。用 npm ls 查某个包能看到重复路径,npm 项目用 overrides 收敛,yarn 项目用 resolutions,两个字段名很容易写混。

把检查写进流水线

一次优化不难,难的是三个月后没人把体积涨回去。把产物体积写进 CI 检查,超过阈值就让流水线失败,比写一份规范文档有效得多。

多屏工作台旁的交换机与端口指示灯,vite打包优化

阈值不要拍脑袋定。先记录当前基线,按 chunk 分别设上限,允许小幅波动。gzip 后的体积更接近真实传输成本,用 gzip-size 统计比看原始字节准。

阈值卡得太紧,团队会习惯性调高上限,检查很快形同虚设。留出合理余量,只在明显超标时拦截,vite打包优化的规则才守得住。

这套检查落地之后,vite打包优化的讨论就从主观感受变成数字对比,评审时也容易达成一致,不必反复争论某个库该不该拆。

几个容易踩的坑

external 与 manualChunks 同时使用时,被 external 的模块不会进 chunk。配置看着生效,产物里却没有,排查时容易误判成缓存问题,白折腾一轮。

cssCodeSplit 关掉会把样式合并进单个文件,首屏可能更快,路由多的时候反而拖慢。关与不关,取决于首屏页面和后续页面的访问比例,没有通用答案。

动态 import 上的 webpackChunkName 注释在 Vite 里不起作用,Rollup 走自己的 chunk 命名规则。两套写法混用,命名预期会落空,线上缓存策略也会跟着失准。

分包边界一动,缓存策略、CDN 回源、监控埋点的假设都要复核一遍。改完配置跑一次完整回归,比事后从监控图上猜要快。

这几个坑在 vite打包优化的复盘里出现频率很高,多数不是配置写错,而是没弄清哪一步由谁负责。

若非作者标注皆为李涛自留地原创文章,转载或复制请以超链接形式并注明出处。