Day 93 · 产物优化 — bundle 分析与首屏瘦身:数字说话的收官
昨天上线成功,今天给它"称重":bundle 可视化分析找出体积大头、按需引入审计揪出漏网的整包 import、首屏指标建立优化前后的量化对比。今天的原则是"预算制"——设定目标、达标即停,不追求极致(优化兔子洞是收官周最大的时间黑洞)。
目录
一、bundle 可视化分析
1.1 安装与配置
npm install -D rollup-plugin-visualizer
// vite.config.ts
import { visualizer } from "rollup-plugin-visualizer";
export default defineConfig({
plugins: [
vue(),
// 构建时生成 stats.html(打开即见各模块体积占比的可交互图)
visualizer({ open: true, gzipSize: true, filename: "stats.html" }),
],
});
npm run build # 自动打开 stats.html——太阳图里每一块都是体积
1.2 读图要点(预期的大头)
预期分布(优化前的典型形态):
echarts —— 800KB+(若整包引入)/ 300KB(按需后)
vue 全家桶 —— ~100KB(runtime + router + pinia)
业务代码 —— <100KB(三屏 + 组件)
佐证懒加载生效:三屏应是独立 chunk(Day 78 的成果)
二、ECharts 按需引入审计
2.1 最常见的漏网现场
第 8 周按需注册过(Day 53),但 Vue 迁移时容易把注册模块写成整包:
// ❌ 审计目标:这种写法把整包拉进 bundle
import * as echarts from "echarts";
// ✅ 正确:按需注册模块(第 8 周的 echarts-custom.ts 原样迁入)
// src/core/echarts-custom.ts
import * as echarts from "echarts/core";
import { LineChart, BarChart, GaugeChart, RadarChart } from "echarts/charts";
import { GridComponent, TooltipComponent, LegendComponent, MarkLineComponent } from "echarts/components";
import { CanvasRenderer } from "echarts/renderers";
echarts.use([
LineChart, BarChart, GaugeChart, RadarChart,
GridComponent, TooltipComponent, LegendComponent, MarkLineComponent,
CanvasRenderer,
]);
echarts.registerTheme("industrial-dark", INDUSTRIAL_DARK_THEME); // Day 55 主题
export default echarts; // 全工程只从这里 import
2.2 审计方法
1. 全局搜索 from "echarts"(不带 /core 的裸包 import)——逐个改为 echarts-custom
2. VChart 组件(Day 76)的 import 源核对
3. 类型引用检查:EChartsOption 从 "echarts" import 类型是安全的
(纯类型编译后消失,不进 bundle)
审计收益预期:整包 → 按需通常省 500KB+(gzip 后 ~150KB),是今天最大的一刀。
三、manualChunks:手动分包策略
3.1 为什么要手动分包
Vite 默认分包较粗(一个 vendor 大块)。大屏的加载策略值得手动控制:
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks(id) {
// 策略:稳定的大依赖独立分块——利用浏览器长缓存
if (id.includes("echarts")) return "echarts";
if (id.includes("node_modules")) return "vendor";
// 业务代码不干预:三屏已由路由懒加载自动分包(Day 78)
},
},
},
},
});
3.2 分包的缓存逻辑
发版后:
vendor-abc123.js 内容没变 → hash 没变 → 用户浏览器直接用缓存 ✅
index-def456.js 业务变了 → hash 变 → 重新下载(只下载这个小块)
不分包时:任何改动 → 整个大包重新下载
这也是面试题:“你们怎么优化首屏/缓存?”——懒加载(Day 78)+ 按需(今天)+ 分包(今天)三件套是完整答案。
四、首屏指标与优化对比
4.1 指标采集
# Lighthouse(Chrome DevTools 自带):
# 对线上地址跑 Performance 审计(移动端模拟模式)
4.2 优化前后对比表(今天的主产出)
| 指标 | 优化前 | 优化后 | 说明 |
|---|---|---|---|
| 总产物体积 | ___ | ___ | dist 文件夹 |
| 首屏 chunk(index + vendor) | ___ | ___ | 懒加载的屏不算 |
| echarts chunk | ___ | ___ | 按需审计的收益 |
| gzip 后首屏 | ___ | ___ | 传输体积 |
| FCP(首次内容绘制) | ___ | ___ | Lighthouse |
| LCP(最大内容绘制) | ___ | ___ | 大屏首屏=屏 A |
| TTI(可交互时间) | ___ | ___ | 轮播启动 |
填表纪律:优化前数据先采集(改一行代码之前)——没有基线的优化是自嗨。
五、优化预算与止损纪律
5.1 本周的预算制
达标线(够了就停):
□ 首屏(含 vendor)gzip ≤ 200KB
□ echarts chunk gzip ≤ 120KB(按需后合理区间)
□ LCP ≤ 2.5s(普通网络移动端)
超出预算才继续挖;达标立即转 Day 94 的资产工作
5.2 明确不做的优化(记录原因,防未来乱入)
✗ Tree-shaking 骚操作/极致压缩——边际收益 < 维护成本
✗ SSR/SSG——大屏无 SEO 需求,纯负收益
✗ Web Worker 拆数据链路——当前数据量(20 设备 2s 快照)远未到瓶颈
✗ Map 深侦听换引用方案(Day 81 遗留)——触发条件未到(设备 > 50),
写清阈值进待办即可(Day 85 边界文档的复审条件节)
"不做什么"的清单与"做了什么"同样有说服力——止损判断力是工程成熟度的标志。
六、常见坑点
坑 1:类型 import 混淆导致整包误判
import type { EChartsOption } from "echarts" 是纯类型(安全);import { EChartsOption } from "echarts"(无 type)在 verbatimModuleSyntax 关闭时会拉运行时。规范:类型一律 import type。
坑 2:manualChunks 切太碎
每个 npm 包一个 chunk → 请求数爆炸(HTTP/1.1 场景更糟)。粒度原则:只拆"大且稳定"的依赖(echarts 级别),小的归 vendor。
坑 3:优化后路由懒加载失效
manualChunks 把屏组件错误归入大块(判断条件写宽了)→ 三屏 chunk 消失。验证:build 输出里 ScreenA/B/C 的独立 js 仍在。
坑 4:只看体积不看加载时序
首屏 chunk 小了但被 waterfall 阻塞(CSS/字体阻塞渲染)。Lighthouse 的 Opportunities 面板会指路——体积是手段,指标是目的。
坑 5:本地 Lighthouse 数据当真
本机网络/性能偏好影响跑分。以线上地址 + 无痕窗口 + 三遍取中位为准(Day 84 跑分纪律的延伸)。
七、自测挑战
T1 · 分析与审计(60 分钟)
完成第一至二节:stats.html 生成、整包 import 全部清剿、echarts chunk 体积达标。
T2 · 分包与对比表(50 分钟)
完成第三至四节:manualChunks 生效(hash 稳定性验证:改业务代码重 build,vendor hash 不变)、优化前后对比表填满。
T3 · 止损自评(10 分钟)
对照 5.2 的"不做清单",检查自己有没有掉进兔子洞(超预算还在挖)——写下你今天的止损点,这是给自己交付纪律的存证。
八、总结
| 环节 | 要点 |
|---|---|
| 可视化 | visualizer 太阳图——先看再动 |
| 按需审计 | 裸包 import 清剿,只从 echarts-custom 进 |
| 分包 | 大而稳定的依赖独立——hash 缓存红利 |
| 基线 | 优化前先采集,对比表是主产出 |
| 预算制 | 达标即停 + "不做"清单——止损判断力 |
数字达标了。明天把数字和画面一起装进 README:演示资产更新日。