大白話講webpack熱更新

webpack熱更新,也稱熱替換,英文縮寫HMR(Hot Module Replacement)。ok,介紹完定義,那么HMR的作用是什么呢?答案是讓web項目可以在開發(fā)環(huán)境下,每次更改文件后無需重刷瀏覽器,即可看見更改的效果。嗯,了解完這些后,讓我來拋出以下問題:

  • 怎么打造一個高效的開發(fā)環(huán)境?
  • 我在開發(fā)環(huán)境中咋沒見過webpack構(gòu)建的資源呢?
  • 瀏覽器怎么知道要更新哪些內(nèi)容?
  • 為什么有時候更改代碼是熱更新、有時候瀏覽器刷新呢?

相信實際開發(fā)中,我們很少去想過以上的問題,“管他呢能用就行”“項目進(jìn)度趕得飛起”是很多人的常態(tài),就算想去了解也被動不動整篇都講源碼的文章嚇跑。本篇文章希望通過大白話,讓讀者能夠了解熱更新是什么,幫開發(fā)做了什么。當(dāng)然了,大白話講的意思就是不要出現(xiàn)整段整段的代碼,盡量爭取簡單易懂。

先講下webpack與開發(fā)環(huán)境

實際開發(fā)中我們都會用現(xiàn)成的腳手架去創(chuàng)建項目,用成熟腳手架創(chuàng)建的項目自然也就帶上了一系列開發(fā)環(huán)境配置,比如熱更新。那如果我們不用腳手架呢?從零開始會是什么樣,看以下幾個階段:

  1. 手工勞作階段:代碼更新 >> 手動webpack構(gòu)建 >> 生成打包資源 >> 手動刷新瀏覽器看效果。這一階段全靠手動操作。
  2. watch階段,使用webpack --watch模式啟動項目:代碼更新 >> 自動webpack構(gòu)建 >> 生成打包資源 >> 手動刷新瀏覽器。這一階段每次更新代碼后,不用手動構(gòu)建一次了,webpack自動構(gòu)建代碼,提效了一部分。
  3. devServer階段:在第2階段的基礎(chǔ)上,增加了一個靜態(tài)資源服務(wù)器
  4. 熱更新階段:代碼更新 >> 觸發(fā)webpack自動構(gòu)建 >> 瀏覽器自動替換變更模塊

通過這幾個階段演進(jìn),可以看到開發(fā)的效率越來越高,從這里我們看到了一個高效的開發(fā)環(huán)境,但中間依然有幾個疑點,比如階段3之前,我們可以看到構(gòu)建后有資源文件的產(chǎn)出(默認(rèn)輸出到根目錄下的dist目錄),但熱更新階段卻并沒有看到每次構(gòu)建產(chǎn)生的包,那構(gòu)建資源到哪去了呢?其次,瀏覽器怎么知道要更新哪一塊內(nèi)容呢?在回答這些問題之前,讓我們先看看一些名詞定義。

先了解一些名詞

  • 關(guān)于webpack-dev-server(以下稱WDS):一個基于node.js和webpack的簡易服務(wù)器,限開發(fā)時使用。WDS包含三個核心功能,分別是提供一個簡易的web服務(wù)器webpack-dev-middleware以及webpack-dev-server/client

  • 關(guān)于webpack-dev-middleware:一個中間件,用于與webpack的compiler對象綁定,在dev-server啟動時會調(diào)用,限開發(fā)使用。有以下三個特性:

    • 通過watch mode,監(jiān)聽資源變更,然后自動打包。
    • 打包后的文件存入內(nèi)存中,而不是磁盤。
    • 支持HMR
  • 關(guān)于webpack-dev-server/client:與服務(wù)端進(jìn)行一個websocket連接,并對服務(wù)端的消息作出響應(yīng)及消息傳遞。

  • 關(guān)于HMR.runtimeHotModuleReplacementPlugin插件提供,用于在瀏覽器端請求更新后的資源以及進(jìn)行熱替換操作。

  • 關(guān)于webpack/hot/dev-server:根據(jù)webpack-dev-server/client傳遞的消息,決定是進(jìn)行live reload瀏覽器刷新還是熱更新

熱更新流程

相信通過對一些名詞的解釋,有的人大體已經(jīng)猜到熱更新的流程,接下來大體走一遍步驟。

  1. 在webpack watch模式下更新代碼,webpack會自動進(jìn)行構(gòu)建。順帶提一下,以webpack-dev-server運行默認(rèn)watch:true
  2. WDS與webpack之間進(jìn)行交互,實際上是由WDS的中間件webpack-dev-middleware與webpack進(jìn)行接口交互,webpack-dev-middleware對webpack暴露的api進(jìn)行監(jiān)控并告訴webpack將資源打包到內(nèi)存中,保存為一個Javascript對象。
  3. 如果在webpack中配置了devServer.watchContentBase: true,WDS會監(jiān)聽配置中的靜態(tài)文件的變化,并進(jìn)行live reload瀏覽器刷新,注意是直接刷新。靜態(tài)文件什么意思呢?就比如.html文件等entry入口找不到的文件。
  4. 通過WDS里的sockjs在瀏覽器端和服務(wù)器之間建立websocket長連接。這一步的目的是將webpack編譯打包的各個階段狀態(tài)告知瀏覽器,讓瀏覽器端可以根據(jù)這些消息進(jìn)行不同的操作,主要傳遞的還是新模塊的hash值。這里涉及瀏覽器端中webpack-dev-server/client,問題來了,這東西什么時候在瀏覽器端代碼中的呢?先存?zhèn)€疑。
  5. 這一步是判斷步驟,webpack/hot/dev-server會根據(jù)上一步中webpack-dev-server/client傳遞的狀態(tài)及dev-server的配置,決定是刷新瀏覽器還是進(jìn)行熱更新,如果是熱更新則進(jìn)入下一步,如果是刷新瀏覽器則流程到此結(jié)束。
  6. HMR.runtime登場,它接收到webpack-dev-server/client傳遞的新模塊hash值,然后通過JsonpMainTemplate.runtime向server端發(fā)送Ajax請求,請求返回一個json,包含了所有要更新的模塊hash值,然后該模塊再通過jsonp請求,獲取到最新的模塊代碼。
  7. 更新模塊,HMR會對新舊模塊進(jìn)行對比,決定是否更新模塊,決定更新后,檢查模塊之間的依賴關(guān)系,更新模塊的同時更新模塊間的依賴引用。
  8. 如果上一步HMR更新失敗,則回退到live reload的操作。

補充

以下內(nèi)容是對本篇文章上述部分的補充

問題:webpack/Node.js如何監(jiān)聽文件變化?

查一下Node.js的文檔,找到兩個監(jiān)聽文件變化的API:fs.watch | fs.watchFile
在webpack通過配置watch可以開啟監(jiān)聽文件變化,原理如下:
在webpack中,監(jiān)聽一個文件發(fā)生變化的原理是定時獲取該文件的最后編輯時間,如果發(fā)現(xiàn)該時間與上一次保存的時間不一致則認(rèn)為該文件發(fā)生了變化。其中,我們可以在配置項watchOptions.poll中設(shè)置輪詢的間隔時間。
在發(fā)現(xiàn)一個文件發(fā)生變動時,并不會馬上通知監(jiān)聽者,而是先緩存起來,收集一段時間后,再通知。這段時間的配置可以在watchOptions.aggregateTimeout配置,默認(rèn)值300ms

問題:HMR.runtime及webpack-dev-server/client何時注入到瀏覽器代碼(即bundle)中?

HMR.runtime:是由HotModuleReplacementPlugin插入,現(xiàn)在默認(rèn)在webpack/lib/HotModuleReplacement.runtime
webpack-dev-server/client:WDS修改了webpack配置中的entry屬性,在里面添加了webpack-dev-server/client的代碼,這樣在最后生成的bundle.js文件中就有了這一部分的代碼了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。