先说个背景。上个月我接手了一个公司内部的运营后台,技术栈是React + Webpack 5,没上SSR,首屏加载从点击到看到完整页面要5秒左右,线上用户反馈已经炸了。我用Chrome DevTools的Performance面板和Lighthouse各跑了一轮,发现主要瓶颈不是网速,而是打包策略太粗糙:整个业务代码和第三方库全部打进了两个chunk里面,光main.js就接近2.8MB(未压缩),图片也全部走原始文件,没有任何压缩和懒加载处理。
这次优化做完之后,我在本地和测试环境都测过,首屏时间稳定在0.5秒左右,体积最大的一次构建产物从2.8MB降到了约700KB,压缩后传输体积只有不到200KB。整个过程我没用什么奇技淫巧,核心思路就是围绕Webpack配置做6件事:分析产物、压缩代码、拆包、第三方库瘦身、静态资源优化、持久化缓存。这篇文章把这6件事的记录和坑都写出来,希望对正在被首屏性能折磨的同学有点帮助。
1. 先别急着改配置,先搞清楚体积到底去哪了
很多人做性能优化的第一反应是去网上复制一堆splitChunks配置,然后发现改了以后首屏更慢了。我自己的经验是,优化之前一定要先做产物体积分析,不然就相当于闭着眼睛拆弹,拆完了才知道拆的是哪根线。
1.1 用webpack-bundle-analyzer把打包结果变成一张“账本”
我先在webpack.config.js里加了BundleAnalyzerPlugin,这一步是整套优化方案的起点。安装命令很简单:
bash复制npm install -D webpack-bundle-analyzer
然后在配置里引入:
js复制const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: 'bundle-report.html',
openAnalyzer: false,
}),
],
};
我建议用static模式生成一个HTML报告,而不是启动本地服务自动开浏览器。原因很简单:static模式会把报告落地成文件,可以来回翻,而且不会像server模式那样改了配置就要重新起服务。
跑一次打包之后,浏览器打开生成的bundle-report.html,你会看到每个chunk对应的包大小分布图,整个页面就是一个可视化的“矩形面积图”,面积越大说明这个模块在产物里占的体积越大。这个工具可以帮助你快速定位三类问题:单体chunk过大、某个第三方库体积异常、重复依赖被打了多份。
1.2 从报告里读出真正有价值的信息
我第一次打开分析报告时,发现main.js的体积占比几乎是全部,里面引入的模块五花八门。逐个看下来,几个关键结论是:
- moment.js连带所有语言包占到了450KB左右,而项目里其实只用到了它的日期格式化和相对时间功能。
- lodash是整体引入的,当时代码里用的是
import _ from 'lodash',导致打包器把整个库都塞进去了。 - 项目里所有业务页面组件全部通过静态import引到路由配置中,webpack自然没办法拆包,于是一个main.js包含了所有页面代码。
- antd组件库虽然按文档配置了babel-plugin-import,但一些弹窗类组件仍然被全量打包。
这些信息直接决定了后面的优化顺序:先压缩和无损瘦身,再做代码分割,然后是第三方库替换、静态资源、缓存策略。如果一开始就纠结于压缩参数或者CDN域名,方向就偏了。
1.3 明确优化优先级,把精力花在回报最高的地方
体积分析报告的价值不只是看哪些库大,而是帮你算出每一项优化的“收益上限”。比如moment.js就算完全替换掉,最多省下450KB,而如果业务代码本身有1.5MB,那替换moment带来的提升就会被业务代码的体积稀释不少。
我当时的判断逻辑是:产出文件越大,网络传输时间和JS解析时间越长,对首屏的拖累越明显。而优化手段里,代码分割的收益往往比压缩更夸张,因为它是8:00前决定用户下载哪些资源,而不是仅仅把下载的资源变小。所以我的执行顺序是:压缩和tree-shaking这类“无损减重”先行,然后再做路由级代码分割和公共依赖拆分,最后再处理图片和缓存。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 压缩和tree-shaking:先把代码“体重”降下来
代码体积大是首屏慢的直接原因之一,而压缩是最容易上手、收益又稳的一步。Webpack 5在mode设置为production时会自动启用TerserPlugin,但默认配置不一定符合你的全部需求,所以生产环境的压缩配置我会手动覆盖一遍。
2.1 生产环境JS压缩:移除console,关掉注释
这一项对应的配置如下:
js复制const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
mode: 'production',
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {
compress: {
drop_console: true,
pure_funcs: ['console.log'],
},
format: {
comments: false,
},
},
extractComments: false,
}),
],
},
};
这段配置里,parallel设置为true会启用多进程压缩,webpack会默认使用当前机器CPU核心数减一作为进程数,实测下来打包速度会快不少。drop_console会把所有console语句移除,这里要特别提醒一句:如果项目里有需要上线的告警日志或SDK调用,不要在全局把console全部drop掉,可以只drop console.log 和 console.info,保留 console.warn 和 console.error,或者用环境变量控制。
评论和license注释通过format.comments关闭后,也能省下一点体积。extractComments设为false是为了不让webpack把注释单独提取到一个.LICENSE.txt文件中,否则部署时可能会产生额外的文件请求。
