看完這篇,再也不怕被問 Webpack 熱更新

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Webpack熱更新( Hot Module Replacement,簡稱 HMR,後續均以 HMR 替代),無需完全刷新整個頁面的同時,更新所有類型的模塊,是 Webpack 提供的最有用的功能之一。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HMR 作爲一個 Webpack 內置的功能,可以通過 --hot 或者 HotModuleReplacementPlugin 開啓。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"刷新分爲兩種:一種是頁面刷新,不保留頁面狀態,就是簡單粗暴,直接window.location.reload();另一種是基於 WDS(Webpack-dev-server)的模塊熱替換,只需要局部刷新頁面上發生變化的模塊,同時可以保留當前的頁面狀態,比如複選框的選中狀態、輸入框的輸入等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HMR 的好處,在日常開發工作中體會頗深:節省寶貴的開發時間、提升開發體驗。引用官網的描述來概述一下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"模塊熱替換(HMR - hot module replacement)功能會在應用程序運行過程中,替換、添加或刪除 模塊,而無需重新加載整個頁面。主要是通過以下幾種方式,來顯著加快開發速度:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"保留在完全重新加載頁面期間丟失的應用程序狀態。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"只更新變更內容,以節省寶貴的開發時間。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在源代碼中對 CSS \/ JS 進行修改,會立刻在瀏覽器中進行更新,這幾乎相當於在瀏覽器 devtools 直接更改樣式。"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在開發環境,可以將 HMR 作爲 LiveReload 的替代。那麼,HMR到底是怎麼實現熱更新的呢?下面通過觀察編譯構建過程,以及分析源代碼來了解一下。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本次探索依賴公司前端 Vue 項目開發腳手架配置:Webpack + Webpack-Dev-Middleware + Webpack-Hot-Middleware + Express。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"webpack 構建"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"項目啓動之後,會進行首次構建打包,控制檯中會輸出整個的構建過程,可以觀察到一個 Hash 值 3606e1ab1ddcf6626797。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/e2\/e22f1549fd7f53c4c2aefcee4e6f5e63.webp","alt":"Image","title":"image-20190925192508536","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在每次代碼的修改後,保存時都會在控制檯上出現 compiling…字樣,可以在控制檯中觀察到:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Hash 值更新:4f8c0eff7ac051c13277;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"新生成文件:3606e1ab1ddcf6626797.hot-update.json"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"main1.3606e1ab1ddcf6626797.hot-update.js"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/77\/7776906a316657cf225ad60382c59d70.webp","alt":"Image","title":"image-20190925192526730","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果沒有任何改動,直接保存,控制檯輸出編譯打包信息,Hash 值沒有發生變化,仍舊是 4f8c0eff7ac051c13277。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/eb\/eb859107422c974fbb62bae22396dfab.webp","alt":"Image","title":"image-20190925192738074","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"再次修改代碼保存,控制檯輸出編譯打包信息。根據文件名可以發現,上次輸出的 Hash 值被作爲本次編譯新生成的 Hmr 文件標識。同樣,本次輸出的 Hash 值會被作爲下次熱更新的標識。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/e8\/e8008d320f7ac743278bab2c49e3d6c8.jpeg","alt":"Image","title":"image-20190925192752213","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Webpack Watch"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲什麼代碼的改動保存會自動編譯,重新打包?這一系列的重新檢測編譯依賴於 Webpack 的文件監聽:在項目啓動之後,Webpack 會通過 Compiler 類的 Run 方法開啓編譯構建過程,編譯完成後,調用 Watch 方法監聽文件變更,當文件發生變化,重新編譯,編譯完成之後繼續監聽。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/df\/df9d33dc11dec10b01d8b4bbd1361edf.webp","alt":"Image","title":"image-20190925003133799","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"頁面的訪問需要依賴 Web 服務器,那要如何將 Webpack 編譯打包之後的文件傳遞給 Web 服務器呢?這就要看 Webpack-dev-middleware了。Webpack-dev-middleware 是一個封裝器( wrapper ),它可以把 Webpack 處理過的文件發送到一個 Server(其中 Webpack-Dev-Server 就是內置了 Webpack-dev-middleware 和 Express 服務器)。上面有說到編譯之後的文件會被寫入到內存,而 Webpack-dev-middleware 插件通過 "},{"type":"codeinline","content":[{"type":"text","text":"memory-fs"}]},{"type":"text","text":" 實現靜態資源請求直接訪問內存文件。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"   const webpack = require('webpack');\n   const webpackConfig = require('.\/webpack.dev.conf');\n   const compiler = webpack(webpackConfig);\n   debug('webpack編譯完成');\n   debug('將編譯流通過webpack-dev-middleware');\n   const devMiddleware = require('webpack-dev-middleware')(compiler, {\n   \/\/ self-define options\n   });\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面代碼可以看到,Webpack 編譯打包之後得到一個 Compilation ,並將 Compilation 傳遞到 Webpack-dev-middleware 插件中,Webpack-dev-middleware 可以通過 Compilation 調用 Webpack中 的 Watch 方法實時監控文件變化,並重新編譯打包寫入內存。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"留意一下瀏覽器端,在 Network 中可以看到幾個請求:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"\/__Webpack_hmr 請求返回的消息包含了首次 Hash 值,每次代碼變動重新編譯後,瀏覽器會發出 hash.hot-update.json、fileChunk.hash.hot-update.js 資源請求。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/e5\/e5d9457a3a742bfaeee3708a89fee178.webp","alt":"Image","title":"image-20190924112630734","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"點開查看 hash.hot-update.json 請求,返回的結果中,h 是一個 hash 值,用於下次文件熱更新請求的前綴,c 表示當前要熱更新的文件是 main1 。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/0f\/0f582a6de483800a5f56997d202a6810.webp","alt":"Image","title":"image-20190924112716610","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"繼續查看 fileChunk.hash.hot-update.js,返回的內容是使用 webpackHotUpdate 標識的 fileChunk 內容。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/45\/45406504698a0d6612a0059872695b87.webp","alt":"Image","title":"image-20190924113157232","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼 Webpack 服務器和瀏覽器端是怎麼建立起通信的呢?這就是 Webpack-hot-middleware 插件的功勞了。Webpack-hot-middleware 的 README.md 文檔中有這樣一段描述:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"This module is only concerned with the mechanisms to connect a browser client to a Webpack server & receive updates."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Webpack-hot-middleware 插件的作用就是提供瀏覽器和 Webpack 服務器之間的通信機制、且在瀏覽器端接收 Webpack 服務器端的更新變化。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲了更好的理解這一段話,打開瀏覽器開發者調試工具,可以看到在 Webpack 打包好的 Js 中主要包含了以下幾部分。下面截取關鍵部分進行說明:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Webpack-hot-middleware\/client.js"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"源碼中有這麼一段配置,看到這裏瞬間想到了在開發時瀏覽器的 Network 中總是有一個 __Webpack_hmr 的請求,點開查看會看到EventStream 事件流(服務器端事件流,服務器向瀏覽器推送消息,除了 websocket 全雙工通道雙向通信方式還有一種 Server-Sent Events 單向通道的通信方法,只能服務器端向瀏覽器端通過流信息的方式推送消息;頁面可以通過 EventSource 實例接收服務器發送事件通知並觸發 onmessage 事件),並且以 2s 的頻率不停的更新消息內容,每行消息內容都有 ❤️ 的圖標,沒錯這就是一個心跳請求。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" var options = {\n   path: '\/__Webpack_hmr',\n   timeout: 20 * 1000,\n   overlay: true,\n   reload: false,\n   log: true,\n   warn: true,\n   name: '',\n   autoConnect: true,\n   overlayStyles: {},\n   overlayWarnings: false,\n   ansiColors: {},\n };\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"繼續向下查看 Client.js 代碼,發現這完全就是一個只要瀏覽器支持就可以自發建立通信通道的客戶端。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" if (typeof window === 'undefined') {\n   \/\/ do nothing\n } else if (typeof window.EventSource === 'undefined') {\n   \/\/ warning\n } else {\n       if (options.autoConnect) {\n       \/\/ 建立通信連接\n         connect();\n       }\n }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在建立通信的過程中,瀏覽器端會初始化一個 EventSource 實例並通過 onmessage 事件監聽消息。瀏覽器端在收到服務器發來的數據時,就會觸發 onmessage 事件,可以通過定義 onmessage 的回調函數處理接收到的消息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"\/\/ 瀏覽器端建立連接\n function EventSourceWrapper() {\n var source;\n var listeners = [];\n \/\/ 初始化EventSource實例\n source = new window.EventSource(options.path);\n \/\/ 定義onmessage事件監聽服務器端消息返回\n source.onmessage = handleMessage;\n function handleMessage(event) {\n for (var i = 0; i < listeners.length; i++) {\n listeners[i](event);\n }\n }\n return {\n addMessageListener: function(fn) {\n listeners.push(fn);\n }\n };\n }\n \/\/ 瀏覽器端建立通信通道,監聽處理服務器端推送的消息\n function connect() {\n EventSourceWrapper().addMessageListener(handleMessage);\n function handleMessage(event) {\n try {\n processMessage(JSON.parse(event.data));\n } catch (ex) {\n \/\/ handler exception\n }\n }\n }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Client.js 監聽的消息有:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"building\/built:構建中,不會觸發熱更新"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"sync:開始更新的流程"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 processUpdate 方法中,處理一切異常\/錯誤的方法都是直接更新整個頁面即調用 window.location.reload(),首先調用 module.hot.check 方法檢測是否有更新,然後進入 HotModuleReplacement.runtime 的 Check 階段。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" function processMessage(obj) {\n   switch (obj.action) {\n     case 'building':\n       \/\/ tell you rebuilding\n       break;\n     case 'built':\n       \/\/ tell you rebuilt in n ms\n     \/\/ fall through\n     case 'sync':\n        \/\/ 省略...\n       var applyUpdate = true;\n       if (applyUpdate) {\n         processUpdate(obj.hash, obj.modules, options);\n       }\n       break;\n     default:\n       \/\/ do something\n   }\n }\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"熱更新過程"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"改動頁面代碼保存之後,Webpack 會重新編譯文件併發消息通知瀏覽器,瀏覽器在 Check 之後觸發 WebpackHotUpdateCallback,具體 HotModuleReplacement.runtime.js 會做以下幾個操作:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"進入 HotCheck,調用 hotDownloadManifest 發送 \/hash.hot-update.json 請求;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過 Json 請求結果獲取熱更新文件,以及下次熱更新的 Hash 標識,並進入熱更新準備階段;"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"hotAvailableFilesMap = update.c;\/\/ 需要更新的文件\nhotUpdateNewHash = update.h;\/\/ 下次熱更新hash值\nhotSetStatus(\"prepare\");\/\/ 進入熱更新準備狀態"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HotCheck 確認需要熱更新之後進入 hotAddUpdateChunk 方法,該方法先檢查 Hash 標識的模塊是否已更新,如果沒更新,則通過在 DOM 中添加 Script 標籤的方式,動態請求js:\/fileChunk.hash.hot-update.js,獲取最新打包的 js 內容;"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最新打包的js內容如何更新的呢?HotModuleReplacement.runtime.js 在 window 對象上定義了 WebpackHotUpdate方法;在這裏定義瞭如何解析前面 fileChunk.hash.hot-update.js 請求返回的js內容 webpackHotUpdate(main1, { moreModules }),直接遍歷 moreModules,並且執行 hotUpdate 方法更新;"}]}]}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/af\/af225a74cf2d6cdd1360c1419a287021.webp","alt":"Image","title":"image-20190925145730597","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/bc\/bc0cec08933e9fa8936f302357a63c5d.webp","alt":"Image","title":"image-20190925150635231","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"結語"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此頁面已經完成熱更新,Webpack 如何實現熱更新的呢?首先是建立起瀏覽器端和服務器端之間的通信,瀏覽器會接收服務器端推送的消息,如果需要熱更新,瀏覽器發起http請求去服務器端獲取打包好的資源解析並局部刷新頁面。"}]},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"頭圖:Unsplash"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作者:米亞"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文:https:\/\/mp.weixin.qq.com\/s\/Vh7jyg9yWHabw_a2pjm8rQ"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文:看完這篇,面試再也不怕被問 Webpack 熱更新"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"來源:政採雲前端團隊 - 微信公衆號 [ID:Zoo-Team]"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"轉載:著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章