最近將公司專案由webpack4升級到了webpack5,配置了webpack5的長效快取後,二次構建速度直接提速了80%以上,同時打包體積也減少了,當然前提是要調研清楚坑多不多。
網上有一些做法是直接全部升級相關的包,而我是一個個包升級過來,如果有必要再升級,有的包沒有升級(如babel-loader等等),專案本身用的大多也是較新的版本。
Webpack5 新特性
首先先要了解下升級 Webpack5能給我們帶來什麼好處,先看看 Webpack5的新特性
- 通過持久化硬碟快取能力來提升構建效能
- 通過更好的演算法來改進長期快取(降低產物資源的快取失效率)
- 通過更好的 Tree Shaking 能力和程式碼的生成邏輯來優化產物的大小
- 改善 web 平臺的相容效能力
- 清除了內部結構中,在 Webpack4 沒有重大更新而引入一些新特性時所遺留下來的一些奇怪的 state
- 通過引入一些重大的變更為未來的一些特性做準備,使得能夠長期的穩定在 Webpack5 版本上
對我來說最吸引人的還是構建效能的提升和更好的tree shaking 能力來優化產物的大小。
Webpack5 版本上為什麼構建速度有了質的飛躍?
主要是因為:
webpack4是根據程式碼的結構生成chunkhash,新增了空白行或註釋,會引起chunkhash的變化,webpack5是根據內容生成chunkhash,改了註釋或者變數不會引起chunkhash的變化,瀏覽器可以繼續使用快取。
- 優化了對快取的使用效率。在webpack4 中,chunkId與moduleId都是自增id。只要我們新增一個模組,那麼程式碼中module的數量就會發生變化,從而導致moduleId發生變化,於是檔案內容就發生了變化。chunkId也是如此,新增一個入口的時候,chunk數量的變化造成了chunkId的變化,導致了檔案內容變化。所以對實際未改變的chunk檔案不能有效利用。webpack5採用新的演算法來計算確定性的chunkId和moduleId。可以有效利用快取。在production模式下,optimization.chunkIds和optimization.moduleIds預設會設為’deterministic’。
- 新增了可以將快取寫入磁碟的配置項, 在命令列終止當前構建任務,再次啟動構建時,可以複用上一次寫入硬碟的快取,加快構建過程。
以上三點解釋引用自Webpack5構建速度提升令人驚歎,早升級早受益
簡單瞭解上述後,簡單記錄下我在升級過程做的一些事情,要看完整的建議去官方升級文件查閱
直接升級依賴
以下一些庫在我們專案直接升級到最新版本後是不需要做相容的,本身配置不多,但是如果你專案外掛寫的配置項多就可能有需要做相容的地方。
- webpack webpack-cli
- html-webpack-plugin
- terser-webpack-plugin
- mini-css-extract-plugin
- style-loader、css-loader、postcss、postcss-loader(升級)
相容
資源模組型別
在 webpack4及之前我們會用各種loader來處理一些資源,比如file-loader
,url-loader
,raw-loader
等等,但webpack5內建了靜態資源構建能力,不再需要安裝這些額外的 loader,通過簡單的配置就能實現靜態資源的打包。
資源模組型別(asset module type),通過新增 4 種新的模組型別,來替換所有這些 loader:
asset/resource
傳送一個單獨的檔案並匯出 URL。之前通過使用file-loader
實現。asset/inline
匯出一個資源的 data URI。之前通過使用url-loader
實現。asset/source
匯出資源的原始碼。之前通過使用raw-loader
實現。asset
在匯出一個 data URI 和傳送一個單獨的檔案之間自動選擇。之前通過使用url-loader
,並且配置資源體積限制實現。
例子:
module: {
rules: [
{
test: /\.(png|jpg|jpeg|gif)$/,
type: `asset/resource`
}
]
},
webpack-dev-server
需要注意的是:webpack-dev-server v4.0.0+ requires node >= v12.13.0
升級 webpack-dev-server 至 ^4(next) 版本,否則 HMR 會有異常
- wbepack4 啟動方式為:
webpack-dev-server
。 webpack5 修改為:
webpack server
// v4 devServer: { contentBase: path.resolve(__dirname, '../dist'), compress: true, inline: true, // 在構建變化後的程式碼會通過代理客戶端來控制網頁重新整理 host: '0.0.0.0', port: PORT, clientLogLevel: 'info', historyApiFallback: true, stats: 'errors-only', disableHostCheck: true, }
// v5 devServer: { // contentBase 變為 static 物件裡面來配置 static: { directory: path.resolve(__dirname, '../dist'), }, client: { logging: 'info', }, compress: true, // inline: true, // 直接移除,沒有替代項 host: '0.0.0.0', port: PORT, historyApiFallback: true, allowedHosts: 'all', // 代替 disableHostCheck: true // 新增中介軟體配置 devMiddleware: { stats: 'errors-only', }, },
相關文章:
webpack-dev-server v3 遷移 v4 指南
Webpack5 移除了 Node.js Polyfill(相容)
Webpack5 移除了 Node.js Polyfill,將會導致一些包變得不可用(會在控制檯輸出 'XXX' is not defined),如果需要相容 process/buffer 等 Nodejs Polyfill,則要安裝相關的 Polyfill:process,並在 Plugin 中顯式宣告注入。業務程式碼中是有使用的process 變數的,故需要相容,同時要安裝process/buffer 庫。
{
plugins: [
new webpack.ProvidePlugin({
process: 'process/browser',
}),
]
}
升級廢棄的配置項
未改動前的專案配置項含義(config/webpack_prod.ts):
splitChunks: {
chunks: 'all',
minSize: 30000, // 模組要大於30kb才會進行提取
minChunks: 1, // 最小引用次數,預設為1
maxAsyncRequests: 5, // 非同步載入程式碼時同時進行的最大請求數不得超過5個
maxInitialRequests: 3, // 入口檔案載入時最大同時請求數不得超過3個
automaticNameDelimiter: '_', // 模組檔名稱字首
name: true, // 自動生成檔名
cacheGroups: {
// 將來自node_modules的模組提取到一個公共檔案中
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10, // 執行優先順序,預設為0
},
// 其他不是node_modules中的模組,如果有被引用不少於2次,那麼也提取出來
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true, // 如果當前程式碼塊包含的模組已經存在,那麼不在生成重複的塊
},
},
},
// v5
splitChunks: {
cacheGroups: {
// vendors ——> defaultVendors
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10, // 執行優先順序,預設為0
},
},
},
這個在(config/webpack_prod.ts)裡面改個名字即可
splitChunks.name(移除)
splitChunks.name 表示抽取出來檔案的名字
- 在 v4 中該配置預設為 true 表示自動生成檔名
- v5 移除了 optimization.splitChunks 的 name: true:不再支援自動命名
遷移:使用預設值。chunkIds: "named" 會為你的檔案取一個有用的名字,以便於除錯
output 配置(相容)
(node:58337) [DEP_WEBPACK_TEMPLATE_PATH_PLUGIN_REPLACE_PATH_VARIABLES_HASH] DeprecationWarning: [hash] is now [fullhash] (also consider using [chunkhash] or [contenthash], see documentation for details)
hash已經不推薦使用了,改為了fullhash,這個名字比起原來的hash更清楚
config/webpack_dev.ts
// v4
output: {
filename: 'js/[name].[hash].js',
chunkFilename: 'js/[name].[hash].js',
},
// v5
output: {
filename: 'js/[name].[fullhash].js',
chunkFilename: 'js/[name].[fullhash].js',
},
優化配置項(廢棄移除)
(config/webpack_prod.ts)
// webpack v4,在 v5 已被廢棄,故需移除
new webpack.HashedModuleIdsPlugin()
HashedModuleIdsPlugin 作用:實現持久化快取。模組 ID 通過 HashedModuleIdsPlugin來進行計算,它會把基於數字增量的 ID 替換成模組自身的 hash。這樣的話,一個模組的 ID 只會在重新命名或者移除的時候才會改變,新模組不會影響到它的 ID 變化。
webpack5 增加了確定的 moduleId,chunkId 的支援,如下配置:
optimization.moduleIds = 'deterministic'
optimization.chunkIds = 'deterministic'
此配置在生產模式下是預設開啟的,它的作用是以確定的方式為 module 和 chunk 分配 3-5 位數字 id,替代 v4 版本的 HashedModuleIdsPlugin。
全域性變數寫法(相容)
module.exports = () => {
return {
// ...
plugins: [
// webpack5 定義環境變數的寫法變了
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'),
}),
// webpack4的寫法
// new webpack.DefinePlugin({
// 'process.env': {
// NODE_ENV: JSON.stringify('production'),
// },
// }),
],
};
};
使用cache屬性,快取進行優化(新增)
預設情況下 webpack5 不會啟用持久化快取,不自己手動加 cache 配置,webpack5等於沒優化,直接升級二次構建反而更慢;並不是升級就自動提速,還是要手動配置
當設定 cache.type: "filesystem" 時,webpack 會在內部以分層方式啟用檔案系統快取和記憶體快取。 從快取讀取時,會先檢視記憶體快取,如果記憶體快取未找到,則降級到檔案系統快取。 寫入快取將同時寫入記憶體快取和檔案系統快取。
檔案系統快取不會直接將對磁碟寫入的請求進行序列化。它將等到編譯過程完成且編譯器處於空閒狀態才會執行。 如此處理的原因是序列化和磁碟寫入會佔用資源,並且我們不想額外延遲編譯過程。
cache: {
// 將快取型別設定為檔案系統,預設為memory
type: 'filesystem',
buildDependencies: {
// 當構建依賴的config檔案(通過 require 依賴)內容發生變化時,快取失效
config: [__filename],
// 如果你有其他的東西被構建依賴,你可以在這裡新增它們
},
},
為了防止快取過於固定,導致更改構建配置無感知,依然使用舊的快取,預設情況下,每次修改構建配置檔案都會導致重新開始快取。當然也可以自己主動設定 version 來控制快取的更新。
最終優化成果
第一次編譯:
第二次編譯:
效果:二次構建提速85%以上