結論
這篇文章就先把結論放在最前面啦,應該有利於閱讀體驗。
- webpack構建的專案,分為server端和client端(也就是瀏覽器),專案啟動時,雙方會保持一個socket連線,用來通話。
- 當webpack監聽到檔案改動,server向client傳送一個hash值,client儲存下來,然後server再傳送一個ok訊息。client接收到表示知道可以載入更新了,於是呼叫reloadApp方法。
- reloadApp 會呼叫 check方法,然後check呼叫hotDownloadManifest方法,這時候會下載我們提到過的json檔案。
- hotEnsureUpdateChunk完成後呼叫hotDownloadManifest方法,這個方法會通過jsonp的格式載入新的程式碼。
- 載入完成之後,會有其他的方法對其對比分析,用目前最快的方法去更新檔案。
起源
上回書說到git的用法,這篇文章就說下我學習webpack熱更新原理的記錄。
還是因為上次面試的事,面試官問我是否知道webpack熱更新的原理,我回答的是我使用vue-cli3構建專案,初始化的時候,webpack整合了一個可以監聽檔案變化,並且通知頁面重新整理的元件,他問能詳細點嗎,我...
回家後,開啟電腦,執行專案,開啟瀏覽器控制檯,選擇 network ,先清除之前的載入記錄。這時候去修改我的專案檔案,儲存,回來看下控制檯,再修改,再回來看下控制檯,這個時候讓我發現了這個:
一個hot-update.json檔案和一個hot-update.js檔案。有些年頭的前端開發者會發現,hot-update.js返回的內容,怎麼很像jsonp返回的內容。我突然覺得,我是不是要入門了,懷著激動的心情,我四處探索,現在按正確的時間線梳理一下。
1.保持瀏覽器控制檯,重新整理網頁,如下圖,關鍵點在於 app.js,hot-update.js,websocket。
2.開啟app.js,很快能找到如下程式碼
3.修改檔案,控制檯會變成如下
4.同時頁面中也會插入一個script
從以上截圖,我先做個推論:wepack在啟動的時候開啟一個node服務,這個服務通過websocket與瀏覽器保持持續的通訊,在檢測到檔案發生變化時,服務端向瀏覽器傳送一個json和一個js檔案。同時每次服務端傳送的訊息的 hash 將作為下次 hot-update.json 和 hot-update.js 檔案的 hash值。
綜合上面的分析,我覺得我已經完全掌握熱更新流程,本文結束,嘻嘻睡了...
熱更新實現原理分析
上面的推論是錯的,也不是完全錯,只是細節錯了。
一開始我的目標是直接檢視webpack的原始碼,裡面有一個hot的資料夾,在大概讀了其中程式碼,只在裡面找到了一些log輸出和方法,沒有找到在哪裡呼叫的。感謝 HMR的原理 這篇文章讓我找明瞭方向。程式碼是在webpack-dev-server的原始碼下面= =
下面就開始正文了:
當我們構建專案的時候,webpack-dev-server會為我們自己的js檔案包上一層程式碼,加上兩個依賴:
// 這個模組是新加的,我們的入口就是 index,而這裡加了一個模組,引用了 index,並且額外加了兩行 require
/***/ (function(module, exports, __webpack_require__) {
__webpack_require__("./node_modules/webpack-dev-server/client/index.js?http://localhost:8080");
__webpack_require__("./node_modules/webpack/hot/dev-server.js");
module.exports = __webpack_require__("./src/index.js");
/***/ })
/******/ })
複製程式碼
其中client/index.js主要負責建立socket通訊,為我們提供一些監聽方法等等,dev-server提供熱更新的方法。
client/index.js 中有如下程式碼:
const onSocketMsg = {
hash: function msgHash(hash) { // 在 `hash` 事件觸發的時候,把 `hash` 記下來
currentHash = hash;
},
ok: function msgOk() { // `ok` 事件觸發的時候,表示server已經便已完成最新程式碼,
// ...
reloadApp();
}
};
// ...
function reloadApp() {
if (hot) {
log.info('[WDS] App hot update...');
// eslint-disable-next-line global-require
const hotEmitter = require('webpack/hot/emitter');
hotEmitter.emit('webpackHotUpdate', currentHash) // 觸發這個事件
}
// ...
}
複製程式碼
hot/dev-server.js中:
hotEmitter.on("webpackHotUpdate", function(currentHash) {
lastHash = currentHash;
if (!upToDate() && module.hot.status() === "idle") {
log("info", "[HMR] Checking for updates on the server...");
check(); // 觸發check方法
}
});
var check = function check() {
module.hot
.check(true) // 這裡的check方法最終進入到webpack\lib\HotModuleReplacement.runtime.js檔案中
...
}
複製程式碼
HotModuleReplacement.js中有:
function hotCheck(apply) {
...
return hotDownloadManifest(hotRequestTimeout).then(function(update) {
...
{
// 取到了 manifest後,就可以通過jsonp 載入最新的模組的JS程式碼了
hotEnsureUpdateChunk(chunkId);
}
...
});
}
複製程式碼
最終通過jsonp的方式載入,載入js的程式碼如下:
function hotDownloadUpdateChunk(chunkId) {
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.charset = "utf-8";
script.src = $require$.p + $hotChunkFilename$;
if ($crossOriginLoading$) script.crossOrigin = $crossOriginLoading$;
head.appendChild(script);
}
複製程式碼
到此為止,我們就已經得到了新模組的JS程式碼了,下面就是呼叫對應的accpet回撥。
本文結束,這些內容已經有很多人寫過啦,本文只是用來記錄自己的學習歷程和加深自己的印象,如有錯誤,請指正!