Vite SPA 首屏性能预算:测量真实依赖闭包
利用 Vite manifest 计算 SPA 首屏真实静态依赖闭包,并设置 JavaScript 与 CSS gzip 预算,拦截总构建体积或单 chunk 检查遗漏的性能回归。
一次生产构建生成了数 MB JavaScript。完成拆包后,应用入口文件的压缩体积明显下降。两个数字都没有回答实际问题:未登录用户要下载多少代码,登录页才能进入可用状态?
一套经过脱敏的企业单页应用暴露了这个差异。原始构建在启动阶段同步注册数十个路由页面和完整组件库。第一轮优化大幅缩小了入口 chunk,也就是构建工具生成的一份代码块;这份文件却不再包含首屏路由、共享依赖和对应样式。若直接把入口体积当成结果,就会高估优化幅度。
最终方案把路由级懒加载和 Vite 构建清单预算结合起来。预算同时覆盖应用入口、指定首屏路由,以及从这两个根节点可达的全部静态 JavaScript 和 CSS 依赖。暂缓加载的业务页面不进入首屏集合。
表面问题是含义不清的构建数字
三种常见指标回答的是不同问题:
| 指标 | 能回答什么 | 会遗漏什么 |
|---|---|---|
| 总构建体积 | 本次构建生成了多少文件 | 首屏实际请求哪些文件 |
| 入口 chunk 体积 | 某个入口文件有多大 | 共享依赖和首屏动态入口 |
| 最大 chunk 体积 | 哪个单独产物最重 | 一条用户路径所需依赖的合计成本 |
原始应用让全部页面跟随启动同步加载,入口文件勉强能够反映首屏成本,同时也携带了登录用户用不到的代码。路由组件改为动态导入后,入口文件按预期变小;此时只检查该文件又会遗漏首屏必需依赖。
测量失真的根因是没有定义用户路径。“首屏 JavaScript”应表示某个指定页面的静态依赖闭包,也就是从选定入口出发、沿静态导入关系能够到达的全部文件集合。
先缩小启动依赖图,再设置预算
实现先把路由元数据和路由组件加载分开。路由名称、路径、访问规则和导航结构仍可在启动时存在;每个页面组件改为返回动态导入结果的函数,只在导航选中该页面时加载。
其他启动依赖采用相同原则:
- UI 组件及样式按需解析,避免全局安装完整组件库;
- 默认语言随启动提供,其他语言在用户选择后加载;
- 地图等重型功能库留在真正使用它们的页面中;
- 生产构建输出 manifest,让生成文件名和导入关系可以由程序读取。
Vite 官方文档说明,动态导入会形成独立 chunk;构建 manifest 会记录入口、动态入口、静态 imports、动态导入和关联 CSS。结合两项能力后,检查器不必依赖每次构建都会变化的哈希文件名,也能还原启动路径(Vite 功能文档,Vite 后端集成文档)。
只遍历首屏需要的依赖
首屏预算选择两个根节点:
- 启动框架、路由、状态和默认样式的应用入口;
- 未登录导航会立即选择的登录页构建入口。
检查器读取每个根节点的 JavaScript 文件和 CSS 列表,再沿 imports 字段递归。已访问集合能够防止共享 chunk 被重复计数。
collect(root):
已访问时返回
记录已访问
加入 root.file
加入 root.css
遍历 root.imports:
collect(child)
collect(应用入口)
collect(首屏路由入口)
检查器不会遍历所有 dynamicImports 边。全部展开会把延迟加载的业务页面重新计入首屏,懒加载形成的边界也会失去意义。若另一条公开路由本来就属于启动过程,应把它显式加入根节点集合。
根节点缺失或 manifest 引用不存在时,检查直接失败。若悄悄按零处理,路由重命名或意外内联都可能得到失真的绿色结果。
分开限制 JavaScript 与 CSS 的压缩体积
检查器读取构建产物,用同一个 gzip 实现逐文件压缩,再分别汇总去重后的 JavaScript 和 CSS。Node 的 gzipSync 可以接收字节缓冲区并返回 gzip 压缩结果,因此构建任务能稳定复算该指标(Node.js zlib 文档)。
两类资源需要独立上限。合并预算可能让 JavaScript 增长被较小的样式体积掩盖,也可能在 JavaScript 下降时放任 CSS 膨胀。脱敏构建为两类资源分别设置固定上限,任一超限都会让命令以非零状态退出。
重构后,选定首屏依赖图的 JavaScript gzip 体积约为 202 KB,上限为 350 KB;CSS gzip 体积约为 16.5 KB,上限为 50 KB。入口 chunk 本身远小于完整首屏集合,也再次说明单文件不能代替整条路径的指标。
在同一交付链路验证行为与性能
体积检查通过仍不足以证明应用可用。最终验证覆盖了多种边界:
- 单元测试覆盖路由契约、语言加载、认证错误和登录恢复;
- 生产构建与 manifest 生成成功;
- 构建产物通过静态依赖闭包预算;
- 浏览器测试覆盖桌面与移动端登录流程;
- 移动端没有横向溢出,主要操作位于首个视口内;
- 发布工作流调用与本地一致的验证入口。
这套顺序针对两类不同失败。路由测试和浏览器测试保护拆包后的行为;manifest 预算保护打包后的资源依赖图。缺少任何一侧,都会留下未验证的可用性或性能风险。
适用边界
压缩传输体积只是加载性能的一部分。它无法代表网络延迟、缓存状态、JavaScript 解析和执行时间、渲染成本、接口响应时间,也不能反映低性能设备的主线程压力。gzip 结果还会与 Brotli 和生产 CDN 的实际行为存在差异。
根节点必须符合真实的未登录访问路径。使用服务端渲染、Service Worker、条件启动模块,或拥有多个同等常见落地页的应用,需要扩展测量模型。有条件时,静态预算还应结合浏览器时序或真实用户监控。
可复用结论是把一条用户路径作为依赖图来优化和约束:在真实导航边界拆分代码,选定首屏必需根节点,递归统计静态导入,每个构建文件只计一次,再把预算放进同时保护应用行为的统一验证链路。
先说明系统,再说明症状
如果需要生产排障、DevOps 交付或物流系统集成协作,请提供当前表现、预期结果、受影响环境、可用日志或数据样例,以及发布限制。我会从现有证据开始判断。
通过邮件开始