webpack4升級到webpack5的實戰(zhàn)經(jīng)驗總結
前言
最近接手了公司內部webpack相關的依賴包,于是打算優(yōu)化一波??紤]到webpack5已經(jīng)正式發(fā)布近兩年,跟webpack相關的依賴包應該適配的差不多了,于是打算先把webpack4升級到webpack5,然后基于webpack5再進行優(yōu)化。
升級前用的是 "webpack": "^4.42.1" ,升級后用的是 "webpack": "^5.72.1" 。
筆者采用的升級webpack的方法是先一鍵升級所有的依賴包,然后一個一個地去解決運行過程中的報錯。
首先,全局安裝npm-check-updates:
yarn global add npm-check-updates
然后在項目中執(zhí)行 ncu -u ,這樣項目的package.json會把所有的依賴包都更新到最新版本,然后執(zhí)行 yarn 。升級完就可以開啟漫長的debug之旅了。
terser-webpack-plugin語法報錯
Invalid options object. Terser Plugin has been initialized using an options object that does not match the API schema.
- options has an unknown property 'cache'. These properties are valid:
object { test?, include?, exclude?, terserOptions?, extractComments?, parallel?, minify? }
原來的配置:
minimizer: [
new TerserPlugin({
cache: true,
parallel: true,
terserOptions: {
mangle: false, // Note `mangle.properties` is `false` by default.
},
}),
]
從報錯來看,terser-webpack-plugin的配置屬性發(fā)生了改變,現(xiàn)在已經(jīng)沒有 cache 這個選項了。
terser-webpack-plugin是用來壓縮JavaScript代碼的。不過webpack5已經(jīng)自帶了terser-webpack-plugin,如果說你用的是webpack5或者更高版本,同時還希望自定義配置,那么還是需要安裝terser-webpack-plugin。
webpack4的時候可以通過terser-webpack-plugin的cache屬性開啟文件緩存?,F(xiàn)在webpack5自身提供了持久化緩存機制,它能夠將首次打包的結果緩存到硬盤中,等下次打包的時候就可以跳過一系類的耗時的操作,復用第一次的打包結果??梢酝ㄟ^以下配置開啟持久化緩存:
cache: {
type: 'filesystem',
version: 'yourVersion'
}
緩存默認保存路徑是 node_modules/.cache/webpack。這里要注意每當我們修改了webpack配置,記得更新cache的version,否則可能會出現(xiàn)因為重用了緩存導致配置沒生效的問題。
綜上,最后代碼修改如下:
cache: {
type: 'filesystem',
version: '3.8.1',
},
optimization: {
...
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {
mangle: false, // Note `mangle.properties` is `false` by default.
},
}),
],
...
}
fork-ts-checker-webpack-plugin語法報錯
Invalid configuration object. ForkTsCheckerWebpackPlugin has been initialized using a configuration object that does not match the API schema.
- configuration has an unknown property 'reportFiles'. These properties are valid:
object { async?, typescript?, formatter?, issue?, logger?, devServer? }
fork-ts-checker-webpack-plugin 是在單獨的進程上運行 TypeScript 類型檢查器的 Webpack 插件。當文件發(fā)生改動需要重新轉譯和類型檢查時,fork-ts-checker-webpack-plugin會開辟一個單獨的進程去執(zhí)行類型檢查的任務,這樣就不會影響 webpack 重新編譯的速度。
原來的配置:
plugins: [
new ForkTsCheckerWebpackPlugin({
memoryLimit: 4096,
tsconfig: PATH.appDirectory + '/tsconfig.json',
checkSyntacticErrors: true,
reportFiles: [`${PATH.appSrc}/**/*.{ts,tsx}`],
}),
]
fork-ts-checker-webpack-plugin從 4.1.3 升級到 7.2.11 ,這個plugin的API已經(jīng)發(fā)生了改變。改成:
plugins: [
new ForkTsCheckerWebpackPlugin({
typescript: {
memoryLimit: 4096,
configFile: PATH.appDirectory + '/tsconfig.json',
diagnosticOptions: { syntactic: false, semantic: false, declaration: false, global: false }
},
}),
]
diagnosticOptions選項用來設置哪些TypeScript語法需要檢查。
IgnorePlugin報錯
Invalid options object. Ignore Plugin has been initialized using an options object that does not match the API schema.
- options should be one of these:
object { resourceRegExp, contextRegExp? } | object { checkResource }
Details:
* options misses the property 'resourceRegExp'. Should be:
RegExp
-> A RegExp to test the request against.
* options misses the property 'checkResource'. Should be:
function
-> A filter function for resource and context.
IgnorePlugin的作用是忽略第三包指定目錄,讓這些指定目錄不要被打包進去。比如moment包的locale文件夾包括了各國語言的目錄,如果把所有語言都打包進去就會影響打包效率。
原來的配置:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
從報錯來看,該plugin的語法發(fā)生了改變。改成:
new webpack.IgnorePlugin({
resourceRegExp: /^\.\/locale$/,
contextRegExp: /moment/,
})
devtool報錯
Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.devtool should match pattern "^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map$".
BREAKING CHANGE since webpack 5: The devtool option is more strict.
Please strictly follow the order of the keywords in the pattern.
大概的意思是說:檢查devtool的匹配,webpack5要求的匹配更嚴格。
原來的配置:
devtool: isEnvDevelopment ? 'cheap-module-eval-source-map' : false,
改成:
devtool: isEnvDevelopment ? 'eval-cheap-module-source-map' : false,
webpack-dev-server publicPath報錯
Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
- options has an unknown property 'publicPath'. These properties are valid:
object { allowedHosts?, bonjour?, client?, compress?, devMiddleware?, headers?, historyApiFallback?, host?, hot?, http2?, https?, ipc?, liveReload?, magicHtml?, onAfterSetupMiddleware?, onBeforeSetupMiddleware?, onListening?, open?, port?, proxy?, setupExitSignals?, static?, watchFiles?, webSocketServer? }
這次webpack-dev-server從 "^3.11.2" 升級到了 "^4.9.0"。
publicPath用來設置項目跑在本地時,打包生成的文件所在的位置。
原來的配置:
devServer: {
...
publicPath: appConfig.publicPath,
...
},
改成:
devServer: {
...
devMiddleware: {
publicPath: appConfig.publicPath,
},
...
},
webpack-dev-server contentBase報錯
Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
- options has an unknown property 'contentBase'. These properties are valid:
object { allowedHosts?, bonjour?, client?, compress?, devMiddleware?, headers?, historyApiFallback?, host?, hot?, http2?, https?, ipc?, liveReload?, magicHtml?, onAfterSetupMiddleware?, onBeforeSetupMiddleware?, onListening?, open?, port?, proxy?, setupExitSignals?, static?, watchFiles?, webSocketServer? }
contentBase用來設置項目跑在本地時,不由webpack打包生成的文件的位置。
原來的配置:
devServer: {
...
contentBase: PATH.appDirectory,
...
},
devServer的v4中contentBase遷移到了static下,并且static的默認值是path.resolve(process.cwd(), 'public') 。改成:
devServer: {
...
static: [
{
directory: PATH.appDirectory,
},
],
...
},
webpack-dev-server disableHostCheck報錯
Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.
- options has an unknown property 'disableHostCheck'. These properties are valid:
object { allowedHosts?, bonjour?, client?, compress?, devMiddleware?, headers?, historyApiFallback?, host?, hot?, http2?, https?, ipc?, liveReload?, magicHtml?, onAfterSetupMiddleware?, onBeforeSetupMiddleware?, onListening?, open?, port?, proxy?, setupExitSignals?, static?, watchFiles?, webSocketServer? }
原來的配置:
devServer: {
...
disableHostCheck: true,
...
},
改成:
devServer: {
...
allowedHosts: "all",
...
},
移除 node.js polyfill
Module not found: Error: Can't resolve 'crypto' in '/xxx/node_modules/crypto-js'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "crypto": require.resolve("crypto-browserify") }'
- install 'crypto-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "crypto": false }
webpack5 以前,webpack 會包含 nodejs 核心模塊的 polyfill,這樣的話,比如安裝了一個crypto模塊,那么就可以直接使用,因為 node 的polyfill會自動啟動?,F(xiàn)在,webpack5 移除了 nodejs 的 polyfill,無法再直接使用類似crypto的模塊了。
如果你想要使用類似crypto的 nodejs 核心模塊,有兩種方法:
1.在 webpack 配置文件的resolve中配置fallback
module.exports = {
...
resolve: {
fallback: {
"crypto": require.resolve("crypto-browserify"), // 如果不需要,那么就直接改為 false 就可以了
}
}
}
2.如果覺得上面的方法很麻煩,那么可以使用node-polyfill-webpack-plugin:
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin')
{
...
plugins: [
...
new NodePolyfillPlugin(),
...
]
}
導入json文件語法改變
export 'orderLimit'.'group_ids'.'includes' (imported as 'orderLimit') was not found in '../../../custom.config.json' (possible exports: 0, 1, 2)
原來:
// custom.config.json
{
"orderLimit": {
"group_ids": ["xxx"],
"desc": ["xxx"]
}
}
// 原來引用文件方式
import { orderLimit } from '../../../custom.config.json'
改成:
import orderLimit from '../../../custom.config.json'
打包報錯unknown option ‘-p’
[webpack-cli] Error: Unknown option '-p'
原本的打包命令文件:
shellExec(
'webpack -p --color --config ' + require.resolve('../config/webpack.config'),
)
改成如下:
shellExec(
'webpack --mode production --color --config ' + require.resolve('../config/webpack.config'),
)
node版本過低
eslint@8.15.0: The engine "node" is incompatible with this module. Expected version "^12.22.0 || ^14.17.0 || >=16.0.0". Got "14.15.4"
項目跑CI的時候報錯說CI的node鏡像版本偏低了?,F(xiàn)在eslint要求的node版本是16以上,但是公司node版本是14。于是找負責運維的同事升級了node版本。
圖片編譯問題
原來的配置:
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve('url-loader'),
options: {
limit: 10000,
name: 'media/image/[name].[hash:8].[ext]',
},
},
之前處理圖片用的是url-loader。如果圖片比較小那么會被編譯成base64的格式,然后和項目代碼打包在一起;如果是比較大的圖片則會放在build/media/image文件夾下。然而升級到webpack5以后出現(xiàn)了下面的情況:

圖片除了按要求放在build/media/image文件夾下,竟然還多存了一份直接放在了build文件夾下。而且項目代碼中圖片的引用路徑指向了這些放錯位置的圖片,但是這些圖片的格式是錯誤的,根本顯示不了圖像。
在網(wǎng)上查了一下,上面的現(xiàn)象是因為webpack5已經(jīng)自帶了圖片解析功能,再使用url-loader的話會出現(xiàn)圖片重復打包的問題。現(xiàn)在有兩種解決方案:
1.繼續(xù)使用使用url-loader。
2.改用webpack5自身來解析圖片
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 10000,
},
},
generator: {
filename: 'media/image/[name].[hash:8].[ext]',
},
},
看了一下npm,url-loader已經(jīng)兩年沒有更新了。穩(wěn)妥起見,還是webpack自帶的圖片解析吧。
打包文件命名問題
webpack4的時候,代碼編譯后的文件夾命名是按順序自增的數(shù)字id:

這樣就存在一個問題,如果命名排在前面的模塊被移除了,那么后面的模塊就會補位,這會導致即使后續(xù)模塊內容沒有發(fā)生變化,但是因為文件夾名稱發(fā)生了改變,之前的緩存就會失效了。
現(xiàn)在webpack5讓開發(fā)者可以自己設置chunk和module的命名方式:
optimization: {
chunkIds: 'named', // 'natural' | 'named' | 'size' | 'deterministic'
moduleIds: 'named',
}
- natural 就是webpack5以前生產模式下的命名方式。
- named 是根據(jù)項目文件路徑生成名稱,webpack以前和現(xiàn)在的開發(fā)環(huán)境模式默認使用這種命名方式
- size 是根據(jù)模塊大小來生成數(shù)字。
- deterministic 是webpack5新增的命名方式,根據(jù)文件名稱生成短hash,webpack5生產模式下默認啟用。
廢棄了ModuleConcatenationPlugin
項目打包完在線上運行的時候出現(xiàn)了報錯:
Uncaught TypeError: Cannot read properties of undefined (reading 'call')
錯的是這段代碼:
function __webpack_require__(moduleId) {
var cachedModule = __webpack_module_cache__[moduleId]
if (void 0 !== cachedModule) return cachedModule.exports
var module = (__webpack_module_cache__[moduleId] = {
id: moduleId,
loaded: !1,
exports: {},
})
return (
__webpack_modules__[moduleId].call(
module.exports,
module,
module.exports,
__webpack_require__
),
(module.loaded = !0),
module.exports
)
}
webpack實現(xiàn)了一個加載函數(shù) __webpack_require__(moduleId) 來加載模塊。這個函數(shù)做了這些事情:
- 根據(jù)模塊Id在緩存中找是否有這個模塊,如果有直接從緩存拿
- 如果緩存沒有,那么先新建這個模塊的緩存,然后再返回模塊
__webpack_modules__[moduleId].call() 會報錯八成是因為webpack找不到對應模塊,__webpack_modules__[moduleId] 的結果是 undefined 。
順著控制臺列出來的報錯文件繼續(xù)往下看,看看是哪里使調用了 __webpack_require__(moduleId) :
"./node_modules/@babel/runtime/helpers/esm/slicedToArray.js": function(__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) {
"use strict";
...
var arrayWithHoles = __webpack_require__(null);
var unsupportedIterableToArray = __webpack_require__(null)
, nonIterableRest = __webpack_require__(null);
...
},
破案了,顯然 __webpack_require__(null) 是有問題的。
在網(wǎng)上搜了一下,找到了解決方案:

之前公司webapck4的配置中,在生產環(huán)境會啟用 new webpack.optimize.ModuleConcatenationPlugin() :
plugins: [ !isEnvDevelopment && new webpack.optimize.ModuleConcatenationPlugin() ]
看了一下webpack的開發(fā)文檔,webpack在打包的時候會將各個模塊打包成閉包,用了ModuleConcatenationPlugin之后會盡可能將模塊放到一個閉包內,這樣會提升代碼在瀏覽器中的執(zhí)行速度。這種操作還有一個專業(yè)名詞,叫 Scope Hoisting,意思就是作用域提升。
不過現(xiàn)在ModuleConcatenationPlugin已經(jīng)廢棄了,把上面的代碼刪除以后,項目就能正常運行了。
升級前后對比
| 第一次打包 | 第二次打包 | 第三次打包 | 打包體積 | |
|---|---|---|---|---|
| webpack4 | 325.75s | 106.36s | 105.89s | 56.9MB |
| webpack5 | 252.91s | 33.23s | 23.86s | 30.7MB |
沒有緩存,且不保留之前打包的文件
有緩存,但不保留之前打包的文件
有緩存,且保留之前打包的文件
總結
到此這篇關于webpack4升級到webpack5實戰(zhàn)經(jīng)驗總結的文章就介紹到這了,更多相關webpack4升級到webpack5內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
基于Marquee.js插件實現(xiàn)的跑馬燈效果示例
這篇文章主要介紹了基于Marquee.js插件實現(xiàn)的跑馬燈效果,結合實例形式給出了Marquee.js插件的定義及具體使用方法,需要的朋友可以參考下2017-01-01
JS組件Bootstrap Table表格多行拖拽效果實現(xiàn)代碼
這篇文章主要介紹了JS組件Bootstrap Table表格多行拖拽效果實現(xiàn)代碼,需要的朋友可以參考下2015-12-12

