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)境配置,比如熱更新。那如果我們不用腳手架呢?從零開始會是什么樣,看以下幾個階段:
- 手工勞作階段:代碼更新 >> 手動webpack構(gòu)建 >> 生成打包資源 >> 手動刷新瀏覽器看效果。這一階段全靠手動操作。
-
watch階段,使用
webpack --watch
模式啟動項目:代碼更新 >> 自動webpack構(gòu)建 >> 生成打包資源 >> 手動刷新瀏覽器。這一階段每次更新代碼后,不用手動構(gòu)建一次了,webpack自動構(gòu)建代碼,提效了一部分。 - devServer階段:在第2階段的基礎(chǔ)上,增加了一個靜態(tài)資源服務(wù)器
- 熱更新階段:代碼更新 >> 觸發(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.runtime:
HotModuleReplacementPlugin
插件提供,用于在瀏覽器端請求更新后的資源以及進(jìn)行熱替換操作。關(guān)于webpack/hot/dev-server:根據(jù)
webpack-dev-server/client
傳遞的消息,決定是進(jìn)行live reload
瀏覽器刷新還是熱更新
。
熱更新流程
相信通過對一些名詞的解釋,有的人大體已經(jīng)猜到熱更新的流程,接下來大體走一遍步驟。
- 在webpack watch模式下更新代碼,webpack會自動進(jìn)行構(gòu)建。順帶提一下,以
webpack-dev-server
運行默認(rèn)watch:true
。 - WDS與webpack之間進(jìn)行交互,實際上是由WDS的中間件
webpack-dev-middleware
與webpack進(jìn)行接口交互,webpack-dev-middleware
對webpack暴露的api進(jìn)行監(jiān)控并告訴webpack將資源打包到內(nèi)存中,保存為一個Javascript對象。 - 如果在webpack中配置了
devServer.watchContentBase: true
,WDS會監(jiān)聽配置中的靜態(tài)文件的變化,并進(jìn)行live reload
瀏覽器刷新,注意是直接刷新。靜態(tài)文件什么意思呢?就比如.html文件等entry入口找不到的文件。 - 通過WDS里的
sockjs
在瀏覽器端和服務(wù)器之間建立websocket
長連接。這一步的目的是將webpack編譯打包的各個階段狀態(tài)告知瀏覽器,讓瀏覽器端可以根據(jù)這些消息進(jìn)行不同的操作,主要傳遞的還是新模塊的hash值。這里涉及瀏覽器端中webpack-dev-server/client
,問題來了,這東西什么時候在瀏覽器端代碼中的呢?先存?zhèn)€疑。 - 這一步是判斷步驟,
webpack/hot/dev-server
會根據(jù)上一步中webpack-dev-server/client
傳遞的狀態(tài)及dev-server
的配置,決定是刷新瀏覽器還是進(jìn)行熱更新,如果是熱更新則進(jìn)入下一步,如果是刷新瀏覽器則流程到此結(jié)束。 -
HMR.runtime
登場,它接收到webpack-dev-server/client
傳遞的新模塊hash值,然后通過JsonpMainTemplate.runtime
向server端發(fā)送Ajax請求,請求返回一個json,包含了所有要更新的模塊hash值,然后該模塊再通過jsonp請求,獲取到最新的模塊代碼。 - 更新模塊,HMR會對新舊模塊進(jìn)行對比,決定是否更新模塊,決定更新后,檢查模塊之間的依賴關(guān)系,更新模塊的同時更新模塊間的依賴引用。
- 如果上一步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文件中就有了這一部分的代碼了。