溫馨提示×

溫馨提示×

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

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

怎么理解Webpack HMR

發(fā)布時間:2021-11-02 16:21:57 來源:億速云 閱讀:140 作者:iii 欄目:web開發(fā)

本篇內容主要講解“怎么理解Webpack HMR”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“怎么理解Webpack HMR”吧!

一、HMR 介紹

Hot Module Replacement(以下簡稱:HMR 模塊熱替換)是 Webpack 提供的一個非常有用的功能,它允許在 JavaScript 運行時更新各種模塊,而無需完全刷新。

Hot Module Replacement (or HMR) is one of the most useful features offered by webpack. It allows all kinds of modules to be updated at runtime without the need for a full refresh. --《Hot Module Replacement》

當我們修改代碼并保存后,Webpack 將對代碼重新打包,HMR 會在應用程序運行過程中替換、添加或刪除模塊,而無需重新加載整個頁面。

HMR 主要通過以下幾種方式,來顯著加快開發(fā)速度:

  •  保留在完全重新加載頁面時丟失的應用程序狀態(tài);

  •  只更新變更內容,以節(jié)省寶貴的開發(fā)時間;

  •  調整樣式更加快速 - 幾乎相當于在瀏覽器調試器中更改樣式。

需要注意:HMR 不適用于生產環(huán)境,這意味著它應當只在開發(fā)環(huán)境使用。

二、HMR 使用方式

在 Webpack 中啟用 HMR 功能比較簡單:

1. 方式一:使用 devServer

1.1 設置 devServer 選項

只需要在 webpack.config.js 中添加 devServer 選項,并設置 hot 值為 true ,并使用HotModuleReplacementPlugin 和 NamedModulesPlugin (可選)兩個 Plugins :

  1. // webpack.config.js  

  2. const path = require('path')  

  3. const webpack = require('webpack')  

  4. module.exports = {  

  5.  entry: './index.js',  

  6.  output: {  

  7.   filename: 'bundle.js',  

  8.   path: path.join(__dirname, '/')  

  9.  },  

  10. + devServer: {  

  11. +  hot: true,   // 啟動模塊熱更新 HMR  

  12. +   open: true,  // 開啟自動打開瀏覽器頁面  

  13. + },  

  14.   plugins: [  

  15. +   new webpack.NamedModulesPlugin(),  

  16. +   new webpack.HotModuleReplacementPlugin()  

  17.   ]  


1.2 添加 scripts

然后在 package.json 中為 scripts 命令即可:

// package.json  {    // ...    "scripts": {  +    "start": "webpack-dev-server"    },    // ...  }

2. 方式二、使用命令行參數

另一種是通過添加 --hot 參數來實現。添加 --hot 參數后,devServer 會告訴 Webpack 自動引入 HotModuleReplacementPlugin ,而不需要我們手動引入。

另外常常也搭配 --open 來自動打開瀏覽器到頁面。

這里移除掉前面添加的兩個 Plugins :

// webpack.config.js  const path = require('path')  const webpack = require('webpack')  module.exports = {   // ...  - plugins: [  -   new webpack.NamedModulesPlugin(),  -   new webpack.HotModuleReplacementPlugin()  - ]  }

然后修改 package.json 文件中的 scripts 配置:

// package.json  {    // ...    "scripts": {  -    "start": "webpack-dev-server"  +    "start": "webpack-dev-server --hot --open"    },    // ...  }

3. 簡單示例

基于上述配置,我們簡單實現一個場景:index.js 文件中導入 hello.js 模塊,當 hello.js 模塊發(fā)生變化時, index.js 將更新模塊。

模塊代碼如下實現:

// hello.js  export default () => 'hi leo!';  // index.js  import hello from './hello.js'  const div = document.createElement('div');  div.innerHTML = hello(); document.body.appendChild(div);

然后在 index.html 中導入打包后的 JS 文件,并執(zhí)行 npm start 運行項目:

<!DOCTYPE html>  <html lang="en">  <head>   <meta charset="UTF-8">  </head>  <body>   <div>了不起的 Webpack HMR 學習指南</div>   <script src="bundle.js"></script>  </body>  </html>

4. 實現監(jiān)聽更新

當我們通過 HotModuleReplacementPlugin  插件啟用了 HMR,則它的接口將被暴露在全局 module.hot  屬性下面。通常,可以先檢查這個接口是否可訪問,然后再開始使用它。

舉個例子,你可以這樣 accept  一個更新的模塊:

if (module.hot) {    module.hot.accept('./library.js', function() {      // 使用更新過的 library 模塊執(zhí)行某些操作...    })  }

關于 module.hot 更多 API ,可以查看官方文檔《Hot Module Replacement API》 。

回到上面示例,我們測試更新模塊的功能。

這時我們修改 index.js 代碼,來監(jiān)聽 hello.js 模塊中的更新:

import hello from './hello.js';  const div = document.createElement('div');  div.innerHTML = hello();  document.body.appendChild(div);  + if (module.hot) {  +   module.hot.accept('./hello.js', function() {  +     console.log('現在在更新 hello 模塊了~');  +     div.innerHTML = hello();  +   })  + }

然后修改 hello.js 文件內容,測試效果:

- export default () => 'hi leo!';  + export default () => 'hi leo! hello world';

當我們保存代碼時,控制臺輸出 "現在在更新 hello模塊了~" ,并且頁面中 "hi leo!" 也更新為 "hi leo! hello world" ,證明我們監(jiān)聽到文件更新了。

怎么理解Webpack HMR

簡單 Webpack HMR 使用方式就介紹到這,更多介紹,還請閱讀官方文檔《Hot Module Replacement》。

5. devServer 常用配置和技巧

5.1 常用配置

根據目錄結構的不同,contentBase、openPage 參數要配置合適的值,否則運行時應該不會立刻訪問到你的首頁。同時要注意你的 publicPath,靜態(tài)資源打包后生成的路徑是一個需要思考的點,取決于你的目錄結構。

devServer: {    contentBase: path.join(__dirname, 'static'),    // 告訴服務器從哪里提供內容(默認當前工作目錄)    openPage: 'views/index.html',  // 指定默認啟動瀏覽器時打開的頁面    index: 'views/index.html',  // 指定首頁位置    watchContentBase: true, // contentBase下文件變動將reload頁面(默認false)    host: 'localhost', // 默認localhost,想外部可訪問用'0.0.0.0'    port: 8080, // 默認8080    inline: true, // 可以監(jiān)控js變化    hot: true, // 熱啟動    open: true, // 啟動時自動打開瀏覽器(指定打開chrome,open: 'Google Chrome')    compress: true, // 一切服務都啟用gzip 壓縮    disableHostCheck: true, // true:不進行host檢查    quiet: false,    https: false,    clientLogLevel: 'none',    stats: { // 設置控制臺的提示信息      chunks: false,      children: false,      modules: false,      entrypoints: false, // 是否輸出入口信息      warnings: false,      performance: false, // 是否輸出webpack建議(如文件體積大?。?nbsp;   },    historyApiFallback: {      disableDotRule: true,    },    watchOptions: {      ignored: /node_modules/, // 略過node_modules目錄    },    proxy: { // 接口代理(這段配置更推薦:寫到package.json,再引入到這里)      "/api-dev": {        "target": "http://api.test.xxx.com",        "secure": false,        "changeOrigin": true,        "pathRewrite": { // 將url上的某段重寫(例如此處是將 api-dev 替換成了空)          "^/api-dev": ""        }      }    },    before(app) { },  }

5.2 技巧1:文件形式輸出 dev-server 代碼

dev-server 輸出的代碼通常在內存中,但也可以寫入硬盤,產出實體文件:

devServer:{    writeToDisk: true,  }

通常可以用于代理映射文件調試,編譯時會產出許多帶 hash 的 js 文件,不帶 hash 的文件同樣也是實時編譯的。

5.3 技巧2:默認使用本地 IP 啟動服務

有的時候,啟動服務時,想要默認使用本地的 ip 地址打開:

devServer:{    disableHostCheck: true, // true:不進行host檢查    // useLocalIp: true, // 建議不在這里配置    // host: '0.0.0.0', // 建議不在這里配置  }

同時還需要將 host 配置為 0.0.0.0,這個配置建議在 scripts 命令中追加,而非在配置中寫死,否則將來不想要這種方式往回改折騰,取巧一點,配個新命令:

"dev-ip": "yarn run dev --host 0.0.0.0 --useLocalIp"

5.4 技巧3:指定啟動的調試域名

有時啟動的時候希望是指定的調試域名,例如:local.test.baidu.com:

devServer:{    open: true,    public: 'local.test.baidu.com:8080', // 需要帶上端口    port: 8080,  }

同時需要將 127.0.0.1 修改為指定的 host,可以借助 iHost 等工具去修改,各個工具大同小異,格式如下:

127.0.0.1 local.test.baidu.com

服務啟動后將自動打開 local.test.baidu.com:8080 訪問

5.5 技巧4:啟動 gzip 壓縮

devServer:{    compress: true,  }

三、HMR 基本原理介紹

從前面介紹中,我們知道:HMR 主要功能是會在應用程序運行過程中替換、添加或刪除模塊,而無需重新加載整個頁面。

那么,Webpack 編譯源碼所產生的文件變化在編譯時,替換模塊實現在運行時,兩者如何聯系起來?

帶著這兩個問題,我們先簡單看下 HMR 核心工作流程(簡化版):

怎么理解Webpack HMR

HMR 工作流程圖.png

接下來開始 HMR 工作流程分析:

  1.  當 Webpack(Watchman) 監(jiān)聽到項目中的文件/模塊代碼發(fā)生變化后,將變化通知 Webpack 中的構建工具(Packager)即 HMR Plugin;

  2.  然后經過 HMR Plugin 處理后,將結果發(fā)送到應用程序(Application)的運行時框架(HMR Runtime);

  3.  最后由 HMR Runtime 將這些發(fā)生變化的文件/模塊更新(新增/刪除或替換)到模塊系統(tǒng)中。

其中,HMR Runtime 是構建工具在編譯時注入的,通過統(tǒng)一的 Module ID 將編譯時的文件與運行時的模塊對應起來,并且對外提供一系列 API 供應用層框架(如 React)調用。

注意?:建議先理解上面這張圖的大致流程,在進行后續(xù)閱讀。放心,我等著大家~?

四、HMR 完整原理和源碼分析

通過上一節(jié)內容,我們大概知道 HMR 簡單工作流程,那么或許你現在可能還有很多疑惑:文件更新是什么通知 HMR Plugin?HMR Plugin 怎么發(fā)送更新到 HMR Runtime?等等問題。

那么接下來我們開始詳細結合源碼分析整個 HMR 模塊熱更新流程,首先還是先看流程圖,可以先不了解圖中方法名稱(紅色字體黃色背景色部分):

怎么理解Webpack HMR

Webpack HMR.png

上圖展示了從我們修改代碼,到模塊熱更新完成的一個 HMR 完整工作流程,圖中已用紅色阿拉伯數字符號將流程標識出來。

要了解上面工作原理,我們先理解圖中這幾個名稱概念:

  •  Webpack-dev-server :一個服務器插件,相當于 express 服務器,啟動一個 Web 服務,只適用于開發(fā)環(huán)境;

  •  Webpack-dev-middleware :一個 Webpack-dev-server 的中間件,作用簡單總結為:通過watch mode,監(jiān)聽資源的變更,然后自動打包。

  •  Webpack-hot-middleware :結合 Webpack-dev-middleware 使用的中間件,它可以實現瀏覽器的無刷新更新,也就是 HMR;

下面一起學習 HMR 整個工作原理吧:

1.監(jiān)控代碼變化,重新編譯打包

首先根據 devServer 配置,使用 npm start 將啟動 Webpack-dev-server 啟動本地服務器并進入 Webpack 的 watch 模式,然后初始化 Webpack-dev-middleware ,在 Webpack-dev-middleware 中通過調用 startWatch() 方法對文件系統(tǒng)進行 watch:

// webpack-dev-server\bin\webpack-dev-server.js  // 1.啟動本地服務器 Line 386  server = new Server(compiler, options);  // webpack-dev-server\lib\Server.js  // 2.初始化 Webpack-dev-middleware Line 109  this.middleware = webpackDevMiddleware(compiler, Object.assign({}, options, wdmOptions));  // webpack-dev-middleware\lib\Shared.js  // 3.開始 watch 文件系統(tǒng) Line 171  startWatch: function() {   //...   // start watching   if(!options.lazy) {    var watching = compiler.watch(options.watchOptions, share.handleCompilerCallback);    context.watching = watching;   }   //...  }  share.startWatch();  // ...

當 startWatch() 方法執(zhí)行后,便進入 watch 模式,若發(fā)現文件中代碼發(fā)生修改,則根據配置文件對模塊重新編譯打包。

2.保存編譯結果

Webpack 與  Webpack-dev-middleware 交互,Webpack-dev-middleware 調用 Webpack 的 API 對代碼變化進行監(jiān)控,并通知 Webpack 將重新編譯的代碼通過 JavaScript 對象保存在內存中。

我們會發(fā)現,在 output.path 指定的 dist 目錄并沒有保存編譯結果的文件,這是為什么?

其實, Webpack 將編譯結果保存在內存中,因為訪問內存中的代碼比訪問文件系統(tǒng)中的文件快,這樣可以減少代碼寫入文件的開銷。

Webpack 能將代碼保存到內存中,需要歸功于 Webpack-dev-middleware 的 memory-fs 依賴庫,它將原本 outputFileSystem  替換成了 MemoryFileSystem  的實例,便實現代碼輸出到內存中。其中部分源碼如下:

// webpack-dev-middleware\lib\Shared.js Line 108  // store our files in memory  var fs;  var isMemoryFs = !compiler.compilers &&       compiler.outputFileSystem instanceof MemoryFileSystem;  if(isMemoryFs) {   fs = compiler.outputFileSystem;  } else {   fs = compiler.outputFileSystem = new MemoryFileSystem();  }  context.fs = fs;

上述代碼先判斷 fileSystem 是否是 MemoryFileSystem 的實例,若不是,則用 MemoryFileSystem 的實例替換 compiler 之前的 outputFileSystem。這樣 bundle.js 文件代碼就作為一個簡單 JavaScript 對象保存在內存中,當瀏覽器請求 bundle.js 文件時,devServer 就直接去內存中找到上面保存的 JavaScript 對象并返回給瀏覽器端。

3.監(jiān)控文件變化,刷新瀏覽器

Webpack-dev-server 開始監(jiān)控文件變化,與第 1 步不同的是,這里并不是監(jiān)控代碼變化重新編譯打包。

當我們在配置文件中配置了 devServer.watchContentBase 為 true ,Webpack-dev-server 會監(jiān)聽配置文件夾中靜態(tài)文件的變化,發(fā)生變化時,通知瀏覽器端對應用進行瀏覽器刷新,這與 HMR 不一樣。

// webpack-dev-server\lib\Server.js  // 1. 讀取參數 Line 385  if (options.watchContentBase) { defaultFeatures.push('watchContentBase'); }  // 2. 定義 _watch 方法 Line 697  Server.prototype._watch = function (watchPath) {   // ...    const watcher = chokidar.watch(watchPath, options).on('change', () => {      this.sockWrite(this.sockets, 'content-changed');    });    this.contentBaseWatchers.push(watcher);  };  // 3. 執(zhí)行 _watch() 監(jiān)聽文件變化 Line 339  watchContentBase: () => {      if (/^(https?:)?\/\//.test(contentBase) || typeof contentBase === 'number') {          throw new Error('Watching remote files is not supported.');      } else if (Array.isArray(contentBase)) {          contentBase.forEach((item) => {              this._watch(item);          });      } else {          this._watch(contentBase);      }  }

4.建立 WS,同步編譯階段狀態(tài)

這一步都是 Webpack-dev-server 中處理,主要通過 sockjs(Webpack-dev-server 的依賴),在 Webpack-dev-server 的瀏覽器端(Client)和服務器端(Webpack-dev-middleware)之間建立 WebSocket 長連接。

然后將 Webpack 編譯打包的各個階段狀態(tài)信息同步到瀏覽器端。其中有兩個重要步驟:

  •  發(fā)送狀態(tài)

Webpack-dev-server 通過 Webpack API 監(jiān)聽 compile 的 done 事件,當 compile 完成后,Webpack-dev-server 通過 _sendStats 方法將編譯后新模塊的 hash 值用 socket 發(fā)送給瀏覽器端。

  •  保存狀態(tài)

瀏覽器端將_sendStats 發(fā)送過來的 hash 保存下來,它將會用到后模塊熱更新。

怎么理解Webpack HMR

// webpack-dev-server\lib\Server.js  // 1. 定義 _sendStats 方法 Line 685  // send stats to a socket or multiple sockets  Server.prototype._sendStats = function (sockets, stats, force) {    //...    this.sockWrite(sockets, 'hash', stats.hash);  };  // 2. 監(jiān)聽 done 事件 Line 86  compiler.plugin('done', (stats) => {     // 將最新打包文件的 hash 值(stats.hash)作為參數傳入 _sendStats()      this._sendStats(this.sockets, stats.toJson(clientStats));      this._stats = stats;  });  // webpack-dev-server\client\index.js  // 3. 保存 hash 值 Line 74  var onSocketMsg = {    // ...    hash: function hash(_hash) {      currentHash = _hash;    },    // ...  }  socket(socketUrl, onSocketMsg);

5.瀏覽器端發(fā)布消息

當 hash 消息發(fā)送完成后,socket 還會發(fā)送一條 ok 的消息告知 Webpack-dev-server,由于客戶端(Client)并不請求熱更新代碼,也不執(zhí)行熱更新模塊操作,因此通過 emit 一個 "webpackHotUpdate" 消息,將工作轉交回 Webpack。

// webpack-dev-server\client\index.js  // 1. 處理 ok 消息 Line 135  var onSocketMsg = {    // ...    ok: function ok() {        sendMsg('Ok');        if (useWarningOverlay || useErrorOverlay) overlay.clear();        if (initial) return initial = false; // eslint-disable-line no-return-assign        reloadApp();    },    // ...  }  // 2. 處理刷新 APP Line 218  function reloadApp() {    // ...    if (_hot) {      // 動態(tài)加載 emitter      var hotEmitter = require('webpack/hot/emitter');      hotEmitter.emit('webpackHotUpdate', currentHash);      if (typeof self !== 'undefined' && self.window) {        // broadcast update to window        self.postMessage('webpackHotUpdate' + currentHash, '*');      }    }    // ...  }

6.傳遞 hash 到 HMR

Webpack/hot/dev-server 監(jiān)聽瀏覽器端 webpackHotUpdate 消息,將新模塊 hash 值傳到客戶端 HMR 核心中樞的 HotModuleReplacement.runtime ,并調用 check 方法檢測更新,判斷是瀏覽器刷新還是模塊熱更新。如果是瀏覽器刷新的話,則沒有后續(xù)步驟咯~~

// webpack\hot\dev-server.js  // 1.監(jiān)聽 webpackHotUpdate Line 42  var hotEmitter = require("./emitter");  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() {      module.hot.check(true).then(function(updatedModules) {          if(!updatedModules) {              // ...        window.location.reload();// 瀏覽器刷新              return;          }          if(!upToDate()) {              check();          }      }).catch(function(err) { /*...*/});  };  // webpack\lib\HotModuleReplacement.runtime.js  // 3.調用 HotModuleReplacement.runtime 定義的 check 方法 Line 167  function hotCheck(apply) {      if(hotStatus !== "idle") throw new Error("check() is only allowed in idle status");      hotApplyOnUpdate = apply;      hotSetStatus("check");      return hotDownloadManifest(hotRequestTimeout).then(function(update) {      //...      });  }

7.檢測是否存在更新

當 HotModuleReplacement.runtime 調用 check 方法時,會調用 JsonpMainTemplate.runtime 中的 hotDownloadUpdateChunk (獲取最新模塊代碼)和 hotDownloadManifest (獲取是否有更新文件)兩個方法,這兩個方法的源碼,在下一步展開。

// webpack\lib\HotModuleReplacement.runtime.js  // 1.調用 HotModuleReplacement.runtime 定義 hotDownloadUpdateChunk 方法 Line 171  function hotCheck(apply) {      if(hotStatus !== "idle") throw new Error("check() is only allowed in idle status");      hotApplyOnUpdate = apply;      hotSetStatus("check");      return hotDownloadManifest(hotRequestTimeout).then(function(update) {      //...          {            // hotEnsureUpdateChunk 方法中會調用 hotDownloadUpdateChunk            hotEnsureUpdateChunk(chunkId);          }      });  }

其中 hotEnsureUpdateChunk 方法中會調用 hotDownloadUpdateChunk :

// webpack\lib\HotModuleReplacement.runtime.js Line 215   function hotEnsureUpdateChunk(chunkId) {    if(!hotAvailableFilesMap[chunkId]) {     hotWaitingFilesMap[chunkId] = true;    } else {     hotRequestedFilesMap[chunkId] = true;     hotWaitingFiles++;     hotDownloadUpdateChunk(chunkId);    }   }

8.請求更新最新文件列表

在調用 check 方法時,會先調用 JsonpMainTemplate.runtime 中的 hotDownloadManifest 方法, 通過向服務端發(fā)起 AJAX 請求獲取是否有更新文件,如果有的話將 mainfest 返回給瀏覽器端。

怎么理解Webpack HMR

這邊涉及一些原生 XMLHttpRequest,就不全部貼出了~

// webpack\lib\JsonpMainTemplate.runtime.js  // hotDownloadManifest 定義 Line 22  function hotDownloadManifest(requestTimeout) {      return new Promise(function(resolve, reject) {          try {              var request = new XMLHttpRequest();              var requestPath = $require$.p + $hotMainFilename$;              request.open("GET", requestPath, true);              request.timeout = requestTimeout;              request.send(null);          } catch(err) {              return reject(err);          }          request.onreadystatechange = function() {              // ...          };      });  }

9.請求更新最新模塊代碼

在  hotDownloadManifest 方法中,還會執(zhí)行  hotDownloadUpdateChunk 方法,通過 JSONP 請求最新的模塊代碼,并將代碼返回給 HMR runtime 。

怎么理解Webpack HMR

然后 HMR runtime 會將新代碼進一步處理,判斷是瀏覽器刷新還是模塊熱更新。

// webpack\lib\JsonpMainTemplate.runtime.js  // hotDownloadManifest 定義 Line 12  function hotDownloadUpdateChunk(chunkId) {    // 創(chuàng)建 script 標簽,發(fā)起 JSONP 請求      var head = document.getElementsByTagName("head")[0];      var script = document.createElement("script");      script.type = "text/javascript";      script.charset = "utf-8";      script.src = $require$.p + $hotChunkFilename$;      $crossOriginLoading$;      head.appendChild(script);  }

10.更新模塊和依賴引用

這一步是整個模塊熱更新(HMR)的核心步驟,通過 HMR runtime 的 hotApply 方法,移除過期模塊和代碼,并添加新的模塊和代碼實現熱更新。

從 hotApply 方法可以看出,模塊熱替換主要分三個階段:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術社區(qū)

  2.  找出過期模塊 outdatedModules 和過期依賴 outdatedDependencies ; 

// webpack\lib\HotModuleReplacement.runtime.js  // 找出 outdatedModules 和 outdatedDependencies Line 342  function hotApply() {     // ...    var outdatedDependencies = {};    var outdatedModules = [];    function getAffectedStuff(updateModuleId) {      var outdatedModules = [updateModuleId];      var outdatedDependencies = {};      // ...      return {          type: "accepted",          moduleId: updateModuleId,          outdatedModules: outdatedModules,          outdatedDependencies: outdatedDependencies      };   };    function addAllToSet(a, b) {        for (var i = 0; i < b.length; i++) {            var item = b[i];            if (a.indexOf(item) < 0)                a.push(item);        }    }    for(var id in hotUpdate) {        if(Object.prototype.hasOwnProperty.call(hotUpdate, id)) {            // ... 省略多余代碼            if(hotUpdate[id]) {                result = getAffectedStuff(moduleId);            }            if(doApply) {                for(moduleId in result.outdatedDependencies) {                   // 添加到 outdatedDependencies                    addAllToSet(outdatedDependencies[moduleId], result.outdatedDependencies[moduleId]);                }            }            if(doDispose) {                // 添加到 outdatedModules                addAllToSet(outdatedModules, [result.moduleId]);                appliedUpdate[moduleId] = warnUnexpectedRequire;            }        }    }  }

   2.  從緩存中刪除過期模塊、依賴和所有子元素的引用;

// webpack\lib\HotModuleReplacement.runtime.js  // 從緩存中刪除過期模塊、依賴和所有子元素的引用 Line 442  function hotApply() {     // ...      var idx;      var queue = outdatedModules.slice();      while(queue.length > 0) {          moduleId = queue.pop();          module = installedModules[moduleId];          // ...          // 移除緩存中的模塊          delete installedModules[moduleId];          // 移除過期依賴中不需要使用的處理方法          delete outdatedDependencies[moduleId];          // 移除所有子元素的引用          for(j = 0; j < module.children.length; j++) {              var child = installedModules[module.children[j]];              if(!child) continue;              idx = child.parents.indexOf(moduleId);              if(idx >= 0) {                  child.parents.splice(idx, 1);              }          }      }    // 從模塊子組件中刪除過時的依賴項    var dependency;    var moduleOutdatedDependencies;    for(moduleId in outdatedDependencies) {     if(Object.prototype.hasOwnProperty.call(outdatedDependencies, moduleId)) {      module = installedModules[moduleId];      if(module) {       moduleOutdatedDependencies = outdatedDependencies[moduleId];       for(j = 0; j < moduleOutdatedDependencies.length; j++) {        dependency = moduleOutdatedDependencies[j];        idx = module.children.indexOf(dependency);        if(idx >= 0) module.children.splice(idx, 1);       }      }    }    }  }

   3.  將新模塊代碼添加到 modules 中,當下次調用 __webpack_require__  (webpack 重寫的 require  方法)方法的時候,就是獲取到了新的模塊代碼了。

// webpack\lib\HotModuleReplacement.runtime.js  // 將新模塊代碼添加到 modules 中 Line 501  function hotApply() {     // ...      for(moduleId in appliedUpdate) {          if(Object.prototype.hasOwnProperty.call(appliedUpdate, moduleId)) {              modules[moduleId] = appliedUpdate[moduleId];          }      }  }

hotApply 方法執(zhí)行之后,新代碼已經替換舊代碼,但是我們業(yè)務代碼并不知道這些變化,因此需要通過 accept事件通知應用層使用新的模塊進行“局部刷新”,我們在業(yè)務中是這么使用:

if (module.hot) {    module.hot.accept('./library.js', function() {      // 使用更新過的 library 模塊執(zhí)行某些操作...    })  }

11.熱更新錯誤處理

在熱更新過程中,hotApply 過程中可能出現 abort 或者 fail 錯誤,則熱更新退回到刷新瀏覽器(Browser Reload),整個模塊熱更新完成。

// webpack\hot\dev-server.js Line 13  module.hot.check(true).then(function (updatedModules) {      if (!updatedModules) {          return window.location.reload();      }      // ...  }).catch(function (err) {      var status = module.hot.status();      if (["abort", "fail"].indexOf(status) >= 0) {          window.location.reload();      }  });

到此,相信大家對“怎么理解Webpack HMR”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續(xù)學習!

向AI問一下細節(jié)

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

AI