一、前言
从 0 到 1 学习的朋友可参考前置学习文章:
-
学习 Webpack5 之路(基础篇) [1] -
学习 Webpack5 之路(实践篇) [2]
前置文章 学习 Webpack5 之路(基础篇)[1] 对 webpack 的概念做了简单介绍,学习 Webpack5 之路(实践篇)[2] 则从配置着手,用 webpack 搭建了一个 SASS + TS + React 的项目。
本篇将从优化开发体验、加快编译速度、减小打包体积、加快加载速度 4 个角度出发,介绍如何对 webpack 项目进行优化。
本文依赖的 webpack 版本信息如下:
-
webpack-cli@4.7.2 -
webpack@5.46.0
二、优化效率工具
在优化开始之前,需要做一些准备工作。
安装以下 webpack 插件,帮助我们分析优化效率:
-
progress-bar-webpack-plugin [3]:查看编译进度; -
speed-measure-webpack-plugin [4]:查看编译速度; -
webpack-bundle-analyzer [5]:打包体积分析。
1. 编译进度条—
一般来说,中型项目的首次编译时间为 5-20s,没个进度条等得多着急,通过 progress-bar-webpack-plugin[3] 插件查看编译进度,方便我们掌握编译情况。
安装:
npm i -D progress-bar-webpack-plugin
webpack.common.js
配置方式如下:
const chalk = require('chalk')
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
module.exports = {
plugins: [
// 进度条
new ProgressBarPlugin({
format: ` :msg [:bar] ${chalk.green.bold(':percent')} (:elapsed s)`
})
],
}
贴心的为进度百分比添加了加粗和绿色高亮态样式。
包含内容、进度条、进度百分比、消耗时间,进度条效果如下:
2. 编译速度分析—
优化 webpack 构建速度,首先需要知道是哪些插件、哪些 loader 耗时长,方便我们针对性的优化。
通过 speed-measure-webpack-plugin[4] 插件进行构建速度分析,可以看到各个 loader、plugin 的构建时长,后续可针对耗时 loader、plugin 进行优化。
安装:
npm i -D speed-measure-webpack-plugin
webpack.dev.js
配置方式如下:
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
module.exports = smp.wrap({
// ...webpack config...
})
包含各工具的构建耗时,效果如下:
注意:这些灰色文字的样式,是因为我在 vscode 终端运行的,导致有颜色的字体都显示为灰色,换个终端就好了,如 iTerm2[6]。
3. 打包体积分析—
同样,优化打包体积,也需要先分析各个 bundle 文件的占比大小,来进行针对优化。
使用 webpack-bundle-analyzer[5] 查看打包后生成的 bundle 体积分析,将 bundle 内容展示为一个便捷的、交互式、可缩放的树状图形式。帮助我们分析输出结果来检查模块在何处结束。
安装:
npm i -D webpack-bundle-analyzer
webpack.prod.js
配置方式如下:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
// 打包体积分析
new BundleAnalyzerPlugin()
],
}
包含各个 bundle 的体积分析,效果如下:
三、优化开发体验
1. 自动更新—
自动更新[7] 指的是,在开发过程中,修改代码后,无需手动再次编译,可以自动编译代码更新编译后代码的功能。
webpack 提供了以下几种可选方式,实现自动更新功能:
-
webpack's Watch Mode [8] -
webpack-dev-server [9] -
webpack-dev-middleware [10]
webpack 官方推荐的方式是 webpack-dev-server
,在 学习 Webpack5 之路(实践篇)[2] - DevServer 章节[11] 已经介绍了 webpack-dev-server[9] 帮助我们在代码发生变化后自动编译代码实现自动更新的用法,在这里不重复赘述。
这是针对开发环境的优化,修改
webpack.dev.js
配置。
2. 热更新—
热更新[12] 指的是,在开发过程中,修改代码后,仅更新修改部分的内容,无需刷新整个页面。
2.1 修改 webpack-dev-server 配置
使用 webpack 内置的 HMR 插件,更新 webpack-dev-server 配置。
webpack.dev.js
配置方式如下:
module.export = {
devServer: {
contentBase: './dist',
hot: true, // 热更新
},
}
2.2 引入 react-refresh-webpack-plugin
使用 react-refresh-webpack-plugin[13] 热更新 react 组件。
安装:
npm install -D @pmmmwh/react-refresh-webpack-plugin react-refresh
webpack.dev.js
配置方式如下:
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
module.exports = {
plugins: [
new webpack.HotModuleReplacementPlugin(),
new ReactRefreshWebpackPlugin(),
]
}
遇到的问题:
配置了 SpeedMeasurePlugin 后,热更新就无效了,会提示 runtime is undefined
。
解决方案:
仅在分析构建速度时打开 SpeedMeasurePlugin 插件,这里我们先关闭 SpeedMeasurePlugin 的使用,来查看热更新效果。
最终效果:
更新 react 组件代码时,无需刷新页面,仅更新组件部分。
四、加快构建速度
1. 更新版本—
1.1 webpack 版本
使用最新的 webpack 版本,通过 webpack 自身的迭代优化,来加快构建速度。
这一点是非常有效的,如 webpack5 较于 webpack4,新增了持久化缓存、改进缓存算法等优化,webpack5 新特性可查看 参考资料[14]。
1.2 包管理工具版本
将 Node.js 、package 管理工具(例如 npm
或者 yarn
)更新到最新版本,也有助于提高性能。较新的版本能够建立更高效的模块树以及提高解析速度。
本文依赖的版本信息如下:
-
webpack@5.46.0
-
node@14.15.0
-
npm@6.14.8
2. 缓存—
2.1 cache
通过配置 webpack 持久化缓存[15] cache: filesystem
,来缓存生成的 webpack 模块和 chunk,改善构建速度。
简单来说,通过 cache: filesystem
可以将构建过程的 webpack 模板进行缓存,大幅提升二次构建速度、打包速度,当构建突然中断,二次进行构建时,可以直接从缓存中拉取,可提速 90% 左右。
webpack.common.js
配置方式如下:
module.exports = {
cache: {
type: 'filesystem', // 使用文件缓存
},
}
引入缓存后,首次构建时间将增加 15%,二次构建时间将减少 90%,效果如下:
2.2 dll ❌
在 webpack 官网构建性能[16] 中看到关于 dll 的介绍:
dll 可以为更改不频繁的代码生成单独的编译结果。可以提高应用程序的编译速度。
我兴冲冲的开始寻找 dll 的相关配置说明,太复杂了,接着找到了一个辅助配置 dll 的插件 autodll-webpack-plugin[17],结果上面直接写了 webpack5 开箱即用的持久缓存是比 dll 更优的解决方案。
所以,不用再配置 dll了,上面介绍的 cache 明显更香。
2.3 cache-loader ❌
没错,cache-loader[18] 也不需要引入了,上面的 cache 已经帮助我们缓存了。
3. 减少 loader、plugins—
每个的 loader、plugin 都有其启动时间。尽量少地使用工具,将非必须的 loader、plugins 删除。
3.1 指定 include
为 loader 指定 include,减少 loader 应用范围,仅应用于最少数量的必要模块,。
webpack 构建性能文档[19]
rule.exclude 可以排除模块范围,也可用于减少 loader 应用范围.
webpack.common.js
配置方式如下:
module.exports = {
rules: [
{
test: /.(js|ts|jsx|tsx)$/,
include: paths.appSrc,
use: [
{
loader: 'esbuild-loader',
options: {
loader: 'tsx',
target: 'es2015',
},
}
]
}
]
}
定义 loader 的 include 后,构建时间将减少 12%,效果如下:
3.2 管理资源
使用 webpack 资源模块[20] (asset module) 代替旧的 assets loader(如 file-loader
/url-loader
/raw-loader
等),减少 loader 配置数量。
配置方式如下:
module.exports = {
rules: [
{
test: /.(png|svg|jpg|jpeg|gif)$/i,
include: [
paths.appSrc,
],
type: 'asset/resource',
},
]
}
引入资源模块后,构建时间将减少 7%,效果如下:
4. 优化 resolve 配置—
resolve[21] 用来配置 webpack 如何解析模块,可通过优化 resolve 配置来覆盖默认配置项,减少解析范围。
4.1 alias
alias 可以创建 import
或 require
的别名,用来简化模块引入。
webpack.common.js
配置方式如下:
module.exports = {
resolve: {
alias: {
'@': paths.appSrc, // @ 代表 src 路径
},
}
}
4.2 extensions
extensions 表示需要解析的文件类型列表。
根据项目中的文件类型,定义 extensions,以覆盖 webpack 默认的 extensions,加快解析速度。
由于 webpack 的解析顺序是从左到右,因此要将使用频率高的文件类型放在左侧,如下我将 tsx
放在最左侧。
webpack.common.js
配置方式如下: