為了前端的深度-webpack熱更新

lionel愛學習發表於2019-01-14

結論

這篇文章就先把結論放在最前面啦,應該有利於閱讀體驗。

  1. webpack構建的專案,分為server端和client端(也就是瀏覽器),專案啟動時,雙方會保持一個socket連線,用來通話。
  2. 當webpack監聽到檔案改動,server向client傳送一個hash值,client儲存下來,然後server再傳送一個ok訊息。client接收到表示知道可以載入更新了,於是呼叫reloadApp方法。
  3. reloadApp 會呼叫 check方法,然後check呼叫hotDownloadManifest方法,這時候會下載我們提到過的json檔案。
  4. hotEnsureUpdateChunk完成後呼叫hotDownloadManifest方法,這個方法會通過jsonp的格式載入新的程式碼。
  5. 載入完成之後,會有其他的方法對其對比分析,用目前最快的方法去更新檔案。

起源

上回書說到git的用法,這篇文章就說下我學習webpack熱更新原理的記錄。

還是因為上次面試的事,面試官問我是否知道webpack熱更新的原理,我回答的是我使用vue-cli3構建專案,初始化的時候,webpack整合了一個可以監聽檔案變化,並且通知頁面重新整理的元件,他問能詳細點嗎,我...

回家後,開啟電腦,執行專案,開啟瀏覽器控制檯,選擇 network ,先清除之前的載入記錄。這時候去修改我的專案檔案,儲存,回來看下控制檯,再修改,再回來看下控制檯,這個時候讓我發現了這個:

1

一個hot-update.json檔案和一個hot-update.js檔案。有些年頭的前端開發者會發現,hot-update.js返回的內容,怎麼很像jsonp返回的內容。我突然覺得,我是不是要入門了,懷著激動的心情,我四處探索,現在按正確的時間線梳理一下。

1.保持瀏覽器控制檯,重新整理網頁,如下圖,關鍵點在於 app.js,hot-update.js,websocket。

2

2.開啟app.js,很快能找到如下程式碼

為了前端的深度-webpack熱更新

3.修改檔案,控制檯會變成如下

為了前端的深度-webpack熱更新

4.同時頁面中也會插入一個script

為了前端的深度-webpack熱更新

從以上截圖,我先做個推論: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回撥。

本文結束,這些內容已經有很多人寫過啦,本文只是用來記錄自己的學習歷程和加深自己的印象,如有錯誤,請指正!

本文參考:

HMR的原理 (特別鳴謝,寫的真好)

Webpack的HMR原理分析

Webpack 熱更新實現原理分析

相關文章