这阵子把组内最大的前端项目做了下 webpack 的升级,构建效率直线上升,主要得益于 webpack5 的缓存策略
webpack5 带来了什么?
- 持久化缓存。webpack4 需要通过 cache-loader/hardSourcePlugin 来实现中间缓存,webpack5 相当于内置了这部分功能
- 更好的 hash 算法。hash—>fullhash,比如 webpack 4 如果添加空白、注释或修改变量名是会影响 contenthash 值的计算,webpack5 则不会影响,从而能继续使用缓存,这个方式降低了缓存的失效率,间接加快了应用 rebuild 的速度
- Asset Modules。指的是图片和字体等这一类型文件模块,它们无须使用额外的预处理插件
- 模块联邦。实现应用级的模块复用,这个是现在蛮多新兴微前端框架的基础
- tree-shaking 改进。据说可以减少约 30%的 bundle-size,不过实际项目中并没有体会到这个变化
- 更严格的代码检查。这个也是造成很多 webpack4 项目在升级后突然在运行时报错的原因,会导致页面 crash
- 确定的 moduleId/chunkId
- Node Polyfill 脚本被移除
- 原生 worker 支持。可以参考 react 项目中使用 web worker 里面有提及 webpack5 下如何使用 web worker
开始升级
这个过程也是遇到了一些问题,总结如下:
因为我们的项目是基于 CRA4 搭建的,并且基于 react-app-rewired 扩展 webpack 配置,所以首先需要升级 react-scripts、react-app-rewired、customize-cra
额外引入的 loader、plugin 都要升级到支持 webpack5 的版本。没有支持 webpack5,那就去 github issue 里面看看啥时候支持?
首先是 addLessLoader
不适配,需要更换插件如下:
// config-overrides.js
const addLessLoader = require("customize-cra-less-loader");
//...
addLessLoader({
lessOptions: {
javascriptEnabled: true,
sourceMap: false,
},
}),
可以借助 npm-check-updates 这个插件检查所有依赖版本
去除一些废弃的插件,比如以下的资源插件
url-loader
将文件作为 dataURI 内联到 bundle 中file-loader
将文件发送到输出目录raw-loader
将文件导入为字符串
webpack5 通过以下配置就可以完成对资源文件的解析
asset/resource
发送一个单独的文件并导出 URL(file-loader)asset/inline
导出一个资源的 dataURI(url-loader)asset
在导出一个 dataURI 和发送一个单独的文件之间自动选择。webpack4 需要通过使用 url-loader 并且配置资源体积限制来实现asset/source
导出资源的源代码
配置方式参考:
// ...
rules: [
{
test: /\.png$/i,
use: 'file-loader'
},
{
test: /\.(jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 1024,
},
},
],
}
],
// 改成
// ...
rules: [
{
test: /\.png$/i,
use: 'asset/resource'
},
{
test: /\.(jpg|gif)$/i,
type: 'asset',
parser: {
dataurlCondition: {
maxSize: 1024 // 单位是B
}
}
}
],
这里可以参考下我给项目做的资源配置
// config-overrides.js
// ...
addWebpackModuleRule({
test: /\.(png|jpe?g|gif|webp|svg|bmp|ttf|eot|woff|woff2)$/i,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 1024 * 1024,
},
},
generator: {
filename: "img/[name].[hash:4][ext]",
publicPath: process.env.PUBLIC_URL,
},
}),
addWebpackModuleRule({
test: /\.(objs?|mtl)$/i,
type: "asset/resource",
generator: {
filename: "model/[name].[hash:4][ext]",
publicPath: process.env.PUBLIC_URL,
},
}),
// ...
去除 hard-source-webpack-plugin
,取而代之的是 cache。
cache: {
// memory(内存)|filesystem(持久化缓存)
type: "filesystem",
buildDependencies: {
config: [__filename],
},
version: '1.0'
}
缓存文件会保存在 node_modules/.cache
。参考 https://github.com/webpack/webpack/issues/6527
如果配置 filesystem 做持久化储存,webpack5 还是会同时使用 memory,用于 watch 模式
如果是 eject 导出完整 webpack 配置的项目,可能还会遇到以下问题:
部分插件的引入方式需要调整,比如 webpack-merge
改成解构的方式引入
const webpackMerge = require("webpack-merge");
const ManifestPlugin = require("webpack-manifest-plugin");
// 改成
const { merge: WebpackMerge } = require("webpack-merge");
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
如果有单独引用 dev-sever
也要升级,启动命令调整如下:
// package.json
{
"scripts": {
"serve": "webpack-dev-server --config webpack.config.dev.js"
}
}
// 改成
{
"scripts": {
"serve": "webpack server --config webpack.config.dev.js"
}
}
通过 require 引入的图片会无法正常加载,需要在 url-loader 配置中添加 esModule: false
sourcemap 的名称可能也需要调整
devtool: "cheap-eval-module-source-map";
// 改成
devtool: "eval-cheap-module-source-map";
开启 hot: true
热更新无效,需要添加配置如下:
{
target: process.env.NODE_ENV === "development" ? "web" : "browserslist",
}
这像是一个 bug,参考 https://github.com/webpack/webpack-dev-server/issues/2758
除去编译问题后,接下来主要在于解决一些运行时的报错,基本上按照提示一个个去解就行了
JSON 模块只能使用默认引入,调整如下
import { name } from "package.json";
// 改为
import pkgInfo from "package.json";
const { name } = pkgInfo;
其他的问题比如重复的函数或变量、undefined 等,在 webpack5 更严格的检查下会暴露,逐个修复就行了
进一步优化
CRA5 在 webpeck 的配置上已经做了很充分的优化工作,参考源码 https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/config/webpack.config.js
进一步优化的空间其实不多,可以做下 code spilting,配置参考:
// config-overrides.js
module.exports = {
webpack: override(
// ...
isProduction &&
setWebpackOptimizationSplitChunks({
chunks: "all",
cacheGroups: {
react: {
test: /[\\/]node_modules[\\/](react|react-dom)/,
name: "react",
priority: 1,
},
three: {
test: /[\\/]node_modules[\\/](three)/,
name: "three",
priority: 1,
},
antd: {
test: /[\\/]node_modules[\\/](antd|@ant-design)/,
name: "antd",
priority: 1,
},
echarts: {
test: /[\\/]node_modules[\\/](echarts|zrender)/,
name: "echarts",
priority: -1,
},
antv: {
test: /[\\/]node_modules[\\/](@antv)/,
name: "antv",
priority: -1,
},
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendor",
priority: -5,
},
},
})
),
};
// ...
如果是多页或者多路由页面的应用,还可以结合 React.lazy
进行页面级别的分包,按需加载页面及其资源
总结
实际升级下来,其实问题不多,花个一天左右就填完坑了,可能是 react-scripts@5.0
已经做了大部分工作了,然后疑难问题基本上都可以找到解决方法。升级效果也很明显,主要体现在 rebuild 的速度上。我觉得还没升级的话,可以大胆升级一波,升级完之后就可以尝试 webpack5 的各种新特性啦 ~