成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

聊聊Webpack熱更新以及原理

開發(fā) 前端
本文介紹了 webpack 熱更新的簡單使用、相關的流程以及原理。

 [[394013]]

什么是熱更新

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

一般的刷新我們分兩種:

  •  一種是頁面刷新,不保留頁面狀態(tài),就是簡單粗暴,直接 window.location.reload()。
  •  另一種是基于 WDS (Webpack-dev-server) 的模塊熱替換,只需要局部刷新頁面上發(fā)生變化的模塊,同時可以保留當前的頁面狀態(tài),比如復選框的選中狀態(tài)、輸入框的輸入等。

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

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

具體我們?nèi)绾卧?webpack 中使用這個功能呢?

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

如何使用熱更新

  1. npm install webpack webpack-dev-server --save-dev 

設置 HotModuleReplacementPlugin,HotModuleReplacementPlugin 是 webpack 是自帶的 

  1. plugins: {  
  2.     HotModuleReplacementPlugin: new webpack.HotModuleReplacementPlugin()  

再設置一下 devServer 

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

兩個重要的文件

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

更改前: 

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

更改后: 

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

瀏覽器會去請求兩個文件

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

  •  JSON 文件,h 代表本次新生成的 Hash 值為 0c256052432b51ed32c8——本次輸出的 Hash 值會被作為下次熱更新的標識。c 表示當前要熱更新的文件對應的是哪個模塊,可以讓 webpack 知道它要更新哪個模塊 
  1.  
  2.     "h": "0c256052432b51ed32c8",  
  3.     "c": {  
  4.         "201": true  
  5.     }  
  •  js 文件,就是本次修改的代碼,重新編譯打包后的,大致是下面這個樣子(已刪減一些并格式化過,這里看不懂沒關系的,就記住是返回要更新的模塊就好了),webpackHotUpdate 方法就是用來更新模塊的,201 對應的是哪個模塊(我們稱它為模塊標識),其他的就是要更新的模塊的內(nèi)容了 
  1. webpackHotUpdate(201, {  
  2.   "./src/views/moveTransfer/list/index.vue?vue&type=script&lang=js&": function (  
  3.     module,  
  4.     exports,  
  5.     __webpack_require__ 
  6.   ) {  
  7.     "use strict";  
  8.     var _Object$defineProperty = __webpack_require__ 
  9.       /*! @babel/runtime-corejs3/core-js-stable/object/define-property */ "./node_modules/@babel/runtime-corejs3/core-js-stable/object/define-property.js"  
  10.     );  
  11.     _Object$defineProperty(exports, "__esModule", {  
  12.       value: true,  
  13.     });  
  14.     exports.default = void 0;  
  15.     var _default = {  
  16.       data: function data() {  
  17.         return {};  
  18.       },  
  19.       computed: {},  
  20.       methods: {  
  21.         clickMe: function clickMe() {  
  22.           console.log("我是 Gopal,歡迎關注「前端雜貨鋪」,一起學習成長吧");  
  23.         },  
  24.       },  
  25.     };  
  26.     exports.default = _default 
  27.   },  
  28. }); 

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

了解一下 Websocket

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

  1. // 執(zhí)行上面語句之后,客戶端就會與服務器進行連接。  
  2. var ws = new WebSocket("wss://echo.websocket.org");  
  3. // 實例對象的 onopen 屬性,用于指定連接成功后的回調(diào)函數(shù)  
  4. ws.onopen = function(evt) {   
  5.   console.log("Connection open ...");   
  6.   ws.send("Hello WebSockets!");  
  7. };   
  8. // 實例對象的 onmessage 屬性,用于指定收到服務器數(shù)據(jù)后的回調(diào)函數(shù)。可以接受二進制數(shù)據(jù),blob 對象或者 Arraybuffer 對象  
  9. ws.onmessage = function(evt) {  
  10.   console.log( "Received Message: " + evt.data);  
  11.   ws.close();  
  12. };  
  13. // 實例對象的 onclose 屬性,用于指定連接關閉后的回調(diào)函數(shù)。  
  14. ws.onclose = function(evt) {  
  15.   console.log("Connection closed.");  
  16. };       

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

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

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

熱更新原理

熱更新的過程

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

  •  Webpack-complier :webpack 的編譯器,將 JavaScript 編譯成 bundle(就是最終的輸出文件)
  •  HMR Server:將熱更新的文件輸出給 HMR Runtime
  •  Bunble Server:提供文件在瀏覽器的訪問,也就是我們平時能夠正常通過 localhost 訪問我們本地網(wǎng)站的原因
  •  HMR Runtime:開啟了熱更新的話,在打包階段會被注入到瀏覽器中的 bundle.js,這樣 bundle.js 就可以跟服務器建立連接,通常是使用 websocket ,當收到服務器的更新指令的時候,就   去更新文件的變化
  •  bundle.js:構(gòu)建輸出的文件

啟動階段

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

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

文件熱更新階段

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

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

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

深入——源碼閱讀

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

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

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

所以我們聚焦以下幾點:

  •  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 

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

啟動服務結(jié)束之后就通過 createSocketServer 創(chuàng)建 websocket 服務 

  1. listen(port, hostname, fn) {  
  2.   this.hostname = hostname;  
  3.   return (  
  4.     findPort(port || this.options.port) 
  5.       .then((port) => {  
  6.         this.port = port;  
  7.         return this.server.listen(port, hostname, (err) => {  
  8.           if (this.options.hot || this.options.liveReload) {  
  9.             // 啟動 express 服務之后,啟動 websocket 服務  
  10.             this.createSocketServer();  
  11.           }  
  12.         });  
  13.       })  
  14.   );  
  15.  
  1. createSocketServer() {  
  2.   this.socketServer = new this.SocketServerImplementation(this);  
  3.   this.socketServer.onConnection((connection, headers) => {   
  4.   });  

HMR Server 和 HMR Runtime 的通信

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

看 lib/Server.js 中的 setupHooks 方法 

  1. // lib/Server.js  
  2. // 綁定監(jiān)聽事件  
  3. setupHooks() {  
  4.   // ...  
  5.   const addHooks = (compiler) => {  
  6.     // 監(jiān)聽 webpack 的 done 鉤子,tapable 提供的監(jiān)聽方法  
  7.     // done 標識編譯結(jié)束  
  8.     const { compile, invalid, done } = compiler.hooks;  
  9.     compile.tap('webpack-dev-server', invalidPlugin);  
  10.     invalid.tap('webpack-dev-server', invalidPlugin); 
  11.     done.tap('webpack-dev-server', (stats) => {  
  12.       // 當監(jiān)聽到一次webpack編譯結(jié)束,就會調(diào)用 sendStats 方法  
  13.       this.sendStats(this.sockets, this.getStats(stats));  
  14.       this.stats = stats;  
  15.     });  
  16.   };  

當監(jiān)聽到一次 webpack 編譯結(jié)束,就會調(diào)用 sendStats 方法,里面會向客戶端發(fā)送 hash 和 ok 事件 

  1. // lib/Server.js  
  2. // send stats to a socket or multiple sockets  
  3. sendStats(sockets, stats, force) {  
  4.   // ok和 hash  
  5.   this.sockWrite(sockets, 'hash', stats.hash); 
  6.   if (stats.errors.length > 0) {  
  7.     this.sockWrite(sockets, 'errors', stats.errors);  
  8.   } else if (stats.warnings.length > 0) {  
  9.     this.sockWrite(sockets, 'warnings', stats.warnings);  
  10.   } else {  
  11.     this.sockWrite(sockets, 'ok');  
  12.   } 

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

  1. // client-src/default/index.js   
  2. const onSocketMessage = {  
  3.   // 更新 current Hash  
  4.   hash(hash) { 
  5.      status.currentHash = hash 
  6.   },  
  7.   'progress-update': function progressUpdate(data) {  
  8.     if (options.useProgress) {  
  9.       log.info(`${data.percent}% - ${data.msg}.`);  
  10.     }  
  11.     sendMessage('Progress', data);  
  12.   },  
  13.   ok() {  
  14.     sendMessage('Ok');  
  15.     if (options.useWarningOverlay || options.useErrorOverlay) {  
  16.       overlay.clear();  
  17.     }  
  18.     if (options.initial) {  
  19.       return (options.initial = false);  
  20.     }  
  21.     // 進行更新檢查等操作  
  22.     reloadApp(options, status);  
  23.   }  
  24. }; 

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

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

在 webpack 的 hot/dev-server.js 中,監(jiān)聽 webpackHotUpdate 事件,并執(zhí)行 check 方法。并在 check 方法中調(diào)用 module.hot.check 方法進行熱更新。 

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

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

HMR Runtime 中更新 bundle.js

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

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

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

實際上這個函數(shù)就是用來返回一個 hot 對象,所以調(diào)用 module.hot.check 的時候,實際上就是執(zhí)行 hotCheck 函數(shù) 

  1. function createModuleHotObject(moduleId, me) {  
  2.   var hot = {  
  3.     // Module API  
  4.     addDisposeHandler: function (callback) {  
  5.       hot._disposeHandlers.push(callback);  
  6.     },  
  7.     removeDisposeHandler: function (callback) {  
  8.       var idx = hot._disposeHandlers.indexOf(callback);  
  9.       if (idx >= 0) hot._disposeHandlers.splice(idx, 1);  
  10.     },  
  11.     // Management API  
  12.     check: hotCheck,  
  13.     apply: hotApply,  
  14.     status: function (l) {  
  15.       if (!l) return currentStatus; 
  16.       registeredStatusHandlers.push(l);  
  17.     },  
  18.     addStatusHandler: function (l) {  
  19.       registeredStatusHandlers.push(l); 
  20.      },  
  21.     removeStatusHandler: function (l) {  
  22.       var idx = registeredStatusHandlers.indexOf(l);  
  23.       if (idx >= 0) registeredStatusHandlers.splice(idx, 1);  
  24.     }, 
  25.   };  
  26.   currentChildModule = undefined 
  27.   return hot;  

其中就有 hotCheck 中調(diào)用了 __webpack_require__.hmrM 

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

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

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

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

加載要更新的模塊

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

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

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

  1. __webpack_require__.l = (url, done, key, chunkId) => {  
  2.   // ...  
  3.   if (!script) { 
  4.      script = document.createElement("script");  
  5.     script.charset = "utf-8" 
  6.     script.timeout = 120 
  7.     if (__webpack_require__.nc) {  
  8.       script.setAttribute("nonce", __webpack_require__.nc);  
  9.     }  
  10.     script.setAttribute("data-webpack", dataWebpackPrefix + key);  
  11.     script.src = url
  12.    }  
  13.   // ...  
  14.   needAttach && document.head.appendChild(script); 
  15. }; 

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

  1. // webpackHotUpdate + 項目名  
  2. self["webpackHotUpdatelearn_hot_reload"] = (chunkId, moreModules, runtime) => {  
  3.   for(var moduleId in moreModules) {  
  4.     if(__webpack_require__.o(moreModules, moduleId)) {  
  5.       currentUpdate[moduleId] = moreModules[moduleId];  
  6.       if(currentUpdatedModulesList) currentUpdatedModulesList.push(moduleId);  
  7.     }  
  8.   }  
  9.   if(runtime) currentUpdateRuntime.push(runtime);  
  10.   if(waitingUpdateResolves[chunkId]) {  
  11.     waitingUpdateResolves[chunkId]();  
  12.     waitingUpdateResolves[chunkId] = undefined;  
  13.   }  
  14. }; 

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

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

總結(jié)

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

  •  HMR Runtime 通過 HotModuleReplacementPlugin 已經(jīng)注入到我們 chunk 中了
  •  除了開啟一個 Bundle Server,還開啟了 HMR Server,主要用來和 HMR Runtime 中通信
  •  在編譯結(jié)束的時候,通過 compiler.hooks.done,監(jiān)聽并通知客戶端
  •  客戶端接收到之后,就會調(diào)用 module.hot.check 等,發(fā)起 http 請求去服務器端獲取新的模塊資源解析并局部刷新頁面

 

 

責任編輯:龐桂玉 來源: 前端大全
相關推薦

2021-05-06 14:34:12

Webpack熱更新程序

2020-08-05 08:21:41

Webpack

2023-07-31 09:59:17

JavaJVMAgent

2021-12-22 22:44:49

Webpack熱替換模塊

2011-03-29 15:15:06

熱備份熱修復

2022-07-04 08:54:39

Swift處理器項目

2024-04-26 08:41:04

ViteHMR項目

2023-06-30 07:51:44

springboot初始化邏輯

2021-12-20 00:03:38

Webpack運行機制

2021-09-13 09:40:35

Webpack 前端HMR 原理

2024-12-23 15:05:29

2024-08-05 11:14:45

2021-05-31 05:36:43

WebpackJavaScript 前端

2022-06-21 07:51:06

Redis高可用哨兵進程

2024-05-09 09:55:08

2021-08-26 10:30:29

WebpackTree-Shakin前端

2021-12-15 23:42:56

Webpack原理實踐

2024-04-18 15:22:54

2021-08-03 08:35:36

Vuex數(shù)據(jù)熱更新

2022-08-26 13:24:03

version源碼sources
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲一区二区av在线 | 欧美一区视频在线 | 北条麻妃一区二区三区在线视频 | 亚州精品天堂中文字幕 | 国产精品亚洲一区二区三区在线观看 | 久国产视频 | 日韩欧美中文在线 | 狠狠av| 精品国产不卡一区二区三区 | 亚洲欧美在线一区 | 国产乱码精品一区二区三区忘忧草 | 久热国产在线 | 91小视频在线 | 精品综合 | 西西裸体做爰视频 | 精品国产黄色片 | 91欧美精品 | 99精品视频免费在线观看 | 亚洲精品成人在线 | 亚洲成人一区二区在线 | 欧美一级做性受免费大片免费 | 亚洲色欲色欲www | 久久精品这里精品 | 午夜爽爽男女免费观看hd | 国产欧美一区二区三区免费 | 日韩成人在线播放 | 国产精品夜色一区二区三区 | 国产亚洲精品精品国产亚洲综合 | 亚洲一区二区三区在线免费观看 | 亚洲精品色 | 最新免费av网站 | 色播99| 欧美精品一区二区三区四区 在线 | 欧州一区二区三区 | 国产乱人伦精品一区二区 | 一区二区三区小视频 | 视频在线亚洲 | 日本免费一区二区三区视频 | 99re6在线视频精品免费 | 日韩av免费看 | 久久国产欧美一区二区三区精品 |