中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Webpack實現熱更新的原理是什么

發布時間:2021-06-15 11:08:45 來源:億速云 閱讀:706 作者:Leah 欄目:web開發

本篇文章給大家分享的是有關Webpack實現熱更新的原理是什么,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

什么是熱更新

模塊熱替換(hot module replacement 或 HMR)是 webpack 提供的最有用的功能之一。它允許在運行時更新所有類型的模塊,而無需完全刷新

一般的刷新我們分兩種:

  •  一種是頁面刷新,不保留頁面狀態,就是簡單粗暴,直接 window.location.reload()。

  •  另一種是基于 WDS (Webpack-dev-server) 的模塊熱替換,只需要局部刷新頁面上發生變化的模塊,同時可以保留當前的頁面狀態,比如復選框的選中狀態、輸入框的輸入等。

可以看到相比于第一種,熱更新對于我們的開發體驗以及開發效率都具有重大的意義

HMR 作為一個 Webpack 內置的功能,可以通過 HotModuleReplacementPlugin 或 --hot 開啟。

具體我們如何在 webpack 中使用這個功能呢?

熱更新的使用以及簡單分析

如何使用熱更新

npm install webpack webpack-dev-server --save-dev

設置 HotModuleReplacementPlugin,HotModuleReplacementPlugin 是 webpack 是自帶的

plugins: {      HotModuleReplacementPlugin: new webpack.HotModuleReplacementPlugin()  }

再設置一下 devServer

devServer: {      contentBase: path.resolve(__dirname, 'dist'),      hot: true, // 重點關注      historyApiFallback: true,      compress: true  }
  •  hot 為 true,代表開啟熱更新

兩個重要的文件

當我們改變我們項目的文件的時候,比如我修改 Vue 的一個 方法:

更改前:

clickMe() {    console.log('我是 Gopal,歡迎關注「前端雜貨鋪」');  }

更改后:

clickMe() {    console.log('我是 Gopal,歡迎關注「前端雜貨鋪」,一起學習成長吧');  }

瀏覽器會去請求兩個文件

Webpack實現熱更新的原理是什么

接下來我們看看這兩個文件:

  •  JSON 文件,h 代表本次新生成的 Hash 值為 0c256052432b51ed32c8——本次輸出的 Hash 值會被作為下次熱更新的標識。c 表示當前要熱更新的文件對應的是哪個模塊,可以讓 webpack 知道它要更新哪個模塊 

{      "h": "0c256052432b51ed32c8",      "c": {          "201": true      }  }
  •  js 文件,就是本次修改的代碼,重新編譯打包后的,大致是下面這個樣子(已刪減一些并格式化過,這里看不懂沒關系的,就記住是返回要更新的模塊就好了),webpackHotUpdate 方法就是用來更新模塊的,201 對應的是哪個模塊(我們稱它為模塊標識),其他的就是要更新的模塊的內容了 

webpackHotUpdate(201, {    "./src/views/moveTransfer/list/index.vue?vue&type=script&lang=js&": function (      module,      exports,      __webpack_require__   ) {      "use strict";      var _Object$defineProperty = __webpack_require__(        /*! @babel/runtime-corejs3/core-js-stable/object/define-property */ "./node_modules/@babel/runtime-corejs3/core-js-stable/object/define-property.js"      );      _Object$defineProperty(exports, "__esModule", {        value: true,      });      exports.default = void 0;      var _default = {        data: function data() {          return {};        },        computed: {},        methods: {          clickMe: function clickMe() {            console.log("我是 Gopal,歡迎關注「前端雜貨鋪」,一起學習成長吧");          },        },      };      exports.default = _default;    },  });

那么問題來了,我修改了文件,瀏覽器是怎么知道要更新的呢?

了解一下 Websocket

熱更新使用到了 Websocket,這里不會細講 Websocket,可以看下阮一峰老師的 WebSocket 教程,下面是一個 簡單的例子

// 執行上面語句之后,客戶端就會與服務器進行連接。  var ws = new WebSocket("wss://echo.websocket.org");  // 實例對象的 onopen 屬性,用于指定連接成功后的回調函數  ws.onopen = function(evt) {     console.log("Connection open ...");     ws.send("Hello WebSockets!");  };   // 實例對象的 onmessage 屬性,用于指定收到服務器數據后的回調函數。可以接受二進制數據,blob 對象或者 Arraybuffer 對象  ws.onmessage = function(evt) {    console.log( "Received Message: " + evt.data);    ws.close();  };  // 實例對象的 onclose 屬性,用于指定連接關閉后的回調函數。  ws.onclose = function(evt) {    console.log("Connection closed.");  };

上面通過 new Websocket 創建一個客戶端與服務端通信的實例,并通過 onmessage屬性,接受指定服務器返回的數據,并進行相應的處理。

這里大概解釋下,為什么是 Websocket ?因為 Websocket 是一種雙向協議,它最大的特點就是 服務器可以主動向客戶端推送消息,客戶端也可以主動向服務器發送信息。這是 HTTP 不具備的,熱更新實際上就是服務器端的更新通知到客戶端,所以選擇了 Websocket

接下來讓我們進一步的討論關于熱更新的原理

熱更新原理

熱更新的過程

幾個重要的概念(這里有一個大致的概念就好,后面會把它們串起來):

  •  Webpack-complier :webpack 的編譯器,將 JavaScript 編譯成 bundle(就是最終的輸出文件)

  •  HMR Server:將熱更新的文件輸出給 HMR Runtime

  •  Bunble Server:提供文件在瀏覽器的訪問,也就是我們平時能夠正常通過 localhost 訪問我們本地網站的原因

  •  HMR Runtime:開啟了熱更新的話,在打包階段會被注入到瀏覽器中的 bundle.js,這樣 bundle.js 就可以跟服務器建立連接,通常是使用 websocket ,當收到服務器的更新指令的時候,就   去更新文件的變化

  •  bundle.js:構建輸出的文件

啟動階段

文件經過 Webpack-complier 編譯好后傳輸給 Bundle Server,Bundle Server 可以讓瀏覽器訪問到我們打包出來的文件

下面流程圖中的 1、2、A、B階段

文件熱更新階段

文件經過 Webpack-complier 編譯好后傳輸給 HMR Server,HMR Server 知道哪個資源(模塊)發生了改變,并通知 HMR Runtime 有哪些變化(也就是上面我們看到的兩個請求),HMR Runtime 就會更新我們的代碼,這樣我們瀏覽器就會更新并且不需要刷新

下面流程圖的 1、2、3、4、5 階段

參考 19 | webpack中的熱更新及原理分析

Webpack實現熱更新的原理是什么

深入——源碼閱讀

我們還看回上圖,其中啟動階段圖中的 1、2、A、B階段就不講解了,主要看熱更新階段主要講 3、4 和 5 階段

在開始接下開的閱讀前,我們再回到最初的問題上我本地修改了文件,瀏覽器是怎么知道要更新的呢?

通過上面的流程圖,其實我們可以猜測,本地實際上啟動了一個 HMR Server 服務,而且在啟動 Bundle Server 的時候已經往我們的 bundle.js 中注入了 HMR Runtime(主要用來啟動 Websocket,接受 HMR Server 發來的變更)

所以我們聚焦以下幾點:

  •  Webpack 如何啟動了 HMR Server

  •  HMR Server 如何跟 HMR Runtime 進行通信的

  •  HMR Runtime 接受到變更之后,如何生效的

以下的源碼解析分別對應的版本是:

  •  webpack——5.24.3

  •  webpack-dev-server——4.0.0-beta.0

  •  webpack-dev-middleware——4.1.0

啟動 HMR Server

這個工作主要是在 webpack-dev-server 中完成的

看 lib/Server.js setupApp 方法,下面的 express 服務實際上對應的是 Bundle Server

setupApp() {    // Init express server    // eslint-disable-next-line new-cap    // 初始化 express 服務    // 使用 express 框架啟動本地 server,讓瀏覽器可以請求本地的靜態資源。    this.app = new express();  }

啟動服務結束之后就通過 createSocketServer 創建 websocket 服務

listen(port, hostname, fn) {    this.hostname = hostname;    return (      findPort(port || this.options.port)       .then((port) => {          this.port = port;          return this.server.listen(port, hostname, (err) => {            if (this.options.hot || this.options.liveReload) {              // 啟動 express 服務之后,啟動 websocket 服務              this.createSocketServer();            }          });        })    );  }
createSocketServer() {    this.socketServer = new this.SocketServerImplementation(this);    this.socketServer.onConnection((connection, headers) => {     });  }

HMR Server 和 HMR Runtime 的通信

首先要通信的第一個問題在于——通信的時機,什么時候我去通知客戶端我的文件更新。通過 webpack 創建的 compiler 實例(監聽本地文件的變化、文件改變自動編譯、編譯輸出),可以往 compiler.hooks.done 鉤子(代表 webpack 編譯完之后觸發)注冊事件, 當監聽到一次 webpack 編譯結束,就會調用 sendStats 方法

看 lib/Server.js 中的 setupHooks 方法

// lib/Server.js  // 綁定監聽事件  setupHooks() {    // ...    const addHooks = (compiler) => {      // 監聽 webpack 的 done 鉤子,tapable 提供的監聽方法      // done 標識編譯結束      const { compile, invalid, done } = compiler.hooks;      compile.tap('webpack-dev-server', invalidPlugin);      invalid.tap('webpack-dev-server', invalidPlugin);     done.tap('webpack-dev-server', (stats) => {        // 當監聽到一次webpack編譯結束,就會調用 sendStats 方法        this.sendStats(this.sockets, this.getStats(stats));        this.stats = stats;      });    };  }

當監聽到一次 webpack 編譯結束,就會調用 sendStats 方法,里面會向客戶端發送 hash 和 ok 事件

// lib/Server.js  // send stats to a socket or multiple sockets  sendStats(sockets, stats, force) {    // ok和 hash    this.sockWrite(sockets, 'hash', stats.hash);   if (stats.errors.length > 0) {      this.sockWrite(sockets, 'errors', stats.errors);    } else if (stats.warnings.length > 0) {      this.sockWrite(sockets, 'warnings', stats.warnings);    } else {      this.sockWrite(sockets, 'ok');    } }

在 client-src/default/index.js 中,會去更新 hash,并且在 ok 的時候去進行檢查更新 reloadApp

// client-src/default/index.js   const onSocketMessage = {    // 更新 current Hash    hash(hash) {      status.currentHash = hash;    },    'progress-update': function progressUpdate(data) {      if (options.useProgress) {        log.info(`${data.percent}% - ${data.msg}.`);      }      sendMessage('Progress', data);    },    ok() {      sendMessage('Ok');      if (options.useWarningOverlay || options.useErrorOverlay) {        overlay.clear();      }      if (options.initial) {        return (options.initial = false);      }      // 進行更新檢查等操作      reloadApp(options, status);    }  };

接下來我們看看 client-src/default/utils/reloadApp.js 中的 reloadApp。這里又利用 node.js 的 EventEmitter,發出webpackHotUpdate 消息。這里又將更新的事情給回了 webpack(為了更好的維護代碼,以及職責劃分的更明確。)

function reloadApp(    { hotReload, hot, liveReload },    { isUnloading, currentHash }  ) {    // ...    if (hot) {      log.info('App hot update...');      //  hotEmitter 其實就是 EventEmitter 的實例      const hotEmitter = require('webpack/hot/emitter');      // 又利用 node.js 的 EventEmitter,發出 webpackHotUpdate 消息。      // websocket 僅僅用于客戶端(瀏覽器)和服務端進行通信。而真正做事情的活還是交回給了 webpack。     hotEmitter.emit('webpackHotUpdate', currentHash);      if (typeof self !== 'undefined' && self.window) {        // broadcast update to window        self.postMessage(`webpackHotUpdate${currentHash}`, '*');      }   }    // ...  }  module.exports = reloadApp;

在 webpack 的 hot/dev-server.js 中,監聽 webpackHotUpdate 事件,并執行 check 方法。并在 check 方法中調用 module.hot.check 方法進行熱更新。

// hot/dev-server.js  // 監聽webpackHotUpdate事件  hotEmitter.on("webpackHotUpdate", function (currentHash) {    lastHash = currentHash;    if (!upToDate() && module.hot.status() === "idle") {      log("info", "[HMR] Checking for updates on the server...");      check();    }  });
var check = function check() {    //  moudle.hot.check 開始熱更新    // 之后的源碼都是HotModuleReplacementPlugin塞入到bundle.js中的哦,我就不寫文件路徑了    module.hot      .check(true)      .then(function (updatedModules) {        // ...      })      .catch(function (err) {        // ...      });  };

至于 module.hot.check ,實際上通過 HotModuleReplacementPlugin 已經注入到我們 chunk 中了(也就是我們上面所說的 HMR Runtime),所以后面就是它是如何更新 bundle.js 的呢?

HMR Runtime 中更新 bundle.js

如果我們仔細看我們的打包后的文件的話,開啟熱更新之后生成的代碼會比不開啟多出很多東西(為了更加直觀看到,可以將其輸出到本地),這些就是幫助 webpack 在瀏覽器端去更新 bundle.js 的 HMR Runtime 代碼

來看打包后的代碼中新增了一個 createModuleHotObject

module.hot = createModuleHotObject(options.id, module);

實際上這個函數就是用來返回一個 hot 對象,所以調用 module.hot.check 的時候,實際上就是執行 hotCheck 函數

function createModuleHotObject(moduleId, me) {    var hot = {      // Module API      addDisposeHandler: function (callback) {        hot._disposeHandlers.push(callback);      },      removeDisposeHandler: function (callback) {        var idx = hot._disposeHandlers.indexOf(callback);        if (idx >= 0) hot._disposeHandlers.splice(idx, 1);      },      // Management API      check: hotCheck,      apply: hotApply,      status: function (l) {        if (!l) return currentStatus;       registeredStatusHandlers.push(l);      },      addStatusHandler: function (l) {        registeredStatusHandlers.push(l);      },      removeStatusHandler: function (l) {        var idx = registeredStatusHandlers.indexOf(l);        if (idx >= 0) registeredStatusHandlers.splice(idx, 1);      },   };    currentChildModule = undefined;    return hot;  }

其中就有 hotCheck 中調用了 __webpack_require__.hmrM

function hotCheck(applyOnUpdate) {    setStatus("check");      return __webpack_require__.hmrM().then(function (update) {    }  }

__webpack_require__.hmrM——加載.hot-update.json

來看 __webpack_require__.hmrM, 其中 __webpack_require__.p 指的是我們本地服務的域名,類似 http://0.0.0.0:9528 , 另外 __webpack_require__.hmrF 去獲取 .hot-update.json 文件的地址,就是我們之前提到的重要文件之一

__webpack_require__.hmrM = () => {    if (typeof fetch === "undefined") throw new Error("No browser support: need fetch API");    return fetch(__webpack_require__.p + __webpack_require__.hmrF()).then((response) => {      if(response.status === 404) return; // no update available      if(!response.ok) throw new Error("Failed to fetch update manifest " + response.statusText);      return response.json();    });  };
/* webpack/runtime/get update manifest filename */  (() => {    __webpack_require__.hmrF = () => ("main." + __webpack_require__.h() + ".hot-update.json");  })();

加載要更新的模塊

下面來看如何加載我們要更新的模塊的,可以看到打包出來的代碼中有 loadUpdateChunk

function loadUpdateChunk(chunkId) {    return new Promise((resolve, reject) => {      var url = __webpack_require__.p + __webpack_require__.hu(chunkId);      // create error before stack unwound to get useful stacktrace later      var error = new Error();     var loadingEnded = (event) => {        // ...加載后的處理      };      __webpack_require__.l(url, loadingEnded);    });  }

再來看 __webpack_require__.l,主要通過類似 JSONP 的方式進行,因為JSONP獲取的代碼可以直接執行。

__webpack_require__.l = (url, done, key, chunkId) => {    // ...    if (!script) {      script = document.createElement("script");      script.charset = "utf-8";      script.timeout = 120;      if (__webpack_require__.nc) {        script.setAttribute("nonce", __webpack_require__.nc);      }      script.setAttribute("data-webpack", dataWebpackPrefix + key);      script.src = url;    }    // ...    needAttach && document.head.appendChild(script); };

還記得我們一開始提到的返回的 JS 中就是一個 webpackHotUpdate 函數么?實際上在我們的 HMR Runtime 中就是全局定義了(下面的名稱是 webpackHotUpdatelearn_hot_reload,應該是 webpack 版本不一樣導致的,不影響理解)至于生成的代碼是如何生效的,請移步我的另外一篇文章——【Webpack 進階】Webpack 打包后的代碼是怎樣的?

// webpackHotUpdate + 項目名  self["webpackHotUpdatelearn_hot_reload"] = (chunkId, moreModules, runtime) => {    for(var moduleId in moreModules) {      if(__webpack_require__.o(moreModules, moduleId)) {        currentUpdate[moduleId] = moreModules[moduleId];        if(currentUpdatedModulesList) currentUpdatedModulesList.push(moduleId);      }    }    if(runtime) currentUpdateRuntime.push(runtime);    if(waitingUpdateResolves[chunkId]) {      waitingUpdateResolves[chunkId]();      waitingUpdateResolves[chunkId] = undefined;    }  };

所以,客戶端接受到服務器端推動的消息后,如果需要熱更新,瀏覽器發起 http 請求去服務器端獲取新的模塊資源解析并局部刷新頁面

以上整體的流程如下所示:

Webpack實現熱更新的原理是什么

總結

本文介紹了 webpack 熱更新的簡單使用、相關的流程以及原理。小結一下,webpack 如果開啟了熱更新的時候

  •  HMR Runtime 通過 HotModuleReplacementPlugin 已經注入到我們 chunk 中了

  •  除了開啟一個 Bundle Server,還開啟了 HMR Server,主要用來和 HMR Runtime 中通信

  •  在編譯結束的時候,通過 compiler.hooks.done,監聽并通知客戶端

  •  客戶端接收到之后,就會調用 module.hot.check 等,發起 http 請求去服務器端獲取新的模塊資源解析并局部刷新頁面

Webpack實現熱更新的原理是什么

以上就是Webpack實現熱更新的原理是什么,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

延川县| 兰溪市| 邻水| 永昌县| 呼和浩特市| 吕梁市| 高碑店市| 金寨县| 镇宁| 饶河县| 察哈| 恩施市| 手游| 泰顺县| 揭阳市| 称多县| 河西区| 阿城市| 农安县| 长子县| 镇沅| 宁强县| 蒲江县| 札达县| 新龙县| 合水县| 五莲县| 北辰区| 万盛区| 绥中县| 科尔| 周口市| 秀山| 济宁市| 乌兰浩特市| 垣曲县| 天等县| 昆明市| 邢台县| 静安区| 崇阳县|