Webpack 作为现代前端开发里不能少的工具,在项目构建和资源管理这块起着关键作用。在前端开发越来越复杂的现在,做性能优化特别重要。
先从开发效率这块来说,一个没优化过的 Webpack 配置可能会让构建的时间变得很长。比如说,在大项目里,要是每次改代码都得花好多时间等着构建完,那会严重影响开发的进度。根据一些实际项目的数据统计,没优化的 Webpack 构建可能得要几分钟甚至更长时间,但是通过合理的性能优化办法,能把构建时间缩短到几十秒甚至更短。这样能让开发人员更快看到改了代码后的效果,把开发效率提上去。
其次,性能优化对用户体验也特别关键。优化后的 Webpack 能把打包文件变小,这样就能让用户加载的时间变短。有研究说,每少一秒的加载时间,都可能让用户留存率明显提高。比如说,当页面加载时间从 5 秒降到 3 秒,用户的满意度和转化率可能会提高 20%还多。通过像代码压缩、懒加载、资源优化这些办法,Webpack 能有效地把打包文件的大小变小,把页面的加载速度提上去,给用户更顺溜的体验。
另外,性能优化对提高项目的可维护性也有帮助。一个优化得好的 Webpack 配置能让项目的结构更清楚,代码更好理解也好改。比如说,通过合理地安排模块、用别名还有优化加载器的配置,能把代码的复杂程度降低,把维护的成本也降下来。同时,优化后的构建过程更稳当,出错的可能性也变小了。
总的来说,Webpack 性能优化对提高开发效率、让用户体验变好还有提高项目的可维护性都很重要。在前端开发里,咱们得重视 Webpack 的性能优化,用有效的办法把项目的整体质量提上去。
二、提高打包速度的方法
(一)优化 loader 的文件搜索范围
通过 include 和 exclude 参数来限定 babel-loader 的作用范围,少做点没必要的转译操作。 咱们能在 Webpack 配置文件里,针对 babel-loader,用 include 参数指明要编译的文件夹,比如说 include: path.resolve(__dirname, '../src'),这样能保证只给项目的源代码做转译。同时,用 exclude 参数把不需要处理的文件夹排除掉,像 exclude: /node_modules/,省得对特别大的第三方库做没必要的转译。这么弄能明显减少 babel-loader 的处理范围,把构建速度提上去。有统计说,在一个中等规模的项目里,合理配置 include 和 exclude 参数以后,babel-loader 的处理时间能减少 30%左右。
合理配置 resolve 参数,能少做点路径查询的工作。
resolve 参数能把模块的引入方式变简单,把路径查找的效率提高。比如说,配置 extensions: ['.js', '.jsx'],在代码里引入模块的时候,Webpack 会先试着找.js 文件,找不到再找.jsx 文件,能少点没必要的报错和查找时间。同时,配置 mainFiles: ['index', 'child'],Webpack 会先试着找 index 文件,没有的话再找 child 文件,把默认文件的查找顺序优化了。另外,还能用 alias 配置给模块起个别名,像 alias: { child: path.resolve(__dirname, '../src/a/b/c/child')},这样能把模块的引入路径变简单,把开发效率提高。不过得注意,extensions 和 mainFiles 不建议配置太多,要不就会有额外查询花时间的问题。
(二)缓存加载器执行结果
用 cache-loader 把耗时长的 loader 的执行结果缓存起来,让后面的构建速度变快。
cache-loader 是个特别实用的工具,每次加载源文件之前它都会查查有没有缓存的结果,要是有,就直接用,没有的话才做实际的加载操作,然后把结果存到缓存里。对一些耗时很长的 loader,像 babel-loader,用 cache-loader 能让二次构建的速度快好多。在实际的项目里,用了 cache-loader 以后,二次构建的时间能缩短 50%甚至更多。配置的办法也简单,把 cache-loader 放在比较费时间的 loader 前面,比如说:{ test: /\\.js$/, use: [ 'cache-loader', 'thread-loader', 'babel-loader' ] }。
(三)多进程打包
用 thread-loader 开多线程来处理文件。
thread-loader 是 Webpack 4 官方推荐的多进程打包工具。把 thread-loader 放在别的 loader 前面,放在它后面的 loader 就会在一个单独的 worker 池里运行,一个 worker 就是一个 Node.js 进程。每个单独进程处理的时间上限是 600ms,各个进程的数据交换也会限制在这个时间里。比如说:{ test: /\\.js$/, exclude: /node_modules/, use: [ 'thread-loader', 'babel-loader' ] }。在大项目里,用 thread-loader 能明显把打包速度提上去,根据实际测试,构建时间能缩短 30%以上。
用 HappyPack 插件来实现多线程解析和构建文件。
HappyPack 也是能实现多线程打包的一种办法。它能把任务分给好多子进程,把构建速度提上去。用 HappyPack 的时候,得先装插件,然后在 Webpack 配置文件里做相应的配置。虽说现在 thread-loader 更推荐用,不过在一些特定的情况里,HappyPack 还是能起作用的。
(四)IgnorePlugin 和 noParse 的使用
对比一下 IgnorePlugin 和 noParse 的作用,合理用它们来忽略没用的模块引入,把构建速度提上去。
IgnorePlugin 能在打包的时候忽略特定的模块或者文件。比如说,要是项目里有一些只在特定环境里用的模块,能用 IgnorePlugin 在不需要的环境里忽略它们的引入,这样能少点没必要的打包体积和构建时间。noParse 是用来指定一些不用被解析的模块。比如说,对一些很大的第三方库,要是确定它们不会被改,也不用解析它们的依赖关系,能用 noParse 配置跳过对它们的解析,把构建速度提上去。在实际的项目里,根据具体情况合理用这两个工具,能有效把构建速度提上去。
(五)使用 DllPlugin 提高打包速度
把大型的第三方库打包成单独的文件,把构建效率提上去。
新弄一个 webpack.dll.config.js 配置文件,对第三方模块做有针对性的打包。比如说:module.exports = { mode: 'production', entry: { vendors: ['react', 'react-dom', 'lodash'] }, output: { filename: '[name].dll.js', path: path.resolve(__dirname, '../dll') } }。然后通过全局变量把打包后的库露出来,用 add-asset-html-webpack-plugin 插件在 HTML 文件上加上静态资源的引用。同时,用 webpack.DllPlugin 生成映射文件,结合 webpack.DllReferencePlugin,在打包的时候要是发现第三方模块在映射文件里,就不会再去 node_modules 里面打包了。通过这种办法,能大大把构建速度提上去,特别是在大项目里,效果特别明显。
(六)合理使用 sourceMap
按照开发和生产环境的不同需求,平衡好性能和准确性。
source-map 的作用是在报错的时候方便定位到错误代码的位置。不过它的体积可不小,所以在不同的环境设置不同的类型很有必要。在开发环境里,咱们得能准确定位错误代码的位置,配置成 devtool: 'eval-cheap-module-source-map'。在生产环境里,咱们想把 source-map 打开,但是又不想体积太大,能配置成 devtool: 'nosources-source-map'。通过合理设置 source-map 的类型,能在保证开发效率和错误定位准确的同时,尽量把打包体积变小,把性能提高。
(七)结合分析工具分析打包结果
用 stats、speed-measure-webpack-plugin 和 webpack-bundle-analyzer 这些工具来分析打包的过程,找出性能的瓶颈。
speed-measure-webpack-plugin 能测 Webpack 构建的时候各个阶段花的时间,帮咱们找出构建过程里的瓶颈。装好了以后,通过 const smp = new SpeedMeasurePlugin(); module.exports = smp.wrap(prodWebpackConfig)把整个配置对象包起来,就能分析各个插件和 loader 花的时间。webpack-bundle-analyzer 能检查打包以后的体积分布,然后做相应的体积优化。只在打包的时候看体积,所以只要在 webpack.prod.js 里配置就行。通过这些工具的分析,能有针对性地做性能优化,把打包速度提上去。
(八)开发环境内存编译
用 webpack-dev-server 做开发,把产物存在内存里,把开发阶段的页面响应速度提上去。
webpack-dev-server 在开发的时候能把构建的产物存在内存里,省得老是读写文件,把页面的响应速度大大提高了。同时,结合热更新的功能,能做到只更新改了的模块,别的模块不变,把开发效率进一步提高。在配置 webpack-dev-server 的时候,能设置 devServer: { hot: true }打开热更新功能,还有设置 devServer: { contentBase: path.join(__dirname, 'dist') }指定静态资源的目录。这样,在开发的时候,页面的加载速度会明显变快,开发的体验也会更顺。
三、优化打包后的文件
(一)小图片 base64 编码
通过设置 url-loader 的 limit 参数,能把小图片变成 base64 编码,这样就能减少 HTTP 请求的次数。参考文档上说,只有比较小的图片资源适合弄成 base64 编码,因为弄成 base64 编码的图片资源一般是放在 css 里,太大的图片资源转了以后会让 css 文件变大,然后就会影响页面加载的效率。比如说,可以在 webpack.config 文件里像下面这样配置:
module.exports = {
entry: "./js/main.js",
output: {
path: path.resolve(__dirname, "build"),
publicPath: "/build/",
filename: "[name].js",
},
module: {
rules: [
{
test: /\.(gif|png|jpg|woff|svg|ttf|eot)$/,
use: [
{
loader: "url-loader",
options: {
limit: 500,
outputPath: "images/",
name: "[name].[ext]",
},
},
],
},
],
},
devtool: "inline-source-map",
mode: "development",
plugins: [],
};
这样每次构建的时候,webpack 都会自己给符合规则的图片资源做 base64 编码的转换。
(二)bundle 加 hash
给打包文件加上哈希值能方便处理缓存的问题。比如说,在 webpack 的配置文件里,可以像这样设置输出的文件名:
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.[hash].js',
}
这么改完以后,每次改了源码编译都会弄出新的带着哈希值的文件。为了不让 dist 目录里有好多过时的文件垃圾,可以装 clean-webpack-plugin 来自动把旧文件清理掉。
(三)提取公共代码和压缩 CSS
- 用 SplitChunksPlugin 能把公共的 js 代码提取出来。在有多入口文件的项目里,难免在不同的入口有一样的部分,把多个 css chunk 合成一个 css 文件能提高效率。比如说,可以在 optimization 配置里这么设置:
optimization: {
splitChunks: {
cacheGroups: {
commons: {
name: 'commons',
chunks: 'initial',
minChunks: 2,
minSize: 0,
},
},
},
},
- 用 mini-css-extract-plugin 能够抽取和压缩 CSS 代码。先把这个插件装上,然后在配置文件里引进去再配置:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// css,scss,sass,less
{
test: /\.(sa|sc|c)ss$/,
use: [
process.env.NODE_ENV === 'development'
? 'style-loader'
: MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader',
],
}
//最后对应环境下的 plugins 中
new MiniCssExtractPlugin({
filename: "[name].css",
});
(四)tree shaking
在生产模式下一定得把 tree-shaking 启用了,就编译实际用到的代码,把打包体积变小。webpack 2 正式版本身就支持 ES2015 模块和对没用到的模块的检测能力。webpack 4 正式版把这个检测能力扩展了,通过 package.json 的 "sideEffects" 属性当标记,给编译器提示,告诉它项目里哪些文件是纯的 ES2015 模块,这样就能把文件里没用的部分安全地删掉。比如说,把 mode 配置设成 development,好确定 bundle 不会被压缩,然后设置 optimization:
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
mode: 'development',
optimization: {
usedExports: true,
},
};
(五)ignorePlugin 忽略无用模块引入
IgnorePlugin 和 noParse 都能用来忽略没用的模块引入,让打包体积变小。IgnorePlugin 能在打包的时候忽略特定的模块或者文件。比如说,要是项目里有一些只在特定环境里用的模块,能用 IgnorePlugin 在不需要的环境里忽略它们的引入。noParse 是用来指定一些不用被解析的模块。比如说,对一些很大的第三方库,要是确定它们不会被改,也不用解析它们的依赖关系,能用 noParse 配置跳过对它们的解析。
(六)Scope Hosting 的特点及使用
Scope Hosting(作用域提升)是 webpack 的一种优化技术,能把好几个模块合到一个函数的作用域里,把函数调用和变量查找的开销减少。它的特点有:让代码体积变小、把运行时的性能提高啥的。使用的办法是在 webpack 配置里启用相关的插件或者选项,具体咋配置得看 webpack 的版本和项目的需求。比如说,可以在高级配置里找找关于 Scope Hosting 的选项去设置。通过 Scope Hosting 能进一步把打包的结果优化好,把项目的性能和加载速度提高。
四、webpack总体概括
Webpack 性能优化这事儿啊,又复杂又特别重要。通过把打包速度提上去,把打包后的文件弄好,咱们能明显把开发效率提高,让用户体验变好,项目也更好维护。
在实际的项目里,开发的人得按照项目具体的需求和特点来挑合适的优化办法。比如说,小项目可能用不着多进程打包的工具,可大项目呢,合理用好多进程打包和缓存的机制能大大把构建时间缩短。要是项目特别看重用户体验,像小图片弄成 base64 编码、bundle 加上 hash 还有模块懒加载这些技术能有效把页面加载速度提上去。
同时呢,Webpack 的性能优化是一直得做的事儿。随着前端技术不停地发展,项目也不停地变,新的优化办法会不断出来。开发的人得一直留意 Webpack 的更新和变化,积极去找新的优化办法,好能适应一直变的开发需求。
比如说,Webpack 5 出来了,带来了更多性能优化的特点,像更好的缓存机制、内置的模块联邦啥的。开发的人可以好好研究研究这些新特点,用到实际项目里,进一步把项目的性能提上去。 反正,Webpack 性能优化是前端开发里少不了的一部分。开发的人得充分明白它的重要性,按照实际项目的需求选合适的优化办法,还得一直找新的优化策略,来做出高效、质量好的前端项目。