1、自動重新整理
webpack.config
配置引數devServer
中存在inline
引數,引數可以設定devServer的兩種模式inline
和iframe
,預設使用inline
devServer: {
contentBase: "./dist",
// inline: false,
hot: true,
},
複製程式碼
官方是這樣描述inline
模式的
Toggle between the dev-server's two different modes. By default the application will be served with inline mode enabled. This means that a script will be inserted in your bundle to take care of live reloading, and build messages will appear in the browser console.
大致意思是說在inline
模式下,在bundle.js
中會插入程式碼來處理自動重新整理,由於沒看原始碼,根據開關inline
模式以後,webpack-dev-server
啟動時候的控制檯輸出,和官方文件說明,所以個人猜測,大致實現:
bundle.js
插入socket
的片段,在瀏覽器載入後建立socket
連線- 利用
webpack -watch
來監聽檔案變化,傳送socket
資訊告知瀏覽器,瀏覽器接受資訊呼叫reload
來實現重新整理頁面重新整理
通過分析瀏覽器請求(inline
模式會在載入bundle.js
後發起一個websocket
請求)和bundle.js
程式碼確認了這一點(onSocketMsg
-> reloadApp
-> applyReload
),也就是說使用webpack-dev-server
啟動就可以開啟自動重新整理功能了
2. HMR
HMR(Hot Module Replacement),新增、修改模組(修改JS/CSS)後瀏覽器內容通過非重新整理的方式自動更新,提高開發效率,要注意HMR只應在開發環境使用
HMR在配置成功以後,修改CSS/JS不會進行頁面重新整理(注意儲存檔案變更的時候,瀏覽器Tab頁是否是自動重新整理)
2.1 HMR配置
HMR配置有四個關鍵點:
- 使用
webpack-dev-server
作為伺服器啟動 webpack.config
的devServer
中配置hot: true
webpack.config
的plugins增加HotModuleReplacementPlugin
- 使用
module.hot.accept
增加HMR程式碼
webpack.config
module.exports = {
mode: 'development',
devtool: 'inline-source-map',
entry: {
main: __dirname + '/app/main.js',
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
devServer: {
contentBase: "./dist",//本地伺服器所載入的頁面所在的目錄
// inline: true, //實時重新整理
hot: true,
},
plugins: [
new HtmlWebpackPlugin({
template: __dirname + '/app/index.tmpl.html'
}),
new webpack.HotModuleReplacementPlugin()
],
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
};
複製程式碼
main.js
import './extra.js'
import './style.css'
if (module.hot) {
module.hot.accept('./extra.js', function() {
console.log('Accepting the updated printMe module!');
})
// 關閉指定子模組的HMR
// module.hot.decline('./extra.js')
}
複製程式碼
extra.js
if(module.hot){
// 監聽當前模組HMR異常
// module.hot.accept(() => {
// console.log('error handler')
// })
// 關閉當前模組HMR
// module.hot.decline()
// 儲存模組資料
// module.hot.dispose(disposeHander)
// if (module.hot.data) {
// console.log(module.hot.data.a)
// }
// 移除模組資料
// module.hot.removeDisposeHandler(disposeHander)
}
複製程式碼
配置成功以後修改extra.js
或者style.css
儲存,都會觸發HMR,控制檯會輸出[HMR]
開頭的日誌
PS:
-
css檔案我們會配置
style-loader
,style-loader
中已經增加了module.hot.accept
的支援,所以即使不配置module.hot.accept
,對於css也可以HMR,但是如果JS沒有呼叫module.hot.accept
,HMR執行找不到對應的內容,則會直接重新整理頁面,可以設定引數hotOnly: true
來防止自動重新整理 -
webpack plugins未新增
HotModuleReplacementPlugin
使用hot: true
的時候會報錯Uncaught Error: [HMR] Hot Module Replacement is disabled.
-
官網配置分為Node版本和Web版本,由於自己也瞭解不深,所以不探討Node版本如何使用
webpack
的HMR
2.2 使用HMR API
要想使用HMR,需要利用到HMR的API,在新增了HotModuleReplacementPlugin
模組後,就可以使用module.hot
的API
Module API
用於模組處理的API
2.2.1 accept
module.hot.accept(dependencies,callback)
:新增需要HMR的模組,以及觸發變更後的回撥,dependencies
支援字串或者字串陣列
module.hot.accept(errorHandler)
:被新增的模組內部使用可以捕獲HMR異常時候丟擲的異常
2.2.2 decline
module.hot.decline(dependencies)
:和accept
操作相反,標記一個不需要HMR管理的模組
module.hot.decline()
:JS模組中使用,將導致該模組無法被HMR
2.2.3 dispose
module.hot.addDisposeHandler
/moduel.hot.dispose(data =>{})
:即使使用HMR,每次更新後資料是不能進行保留的,如果想將資料傳遞給更新後的模組,使用dispose
對全域性物件data
賦值,之後可以通過module.hot.data
來獲取
module.hot.removeDisposeHandler(callback)
:移除通過dispose
和addDisposeHandler
新增的回撥,移除後module.hot.data
將變為空物件{}
(呼叫dispose
之前module.hot.data
是undefined
)
Management API
用於HMR狀態管理的API
2.2.4 status
module.hot.status()
: HMR過程是存在變化狀態的,通過該函式來獲取到HMR的當前狀態
2.3 vue-cli3配置HMR
使用vue-cli3構建,通過vue-cli-service serve
啟動的專案,其本身會新增HMR,在vue.config.js
的devServer
中配置hot: false
可以關閉。
但是專案中發現一個情況,當配置了https: true
以後,必須指定host: localhost
,HMR功能才可以開啟,否則HMR功能將失效(其實是整個自動更新功能都失效了,不會發起socket
請求),而在webpack
中並沒有出現該情況。
據此揣測,vue-cli-service
在使用https: true
的時候,socket
連結必須指定使用hostname
才可以進行構建,根據查詢bundle.js
程式碼,確認該邏輯(可以在bundle.js
查詢socket
關鍵字,可以看到hostname
相關檢測邏輯)。
3、HMR實現
3.1 工作原理
本小節內容都不是人話,可跳過~
一、應用程式(Applicationn):
- 應用程式在獲取到
socket
伺服器傳送的更新資訊 - 檢查HMR的執行狀態
- HMR執行環境非同步下載更新內容並通知應用程式
- 應用程式告訴HMR執行環境去應用更新
- HMR執行環境應用變更資訊
二、編譯部分(Compiler):
修改檔案以後,編譯器觸發更新操作,更新兩部分內容:manifest
和chunks
manifest
包含了新的變化的所有模組的程式碼塊,編譯器確保module IDs
和chunk IDs
在本次構建中
三、模組(Module)
隻影響HMR中包含了的模組(module.hot.accept()
),比如:style-loader
,其中實現了HMR介面,所以在樣式更新的時候,新樣式替換老樣式
如果模組沒有HMR的處理,則更新操作持續冒泡,也就是模組樹中只要單個模組發生了更新,整個都會重新載入
四、執行過程(Runtime)
執行過程會持續使用check
和apply
來進行module.hot.staus
的更新
check
: 請求更新清單,請求成功後,比較更新的內容和當前的內容,將所有更新的內容進行儲存直到所有內容更下載完成,切換到ready
狀態準備
apply
: 將updated的模組標記為invalid
,一直冒泡標記到entry point
,所有無效模組處理完成後,更新所有的accept
的handlers
都會被觸發,然後狀態切換為idle
狀態
3.2 原理解析
檢視bundle.js
中的程式碼,將3.1中的內容翻譯一下:
- 在
webpack-dev-server
配置了hot:true
的基礎上,bundle.js
中的reloadApp()
會進行HMR的邏輯(另一個邏輯是之前面的自動重新整理),這個時候會開始判斷HMR
的status
,併發起一個AJAX請求chunk.hot-update.json
,該請求會返回一個JSON物件
{
c: {
main: true
},
h: nextChunk
}
複製程式碼
- 請求成功以後,繼續更新HMR的
status
,會比較返回的nextChunk
和當前的chunk
是否相同,如果相同結束HMR,如果不同將nextChunk
儲存並繼續進行HMR - 根據
chunk
拼接<script>
的src,並將<script>
動態插入html的<head>
中(比如我的配置就是:main.chunk.hot-update.js),操作後chunk陣列序號+1 - 由於
<head>
插入了新的指令碼<script src="main.chunk.hot-update.js"></script>
,實現動態更新JS而不重新整理頁面
補充:對於新增了module.hot.dispose
的部分,建立了全域性物件data={}
來儲存資料,從而實現重新載入JS以後,仍然可以訪問該數值的內容
3. 總結
HMR的核心點有兩個:
- 如何在服務端檔案變化之後通知客戶端?使用socket
- 如何判斷檔案是否發生變化?客戶端和服務端都儲存
chunk
,比較是否發生變化
webpack的內容其實真的有點多,僅僅HMR包含的內容就很多,自己的總結也不夠完整,只能說是剛好入門。不過整個過程一邊自己猜想,一遍跟原始碼,然後驗證自己猜想,真的是很有意思的過程。
4. 參考
webpack官方文件:
CSDN上一篇特別棒的資料: