Webpack打包优化实战经验与性能提升关键点解析
打包体积太大,首屏加载慢到离谱
前几天刚上线的项目,QA同学跑过来跟我说,首屏加载时间太长了,简直没法忍。我一听就有点慌,赶紧打开开发者工具看了一下,果然发现vendor.js居然有3MB多,这谁顶得住啊。
这里我踩了个坑,之前一直觉得webpack配置挺简单的,就是常规的那些套路:babel-loader、css-loader之类的。但这次问题明显出在打包优化上,尤其是第三方库的体积太大了。
折腾了半天,终于找到罪魁祸首
先说排查过程吧。我用了webpack-bundle-analyzer这个插件来分析打包结果:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
运行完打包命令后,可视化报告一出来我就傻眼了——lodash居然被打包进去了,而且是完整版!我记得明明只用到了几个方法,怎么整个库都被打进去了?
后来试了下发现,原来是我直接import的方式有问题:
// 这种写法会把整个lodash都打包进去
import _ from 'lodash';
三种方案对比,最后选了最靠谱的
针对这个问题,我试了三种解决方案:
- 第一种:手动按需引入
- 第二种:使用babel-plugin-lodash
- 第三种:换成lodash-es
先说手动按需引入,虽然确实能解决问题,但太麻烦了,每个文件都要改:
// 手动按需引入
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';
第二种方案看起来不错,只需要在babel配置里加个插件:
// babel.config.js
module.exports = {
presets: ['@vue/cli-plugin-babel/preset'],
plugins: ['lodash']
}
但是这里又踩了个坑,这个插件和tree-shaking不兼容,导致其他库的体积也变大了。
最终我选择了第三种方案——直接换成lodash-es,配合webpack的optimization.splitChunks:
// webpack.config.js
const path = require('path');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist')
},
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\/]node_modules[\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
},
module: {
rules: [
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
}
核心代码就这几行,效果立竿见影
除了处理lodash的问题,我还做了几个关键优化:
首先是开启生产环境的压缩:
mode: 'production'
然后是处理moment.js的locale问题,只保留需要的语言包:
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.ContextReplacementPlugin(/moment[/\]locale$/, /zh-cn/)
]
}
最后是处理css提取和压缩:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
module: {
rules: [
{
test: /.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
})
],
optimization: {
minimizer: [new OptimizeCSSAssetsPlugin({})]
}
}
优化后的成果,还有个小瑕疵
经过这一顿操作,vendor.js从原来的3MB降到了500KB左右,首屏加载时间直接砍掉了一半多。不过这里还有个小问题,因为用了contenthash,每次发布都会生成新的文件名,CDN缓存命中率会受影响,但这点小问题无伤大雅。
以上是我踩坑后的总结,如果你有更好的方案欢迎评论区交流。说实话,打包优化这块水还挺深的,我准备接下来继续研究一下动态导入和更细粒度的代码分割策略。

暂无评论