一、是什麼
HMR
全稱 Hot Module Replacement
,可以理解為模組熱替換,指在應用程式執行過程中,替換、新增、刪除模組,而無需重新重新整理整個應用
例如,我們在應用執行過程中修改了某個模組,通過自動重新整理會導致整個應用的整體重新整理,那頁面中的狀態資訊都會丟失
如果使用的是 HMR
,就可以實現只將修改的模組實時替換至應用中,不必完全重新整理整個應用
在webpack
中配置開啟熱模組也非常的簡單,如下程式碼:
const webpack = require('webpack')
module.exports = {
// ...
devServer: {
// 開啟 HMR 特性
hot: true
// hotOnly: true
}
}
通過上述這種配置,如果我們修改並儲存css
檔案,確實能夠以不重新整理的形式更新到頁面中
但是,當我們修改並儲存js
檔案之後,頁面依舊自動重新整理了,這裡並沒有觸發熱模組
所以,HMR
並不像 Webpack
的其他特性一樣可以開箱即用,需要有一些額外的操作
我們需要去指定哪些模組發生更新時進行HRM
,如下程式碼:
if(module.hot){
module.hot.accept('./util.js',()=>{
console.log("util.js更新了")
})
}
二、實現原理
首先來看看一張圖,如下:
- Webpack Compile:將 JS 原始碼編譯成 bundle.js
- HMR Server:用來將熱更新的檔案輸出給 HMR Runtime
- Bundle Server:靜態資原始檔伺服器,提供檔案訪問路徑
- HMR Runtime:socket伺服器,會被注入到瀏覽器,更新檔案的變化
- bundle.js:構建輸出的檔案
- 在HMR Runtime 和 HMR Server之間建立 websocket,即圖上4號線,用於實時更新檔案變化
上面圖中,可以分成兩個階段:
- 啟動階段為上圖 1 - 2 - A - B
在編寫未經過webpack
打包的原始碼後,Webpack Compile
將原始碼和 HMR Runtime
一起編譯成 bundle
檔案,傳輸給Bundle Server
靜態資源伺服器
- 更新階段為上圖 1 - 2 - 3 - 4
當某一個檔案或者模組發生變化時,webpack
監聽到檔案變化對檔案重新編譯打包,編譯生成唯一的hash
值,這個hash
值用來作為下一次熱更新的標識
根據變化的內容生成兩個補丁檔案:manifest
(包含了 hash
和 chundId
,用來說明變化的內容)和chunk.js
模組
由於socket
伺服器在HMR Runtime
和 HMR Server
之間建立 websocket
連結,當檔案發生改動的時候,服務端會向瀏覽器推送一條訊息,訊息包含檔案改動後生成的hash
值,如下圖的h
屬性,作為下一次熱更新的標識
在瀏覽器接受到這條訊息之前,瀏覽器已經在上一次socket
訊息中已經記住了此時的hash
標識,這時候我們會建立一個 ajax
去服務端請求獲取到變化內容的 manifest
檔案
mainfest
檔案包含重新build
生成的hash
值,以及變化的模組,對應上圖的c
屬性
瀏覽器根據 manifest
檔案獲取模組變化的內容,從而觸發render
流程,實現區域性模組更新
三、總結
關於webpack
熱模組更新的總結如下:
- 通過
webpack-dev-server
建立兩個伺服器:提供靜態資源的服務(express)和Socket服務 - express server 負責直接提供靜態資源的服務(打包後的資源直接被瀏覽器請求和解析)
- socket server 是一個 websocket 的長連線,雙方可以通訊
- 當 socket server 監聽到對應的模組發生變化時,會生成兩個檔案.json(manifest檔案)和.js檔案(update chunk)
- 通過長連線,socket server 可以直接將這兩個檔案主動傳送給客戶端(瀏覽器)
- 瀏覽器拿到兩個新的檔案後,通過HMR runtime機制,載入這兩個檔案,並且針對修改的模組進行更新
參考文獻
- https://mp.weixin.qq.com/s?__biz=MzU1OTgxNDQ1Nw==&mid=2247487323&idx=1&sn=2ed2873cafb8b45062ca83922b38e866&chksm=fc10cd0dcb67441befcd19a1fb81fa6c11b3202532479998b3c51829c2c6c7e836f9d4a2ef47&scene=178&cur_album_id=1842744101150982149#rd