事情是怎么暴露出来的
首屏 JS 1.2MB 不是一天长出来的。项目上线两年,业务一直在加,组件库一直在换,埋点 SDK 换了两轮,谁也没认真看过打包报告。
真正被揪出来,是因为运营做了一次新用户调研。用户的原话很扎心:
「第一次打开时候,我以为是我网不好,后来发现你们网站就是慢。」
产品拿着这句话来找我们的时候,我顺手跑了一下 Lighthouse——4G 网络模拟下首屏 TTI 接近 8 秒,Performance 评分 39。
先看清楚敌人长什么样
第一步没有上来就改配置,而是打开了 webpack-bundle-analyzer。那张「宇宙飞船图」第一次在大屏上放出来的时候,全组都沉默了几秒。
- 某个图表库单包就占了 300KB(而且我们只用到了两种图)
- 有两个日期库同时存在,一个是 moment,一个是 dayjs
- 有一段老的富文本编辑器,已经没人能说清楚哪里还在用
我们把这张图打印出来,贴在了工位后面的白板上,旁边用红笔写了一句:
「先把肉眼可见的浪费砍掉,再谈高级优化。」
第一轮:粗暴砍依赖(1.2MB → 720KB)
第一轮我们基本没动构建配置,只干了三件事:
- 把整包引入的图表库换成了
echarts/core按需引入,只保留折线图和柱状图。 - 全局搜索 moment 的使用,能换成原生 Date 的就直接换,其余用 dayjs 统一替代。
- 把富文本编辑器拆出去了,做成异步加载,只有在配置公告时才加载。
这一轮做完,首屏 JS 体积从 1.2MB 掉到了 720KB。Lighthouse 分数直接涨到了 62。 产品那边第一次在群里发了句:
「确实快了一点,至少不会卡一整条 loading 条了。」
第二轮:拆路由、拆首屏(720KB → 420KB)
720KB 还是偏大,但我们已经把「最肥的肉」砍了一轮,接下来只能动结构了。
1. 路由级别的代码分割
原来的路由写法是传统的:
import Dashboard from './pages/Dashboard'; import UserCenter from './pages/UserCenter'; // ... 我们统一改成了动态 import:
const Dashboard = lazy(() => import('./pages/Dashboard')); const UserCenter = lazy(() => import('./pages/UserCenter'));并且刻意把首屏只保留「仪表盘」,其余子模块全拆到子 chunk 里。
2. 首页组件瘦身
仪表盘本身也很肥。各种图表、统计卡片、活动公告都堆在一起。 产品一开始不太愿意砍,我们后来给了一个很直观的对比:
- 版本 A:首屏所有模块都加载完,TTI 6 秒
- 版本 B:只保留「今日关键指标 + 待办」,其余模块懒加载,TTI 2.8 秒
最后达成的折中方案是: 首屏只保留「关键指标 + 待办 + 一个折线图」,其余模块在用户滚动到对应区域时再加载。
第三轮:一些容易被忽略的小细节
后面我们又做了几件小事,单看收益不大,但叠加起来效果还不错:
- 把几个「一次性弹出」的引导组件改成了运行时动态 import。
- 把 SVG 图标从某个第三方库换成了自建的 iconfont,减少了一半的 icon 体积。
- 给 webpack 配了
splitChunks.cacheGroups,把基础库单独抽出来缓存。
做完这些之后,首屏 JS 体积最终稳定在 420KB 左右,Lighthouse 的 Performance 分数在我们常见的机型上能跑到 82~88。
这件事对团队的影响
减包这件事做完之后,最明显的变化不是「页面变快了」,而是: 再有人想引一个新库的时候,会下意识先看一眼它的体积。
我们还在 PR 模板里加了一行:
- [ ] 是否引入了新依赖?如果是,请在描述里写清楚原因和体积影响。一开始大家觉得这行很烦,但过了两个月,已经成了一个天然的过滤器:很多「只是为了图省事」的依赖,引都懒得引了。
最后的感受
现在再看那次减包,技术上其实没有多高深,很多操作都很常规。真正难的地方,在于说服自己和团队: 这不是一波「性能优化 KPI」,而是一件会长期改变我们写前端方式的事。
如果你们的首屏包现在也已经奔着 1MB 去了,不妨先别立什么「性能优化 OKR」,先把 bundle-analyzer 打开,在大屏上投一次。 把那张图贴到墙上,比任何性能专项动员会都更有说服力。
评论区