1. 起因
老项目首屏加载一直被吐槽慢,用 webpack-bundle-analyzer 一看,主 bundle 将近 3MB,排查下来发现是几个很常见但容易被忽略的问题堆在一起,记一下排查思路和实际改动。
2. 先用分析工具看清楚体积都花在哪
1 | npm install -D webpack-bundle-analyzer |
1 | // webpack.config.js |
跑完之后会打开一个可视化页面,方块面积代表体积大小。当时一眼就看到几个问题:moment.js 占了将近 500KB(因为默认打包了全部语言包),lodash 被整体引入而不是按需引入,还有一个第三方图表库被重复打包了两份(一份在 node_modules 顶层,一份在某个依赖的嵌套 node_modules 里,版本号不一致导致没法去重)。不看这个图,光凭猜是很难定位到具体是哪个包的问题。
3. moment.js 语言包的坑
1 | // 之前 |
moment 默认会把所有语言包一起打进去,哪怕项目只用中文。修法是用 webpack 自带的 IgnorePlugin 排除掉用不到的语言:
1 | const webpack = require('webpack'); |
需要哪个语言再手动 import 'moment/locale/zh-cn'。这一个改动直接省了 400KB 出头,是当时性价比最高的一步。(后来这个项目干脆把 moment 换成了 dayjs,体积从几百 KB 降到几 KB,如果是新项目,建议一开始就别选 moment。)
4. lodash 按需引入
1 | // 之前:整个 lodash 都被打进去了 |
或者用 babel-plugin-lodash 自动转换,不用每处手动改 import 路径。项目里当时只用了 debounce、throttle、cloneDeep 三个函数,但完整引入 lodash 打包进去了整个库,这种”拿了个牛刀切个葱”的引入方式在老项目里特别常见,值得专门搜一遍代码里所有 import _ from 'lodash'。
5. splitChunks 配置没生效
项目一开始也配了 splitChunks,但因为配置写得不对,实际没起作用:
1 | // 之前,缺少 chunks: 'all',默认只对异步加载的模块生效 |
splitChunks.chunks 默认值是 'async',只会对通过 import() 动态引入的模块做分包,同步 import 引入的第三方库根本不会被这条规则命中,等于配置形同虚设。加上 chunks: 'all' 之后才真正把 node_modules 里的依赖单独拆出一个 vendor chunk:
1 | optimization: { |
拆出 vendor chunk 之后,业务代码变动时用户不需要重新下载没变化的第三方库部分,配合浏览器缓存,二次访问的加载速度提升更明显。
6. 小结
体积优化这件事最容易踩的坑不是”不知道怎么优化”,而是”没工具就靠猜”。webpack-bundle-analyzer 是第一步必做的事,看清楚体积具体花在哪之后,剩下的基本就是按需引入、干掉重复依赖、把 splitChunks 配置对这几件常规操作。这次优化下来主 bundle 从将近 3MB 降到 1.1MB 左右,首屏时间肉眼可见地变快了。