使用webpack打包,難免會使用Hot Module Replacement功能,該功能能夠?qū)崿F(xiàn)修改、添加或刪除前端頁面中的模塊代碼,而且是在頁面不刷新的前提下。它究竟是怎么運(yùn)作的呢?本文主要從調(diào)試工具、配置文件、官方文檔三個(gè)方面進(jìn)行解析。

調(diào)試工具

首先從chrome的調(diào)試工具network中看看,代碼改變的時(shí)候,頁面與后端之間發(fā)生了什么?

頁面初始加載

我們看到除了加載頁面所依賴的文件外,多了一個(gè)連接,這是一個(gè)Server-sent Events,相關(guān)的介紹可以參考這篇文章,而且每隔一段時(shí)間都會向發(fā)送一次數(shù)據(jù)。數(shù)據(jù)內(nèi)容主要是

action:sync操作;

hash:f397e485c539fd7a10fb,是bundle的hash,因?yàn)楹彤a(chǎn)出文件collections.f397e485c539fd7a10fbjs的內(nèi)容hash值相同;

modules:產(chǎn)出bundle中的module id和對應(yīng)的文件地址。

 

iOS培訓(xùn),Swift培訓(xùn),蘋果開發(fā)培訓(xùn),移動開發(fā)培訓(xùn)

 

修改代碼

然后修改一處代碼,webpack自動編譯后,發(fā)現(xiàn)network中發(fā)生了幾處變化,首先是客戶端收到后端發(fā)出的事件

action:built操作,通知瀏覽器webpack重新發(fā)起了編譯;

hash:最新產(chǎn)出bundle的內(nèi)容hash值為debc36315df6764f157c;

modules:bundle中的模塊id和對應(yīng)模塊的文件地址。

iOS培訓(xùn),Swift培訓(xùn),蘋果開發(fā)培訓(xùn),移動開發(fā)培訓(xùn)

另外前端對后端發(fā)起了兩個(gè)請求,請求了f397e485c539fd7a10fb.hot-update.json和0.f397e485c539fd7a10fb.hot-update.js兩個(gè)文件,文件的hash值正好是未發(fā)生修改之前后端發(fā)送前端的bundle hash值。

我們查看一下兩個(gè)文件的內(nèi)容。

json文件的內(nèi)容:

h:debc36315df6764f157c,bundle內(nèi)容的最新hash值;

c:"0": true, 表示bundle id為0的文件被修改了;

iOS培訓(xùn),Swift培訓(xùn),蘋果開發(fā)培訓(xùn),移動開發(fā)培訓(xùn)

js文件的內(nèi)容:

內(nèi)容是一個(gè)函數(shù),類似jsonp的返回形式,也就是頁面收到請求后執(zhí)行了webpackHotUpdate函數(shù),對bundle id為0的文件中的moudle id為50的模塊進(jìn)行修改。

iOS培訓(xùn),Swift培訓(xùn),蘋果開發(fā)培訓(xùn),移動開發(fā)培訓(xùn)

跟進(jìn)到這里,我們可以推測出這個(gè)交互過程:

(0)webpack首次編譯時(shí)將如何更新更新模塊(update-method)和接收后端推動事件(event-source)的代碼打包到bundle之中;

(1)webpack進(jìn)入watch 模式,在項(xiàng)目代碼發(fā)生變化的時(shí)候重新編譯;

(2)將編譯產(chǎn)出存放在dev-server,此處的編譯只針對變動的模塊,產(chǎn)出應(yīng)該包含上文中提到的oldbundlehash.hot-update.json和oldbundlehash.hot-update.js文件;

(3)dev-server中使用hot-middleware中間件向前端發(fā)送built事件;

(4)前端收到通知后,向后端請求最新的變動文件,請求到的js文件通過script標(biāo)簽加載后執(zhí)行,其實(shí)就是執(zhí)行已經(jīng)預(yù)埋到bundle中的函數(shù)(update-method),從而修改bundle文件。

 

iOS培訓(xùn),Swift培訓(xùn),蘋果開發(fā)培訓(xùn),移動開發(fā)培訓(xùn)

 

配置文件 

接下來我們從項(xiàng)目的配置文件來驗(yàn)證一下,配置文件主要參考vue-cli中的webapck項(xiàng)目。

webpack.dev.conf.js

涉及到Hot Module Replacement的地方主要有兩處:

entry的配置:在每個(gè)入口bundle開頭引入了event-source,即在頁面中接收后端發(fā)送的事件

1
/*********./build/webpack.dev.conf.js********/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 將event-source相關(guān)代碼,添加到每個(gè)入口chunk中,作為HRM Runtime的一部分。
// 后端相應(yīng)的配置見dev-server的hotMiddleware部分
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
    baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
})
<br>
/*********./build/dev-client.js********/
 
// Event-Source對象用于接收服務(wù)器端推送事件
// eventsource-polyfill用于擴(kuò)展Event-Source對象在IE瀏覽器下的兼容性
require('eventsource-polyfill')
var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
// 主要用于接受后端hotMiddleware的通知,執(zhí)行reload操作
hotClient.subscribe(function (event) {
    if (event.action === 'reload') {
        window.location.reload()
    }
})

 

插件的配置:引入HotModuleReplacementPlugin插件,將update-method的代碼打入bundle

1
2
3
4
5
6
7
8
plugins: [
        ...
        // HMR插件將HMR Runtime代碼嵌入到bundle中,能夠操作APP代碼,完成代碼替換
        new webpack.HotModuleReplacementPlugin(),
        // 報(bào)錯(cuò)提示插件:報(bào)錯(cuò)不阻塞,但是編譯后給出提示
        new webpack.NoEmitOnErrorsPlugin(),
        new FriendlyErrorsPlugin()
    ]

dev-server.js

涉及到Hot Module Replacement的地方主要有兩處:

將compiler掛載在devMiddleware上:對編譯產(chǎn)出提供靜態(tài)文件服務(wù)

1
2
3
4
5
// 將compiler掛載在dev-server上,監(jiān)聽本地代碼變化,變化則啟動編譯并將編譯后的文件暫存到內(nèi)存中
var devMiddleware = require('webpack-dev-middleware')(compiler, {
    publicPath: config.dev.assetsPublicPath === './' '' : config.dev.assetsPublicPath,
    quiet: true
})

  

將compiler掛載在hotMiddleware上:通知前端event-source對象發(fā)生了rebuilt

1
2
3
4
// 編譯后發(fā)送通知到HRM Runtime,HRM Runtime收到update通知后,下載更新的模塊,通知APP更新,APP收到通知,然后要求HRM Runtime執(zhí)行模塊替換
var hotMiddleware = require('webpack-hot-middleware')(compiler, {
    log: () => {}
})

  

由配置文件可以基本驗(yàn)證之前通過network debug得到的推論,接下來去看一下官方文檔驗(yàn)證一下。

官方文檔

官方文檔先是總體介紹了一下 Hot Module Replacement的基本原理,然后將原理中涉及到幾個(gè)知識點(diǎn)進(jìn)行了介紹。

基本原理

webapck在編譯的過程中,將HMR Runtime嵌入到bundle中;編譯結(jié)束后,webpack對項(xiàng)目代碼文件進(jìn)行監(jiān)視,發(fā)現(xiàn)文件變動重新編譯變動的模塊,同時(shí)通知HMR Runtime,然后HMR Runtime加載變動的模塊文件,嘗試執(zhí)行熱更新操作。更新的邏輯是:先檢查模塊是否能支持accept方法,不支持的話,則冒泡查找模塊樹的父節(jié)點(diǎn),直到入口模塊,accept方法也就是模塊hot-replace的handler。

知識點(diǎn)

(1)compiler

這里的compiler也就是指webapck,主要提供update的信息,也就是update menifest(json文件格式)和update chunks(js文件格式);

(2)app

app也就是指前端頁面,app中的代碼主要調(diào)用HMR Runtime下載最新的模塊代碼,然后調(diào)用HMR Runtime執(zhí)行update操作;

(3)HMR Runtime

HMR Runtime是webapck內(nèi)嵌到前端頁面的代碼,主要提供來能給個(gè)職能check和apply。check用來下載最新模塊代碼,runtime能夠接收后端發(fā)送的事件和發(fā)送請求;apply用于更新模塊,主要將要更新的模塊打上tag,然后調(diào)用模塊的(也有可能是父模塊)的更新handler執(zhí)行更新。

(4)module

HRM是一個(gè)可插拔的工具,只能影響包含HMR code的模塊。通常情況下,沒有必要為每個(gè)模塊寫入HMR code,更新的時(shí)候會進(jìn)行冒泡檢查HMR code的是否存在。

 

根據(jù)官方文檔的介紹,基本和我們的推論吻合,區(qū)別在于官方文檔引入了HMR Runtime的概念,這個(gè)可以看作是推論中的event-source和update-method的結(jié)合體。

現(xiàn)在大家應(yīng)該清楚了 webpack的Hot Module Replacement的基本原理了,官方文檔中提到了如何根據(jù)最新的模塊替換舊模塊的方法,這個(gè)會放到下一篇文章中進(jìn)行介紹。

http://www.cnblogs.com/wmhuang/p/7137480.html