1. 程式人生 > >教你怎麼使用 webpack3 的 HMR 模組熱載入

教你怎麼使用 webpack3 的 HMR 模組熱載入

前注:

如果可以,請給本專案加【Star】和【Fork】持續關注。

有疑義請點選這裡,發【Issues】。

點選這裡檢視DEMO

7、模組熱載入 HMR

7.0、使用說明

安裝:

npm install

執行(注意,是 dev):

npm run dev

結論放前面,適合場景:

  1. 當使用 style-loader 時,修改 css 樣式;
  2. 當使用 vue-loader 之類帶 HMR 功能的 loader 時,修改對應的模組;
  3. 當僅僅只是需要修改程式碼後,頁面可以自動重新整理,以保證當前頁面上是最新的程式碼;

7.1、現象和原理

當談到 HMR 的時候,首先要明確一個概念,HMR 到底是什麼?

如果用過帶 HMR 功能的腳手架,例如我分享的這個 Vue的腳手架,大約能給出一個答案:

  1. 修改程式碼後,不需要重新整理頁面,修改後的程式碼效果,就能立刻體現在頁面上;
  2. 已有的效果,比如輸入框裡輸入了一些內容,程式碼更新後,往往內容還在;
  3. 似乎想不到其他的了;

從現象來看,在一定程度上,這個描述問題不大,但不嚴謹。

我們需要分析 HMR 到底是什麼?

  1. webpack 是模組化的,每個 js, css 檔案,或者類似的東西,都是一個模組;
  2. HMR 是模組熱載入,指模組被修改後,webpack檢測並用新模組更新;
  3. 也就是原來的模組被移除後,用修改後的模組替換;
  4. 表現效果(通常):如果是 js,會重新執行一遍;如果是 css,並用了 style-loader
    ,原有的會被替換;

以上是理論基礎,實際流程如下:

  1. 假如 B 模組的程式碼被更改了,webpack 可以檢測到,並且可以知道是哪個更改了;
  2. 然後根據 依賴圖,發現 A 模組依賴於 B 模組,於是向上冒泡到 A 模組中,判斷 A 模組裡有沒有處理熱載入的程式碼;
  3. 如果沒有,則繼續向上冒泡,直到冒泡到頂層,最後觸發頁面重新整理(如果引用了多個chunk,當一個冒泡到頂層並且沒有被處理的話,整個頁面都會觸發重新整理);
  4. 如果中途被捕獲,那麼將只重新載入冒泡路徑上的模組,並觸發對應 HMR 處理函式的回撥函式;

更具體的內容,請檢視官網的說明,附連結如下:

7.2、應用場景

HMR 的應用場景,最合適的是帶 HMR 功能的loader。

例如 style-loader,或 vue-loader

原因很簡單,自己在頁面裡寫 HMR 冒泡捕獲功能,寫起來很麻煩,也很容易導致遺漏。

最重要的是,這些程式碼並不是業務程式碼,而是 HMR 專用程式碼,這些程式碼會在webpack打包時被打包進去(可以檢視打包好後的原始碼)Z,但這沒有意義。

因此在 loader 裡進行處理,對模組的載入才是更加有效的。

當然,假如你只是希望儲存修改的程式碼,會自動觸發頁面重新整理,以保證頁面上的程式碼是最新的,那麼也是可以的。

這種情況只需要啟用 HMR 功能,不需要寫 HMR 的捕獲程式碼,讓觸發的 update 行為自動冒泡到頂層,觸發頁面重新整理就好了(參照 開發環境中的 6.2)。

具體可以參照下面的示例DEMO。

7.3、使用說明

7.3.1、HMR 的冒泡

先假設引用關係: A -> B -> C

【1】HMR 是向上冒泡的:

  1. C 被更改後,會去找他的父模組 B,檢視 B 中有沒有關於 C 的 HMR 的處理程式碼:
  2. 如果沒有,那麼會繼續向上冒泡到 A,檢視 A 中有沒有關於 B 的 HMR 的處理程式碼;
  3. 如果 A 沒有,因為 A 是入口檔案,所以會重新整理整個頁面;

【2】冒泡過程中只有父子關係:

C 更改,冒泡到 B(B 無 HMR 處理程式碼),然後冒泡到 A。

此時,在 A 這裡,視為 B 被更改(而不是 C),

因此 A 裡面處理 HMR 程式碼,捕獲的模組,應該是 B,而不是 C,

如果 A 的目標是 C,那麼該段程式碼不會響應(雖然冒泡的起點是 C);

【3】HMR 觸發,會執行整個冒泡流程中涉及到的模組中的程式碼:

例如上面的 C 更改,B 捕獲到了,重新執行 C;

B 無捕獲程式碼向上冒泡,A捕獲到了,重新執行 B 和 C;

假如引用關係是:A -> B -> C 和 D,即 B 裡面同時引用 C 和 D 兩個模組,並且 B 沒有處理 HMR 的程式碼,A 有:

  1. 冒泡起點是 B:B 重新執行一遍自己的程式碼,C 和 D 不會執行;
  2. 冒泡起點是 C:B 和 C 重新執行一遍自己的程式碼, D 不會執行;
  3. 冒泡起點是 D:B 和 D 重新執行一遍自己的程式碼, C 不會執行;

【4】冒泡行為起點的子模組,其程式碼不會被重新執行:

先假設引用關係:A -> B -> C -> D,B 沒有 處理 HMR 的程式碼,C 有沒有無所謂,A 有。

冒泡起點是 C,因此冒泡到 A。

從上面我們可以得知,B 和 C 會被重新執行,那麼 D 呢?

答案是不會,因為 D 不在冒泡路線上。

總結:

總結以上四點,得出一個結論:

  1. 從修改的模組開始冒泡,直到被捕獲為止;
  2. 冒泡路徑上的程式碼(不包含捕獲到的模組),都會被重新執行;
  3. 非冒泡路徑上的程式碼,不管是子模組,或者是兄弟模組,都不會被重新執行(除非是整個頁面被重新整理)Z。

7.3.2、HMR 的隱患

以上特點這就可能帶來一些後果(主要是 js 程式碼):

  1. 假如我程式碼裡,有繫結事件,那麼當修改程式碼並重新執行一遍後,顯然會再繫結一次,因此會導致重複繫結的問題(因此要考慮到解綁之前的事件);
  2. 類似的,如果程式碼裡添加了 DOM,那麼當重新執行的時候,原本 DOM 節點還在,重新執行的時候又添加了一次;
  3. 如果有某些一次性操作,比如程式碼裡移除了某個 DOM,那麼很可能 HMR 不能解決你的問題,也許需要重新重新整理後,表現才正常;

7.3.3、HMR 的一個坑

那就是引用時候的名字,和處理的 API,引用的檔名,需要相同;

舉例:

// 引入
import foo from './foo.js';

// 處理
module.hot.accept('./foo.js', callback);

如果不一樣,會導致第一次響應正常,後面就可能導致無法正常觸發 HMR ,雖然提示模組更新,但不會重新執行模組的程式碼。

7.4、示例

為了說明 HMR 是怎麼使用和生效,這裡將給一個最簡單的示例,包含 html、css、和 js 程式碼,來解釋其的使用方法。

可以直接 fork 本專案參看原始碼,以下是分析作用,以及如何生效的。

需要使用的東西:

  1. 使用 webpack-dev-server,參考上一篇6、開發環境
  2. 兩個HMR外掛:webpack.NamedModulesPluginwebpack.HotModuleReplacementPlugin
  3. 配置一下 package.json,新增一行 scripts :"dev": "webpack-dev-server --open --config webpack.config.js"
  4. style-loader,用於實現 css 的 HMR(使用後預設開啟);

依賴圖:

app.js        入口檔案,在其中配置了 foo.js 和 bar.js 的 HMR 處理函式
├─style.css   樣式檔案
├─img
│  ├─1.jpg    圖片1
│  └─2.jpg    圖片2
├─foo.js      模組foo,配置了 HMR 模組熱替換的介面
│  └─bar.js   模組bar,是foo的子模組
└─DOM.js      抽象出一個創造 DOM,並插入到 body 標籤的函式

1、先分析 js 部分

app.js

// 引入資源
import './style.css';
import foo from './foo.js';
import createDOM from './DOM.js'

// 建立一個DOM並插入<body>標籤
let el = createDOM({
    id: 'app-box',
    innerHTML: 'app.js<input>'
})
document.body.appendChild(el);

// 本行程式碼表示app.js已經被執行了一遍
console.log('%c%s', 'color:red;', 'app.js is running...')

// 兩個子模組建立DOM並插入<body>標籤
foo()

// 這裡是控制 HMR 的函式
// 注:
// 這裡引用的 foo.js 模組,那麼處理 foo.js HMR 效果的程式碼必須寫在這裡;
// 特別提示:這段程式碼不能抽象封裝到另外一個js檔案中(即使那個js檔案也被 app.js import進來)
// 推測是根據webpack的依賴圖,向上找父模組,然後在父模組的程式碼中,找有沒有處理 HMR 的程式碼
if (module.hot) {
    module.hot.accept('./foo.js', function (url) {
        // 回撥函式只有url一個引數,型別是陣列
        // 執行時機是 foo.js 中的程式碼執行完畢後執行
        console.log('%c%s', 'color:#FF00FF;', `[${url}] is update`)
    })
}

foo.js

// 引入資源
import createDOM from './DOM'
import bar from "./bar.js";
// bar 中建立的DOM邏輯,在 foo 中執行
bar()

// 執行本段程式碼的時候,表示 foo.js 被重新執行了
console.log('%c%s', 'color:green;', 'foo.js is running...')

function Foo() {
    let el = createDOM({
        id: 'foo-box',
        classList: 'foo',
        innerHTML: 'foo.js<input>'
    })

    document.body.appendChild(el);
}

// 匯出給 app.js 執行
export default Foo

// 這裡寫 bar.js 的 HMR 邏輯
if (module.hot) {
    module.hot.accept('./bar.js', function (args) {
        console.log('%c%s', 'color:#FF00FF', `[${args}] is update`)
    })
}

bar.js

// 引入資源
import createDOM from './DOM'

// 執行本段程式碼的時候,表示 bar.js 被重新執行了
console.log('%c%s', 'color:blue;', 'bar.js is running...')

function Bar() {
    let el = createDOM({
        id: 'bar-box',
        classList: 'bar',
        innerHTML: 'bar.js<input>'
    })

    document.body.appendChild(el);
}

// 匯出給 foo.js 執行
export default Bar

簡單總結一下以上程式碼:

  1. app.js 作為入口檔案,他引入了自己的子模組 foo.js,以及 css 資原始檔,並且處理自己子模組的 HMR 行為;
  2. foo.js 作為 app.js 的子模組,他引入了自己的子模組 bar.js ,並且處理自己子模組的 HMR行為;
  3. bar.js 沒做什麼特殊的;
  4. 三個模組裡,都有一行 console.log() 程式碼,當出現在瀏覽器的控制檯裡的時候,表示該模組程式碼被重新執行了一遍;
  5. 父模組處理子模組的 HMR 時,回撥函式裡有一行 console.log() 程式碼,表示該子模組已經重新載入完畢;
  6. 因此,理論上,我們修改 foo.js 或者 bar.js 檔案後,首先會看到該模組的 console.log() 程式碼,其次會看到其父模組處理 HMR 的回撥函式中的 console.log() 程式碼;


首次重新整理頁面後,控制檯先輸出三條 log,和幾行 HMR程式碼,略略略。

修改 foo.js

當我們修改 foo.js 的 log 程式碼:console.log('%c%s', 'color:green;', 'foo.js is running...I change it')

控制檯輸出:

foo.js is running...I change it
[./foo.js] is update
[HMR] Updated modules:
[HMR]  - ./foo.js
[HMR] App is up to date.

正如我們所料,foo.js 程式碼被重新執行了一遍,然後觸發了 app.js 裡面 module.hot.accept() 的回撥函式(注意,有先後順序)。

並且,頁面上多了一個 DOM 節點(來自 bar.js的,因為在 foo.js 裡面執行了 bar()),這正是我們前面所提出來的,HMR 機制的天生缺陷之一。

另外請注意,所以 bar.js 是 foo.js 的子模組,但由於 bar.js 並沒有被修改,所以 bar.js 裡面的程式碼沒有重新執行一遍(除了他暴露給 foo.js 的介面)。

修改 bar.js

當我們修改 bar.js 的 log 程式碼:console.log('%c%s', 'color:blue;', 'bar.js is running...and bar has been changed')

控制檯輸出:

bar.js is running...and bar has been changed
[./bar.js] is update
[HMR] Updated modules:
[HMR]  - ./bar.js
[HMR] App is up to date.

bar.js 是 foo.js 的子模組,而且 foo.js 裡面有關於處理 bar.js 的模組 HMR 功能的程式碼。

因此 bar.js 被修改後,冒泡到自己的父模組時就被捕獲到,並沒有繼續向上冒泡。

讓 bar.js 的修改冒泡到 app.js

假如讓 bar.js 的修改冒泡到 app.js 會發生什麼事情呢?先修改程式碼:

app.js 嘗試讓 app.js 同時捕獲 foo.js 和 bar.js 的修改

// from
module.hot.accept('./foo.js', function (url) {

// to
module.hot.accept(['./foo.js', './bar.js'], function (url) {

foo.js 註釋掉對 bar.js 的 HMR 功能的處理程式碼

// from 
if (module.hot) {
    module.hot.accept('./bar.js', function (args) {
        console.log('%c%s', 'color:#FF00FF', `[${args}] is update`)
    })
}

// to 
// if (module.hot) {
//     module.hot.accept('./bar.js', function (args) {
//         console.log('%c%s', 'color:#FF00FF', `[${args}] is update`)
//     })Z
// }

恢復之前 foo.js 和 bar.js 的 console.log() 的修改

// foo.js
// from
console.log('%c%s', 'color:green;', 'foo.js is running...I change it')

// to
console.log('%c%s', 'color:green;', 'foo.js is running...')


// bar.js
// from
console.log('%c%s', 'color:blue;', 'bar.js is running...and bar has been changed')

// to
console.log('%c%s', 'color:blue;', 'bar.js is running...')

修改完畢,此時重新整理一下頁面,重置狀態。然後我們給 bar.js 新增一行程式碼 console.log('bar.js is be modified')

控制檯輸出:

bar.js is running...
bar.js is be modified
foo.js is running...
[./foo.js] is update
[HMR] Updated modules:
[HMR]  - ./bar.js
[HMR]  - ./foo.js
[HMR] App is up to date.

這說明,webpack 成功捕捉到了 bar.js 的修改,並且更新了 bar.js 和 foo.js 。

並且,雖然在 app.js 裡去嘗試捕獲 bar.js ,然而,因為 bar.js 並不是 app.js 的子模組(而是子模組的子模組),因此是捕獲不到的。

複數監視

module.hot.accept這個函式中,引數一可以接受一個數組,表示監視的模組可以是複數。

所以不需要寫多個函式來監視多個模組,如果他們之間邏輯是複用的話,那麼一個模組就行了。

總結:

js 檔案被修改,會導致冒泡過程中,涉及到的 js 檔案,都被重新執行一遍。

2、再分析 css 部分

在使用 style-loader 後,我們不需要配置任何東西,就可以實現 HMR 效果。

style.css

#app-box {
    color: red;
}

預設開啟頁面,會發現頁面上 app.js 那一行的字型顏色是紅色。

修改這個css樣式為:

#app-box {
    color: red;
    font-size: 24px;
}

在儲存這個css檔案後,會發現頁面在沒有重新整理的情況下,樣式已經改變了。

由於我們開發一般都會採用 style-loader,而且 css 由於是替代效果,也不是可執行程式碼,因此天生適用於 HMR 場景。

7.5、總結

css 檔案沒有什麼好說的,只要使用 style-loader 即可。

因為 HMR 的特性(會重新執行 js 檔案),所以如果沒有 loader 輔助的話,寫在 HMR 下可用的 js 程式碼是很麻煩的。

想象一下,你的js程式碼裡有一個建立並插入 DOM 的操作,然後在你每次修改這個模組裡的程式碼時,都會建立一個新的 DOM,並插入。

例如本 DEMO 裡,修改 foo.js 檔案,會導致重新執行 foo 模組時,執行 bar.js 暴露出來的介面 bar,

於是頁面被重複插入一個 DOM,這顯然不符合我們的預期。

當然了,也有解決辦法,重新整理頁面即可恢復正常。

類似的還有 繫結事件(導致重複繫結),發起非同步請求(導致多次發起非同步請求)等。

那麼有沒有解決辦法呢?

答案是使用相關的 loader,並且寫符合相關格式的程式碼。

例如 vue-loader 可以處理 .vue 結尾的檔案。在你修改 .vue 檔案的時候,就可以自動處理。假如你 .vue 檔案不按要求寫,而是自己亂寫,那麼顯然就不能正常執行。

相關推薦

怎麼使用 webpack3HMR 模組載入

前注: 如果可以,請給本專案加【Star】和【Fork】持續關注。 有疑義請點選這裡,發【Issues】。 點選這裡檢視DEMO 7、模組熱載入 HMR 7.0、使用說明 安裝: npm install 執行(注意,是 dev):

webpack學習(七):啟用 HMR(模組替換)

demo地址: https://github.com/Lkkkkkkg/webpack-demo 上次使用 webpack-dev-serve : https://blog.csdn.net/qq593249106/article/details/84922572 當前目錄結構 :

Android 框架練成 打造高效的圖片載入框架

                轉載請標明出處:http://blog.csdn.net/lmj623565791/article/details/41874561,本文出自:【張鴻洋的部落格】1、概述優秀的圖片載入框架不要太多,什麼UIL , Volley ,Picasso,Imageloader等等。但是

android打造獨一無二的圖片載入框架

前言 首先,最近是在忙okhttp沒錯。不過或許有人問為什麼忙著okhttp怎麼又扯到了圖片載入上了。其實,最近想實現下斷點續傳以及多檔案下載,但並不知道怎麼搞。群裡有小夥伴提出了控制執行緒池來實現。然後我就想到了圖片載入需要控制執行緒池,所以在此鞏固下。

實現圖片的惰性載入

圖片惰性載入 DEMO 地址 -> 圖片惰性載入(放在了 Github 上,所以可能會慢,最好用 chrome) 關於惰性載入 在講圖片的惰性載入前,我們先來聊聊惰性載入。惰性載入又稱為延遲載入、懶載入等,還有個好聽的英文名字叫做 "lazyload"。需要注意

手把手webpack3(11)PostCSS-Loader配置簡述

POSTCSS-LOADER配置簡述 前注: 如果可以,請給本專案加【Star】和【Fork】持續關注。 有疑義請點選這裡,發【Issues】。 DEMO地址 1、概述 postcss-loader 用於處理css程式碼,具有下列特點: 通常

手把手webpack3(7)style-loader詳細使用說明

STYLE-LOADER詳細使用說明 前注: 如果可以,請給本專案加【Star】和【Fork】持續關注。 有疑義請點選這裡,發【Issues】。 DEMO地址 1、概述 簡單來說,style-loader是將css-loader打包好的css程式

如何在FLASH中載入外部SWF檔案

方法很簡單,但是卻相當實用,比如做一個flash全站,需要按功能將網站分解成多個flash,然後利用本例中的原理,通過一個首頁flash呼叫其他各個頁面的flash檔案即可實現。本例為基礎示例,可以根據需要進行擴充套件,關鍵是要學會變通,將其變為自己的東西。 實現方法: 1

SwiftUI - 一步一步使用UIViewRepresentable封裝網路載入檢視(UIActivityIndicatorView)

概述 網路載入檢視,在一個聯網的APP上可以講得上是必須要的元件,在SwiftUI中它並沒有提供如 UIKit 中的UIActivityIndicatorView直接提供給我們呼叫,但是我們可以通過 SwiftUI 中的UIViewRepresentable協議封裝UIActivityIndicatorVie

【小松手遊開發】【unity系統模塊開發】

無 手遊開發 現在的手遊項目如果沒個熱更新叠代根本跟不上, 特別是像我們項目做mmo的更是需要經常改動代碼。 而現在的項目一般會選擇用lua的方式實現熱更新 不過我們項目由於歷史原因沒有使用,用的是另外一種方案 在項目裏的所有GameObject都不掛腳本(NGUI腳本就通過代碼的方式掛上),自己寫的

手把手webpack3搭建react項目(開發環境和生產環境)(一)

stc reac config nod top llb cor git history 開發環境和生產環境整個配置源碼在github上,源碼地址:github-webpack-react 如果覺得有幫助,點個Star謝謝!! (一)是開發環境,(二)是生產環境。 一、首

聰哥哥學Python之模組

聰哥哥本次主要圍繞使用模組和如何安裝第三方模組等兩個話題談論。 不過在此之前,需要普及一下模組的相關概念知識。 那麼什麼是模組? 引用百度百科上說的: 模組 是一個設計術語,是指對詞條中部分內容進行格式化整理的模板。例如歌手類詞條中的“音樂作品”模組,電視劇類詞條的“分集劇

Angular 6 HMR 載入配置

什麼是 HMR? ​ HMR 是hot module replacement 的簡稱,直譯:熱模組替換,如果不開啟HMR模式,angular專案在模組更改的時候會從根節點開始重新整理,開啟HMR模式以後,只會重新整理有修改的地方,開發效率在某種意義上可以提高 配置步驟 配置前提:你已經建立了一個angu

手把手如何開啟湯不(Tumblr),輕鬆一招搞定~

小夥伴們肯定都知道tumblr這個高大上的東東,但是國內好像無法開啟~ 下面我就給大家詳細講解下tumblr登入方法和註冊方法 1.可以去搜索下載一款工具 安裝,如圖 2.選擇國外節點 連線之後,OK,成功開啟tumblr~ 分享給你們一些我關注的有

一步一步實現阿里巴巴的Sophix修復(一)

1.0 整合準備 gradle遠端倉庫依賴, 開啟專案找到app的build.gradle檔案,新增如下配置: 新增maven倉庫地址: repositories { maven { url "http://maven.ali

如何呼叫百度編輯器ueditor的上傳圖片、上傳檔案等模組

出於興趣愛好,前段時間自己嘗試寫了一個叫simple的cms,裡面使用了百度ueditor編輯器,發現它的多圖片上傳模組很不錯,用起來很方便,又可以選擇已經上傳好的圖片。正好我又是個懶人,發現有現成的自己就不想新開發了。於是我就想,是不是可以呼叫這個上傳模板為自己所用呢?

從原理到程式碼:大牛如何用 TensorFlow 親手搭建一套影象識別模組 | AI 研習社

自 2015 年 11 月首次釋出以來,TensorFlow 憑藉谷歌的強力支援,快速的更新和迭代,齊全的文件和教程,以及上手快且簡單易用等諸多的優點,已經在影象識別、語音識別、自然語言處理、資料探勘和預測等 AI 場景中得到了十分廣泛的應用。 在所有這些 AI 應用場景中

大牛如何用 TensorFlow 親手搭建一套影象識別模組

轉自:http://tech.sina.com.cn/roll/2017-03-22/doc-ifycspxn9397393.shtml 視訊:https://v.qq.com/x/page/n0386utnrb0.html?start=492 自 2015 年 11

手把手實現RecyclerView的下拉重新整理和上拉載入更多

個人原創,轉載請註明出處http://blog.csdn.net/u012402124/article/details/78210639 2018年10月25日更新 讓大家花費時間看文章卻沒有解決需求,隨著bug的增多內心的愧疚感逐漸增強,但幾個月前的程式

webpack模組替換(HMR)/更新

這是一篇關於webpack熱模組替換的最簡單的配置(不需要react),也稱作熱更新。 模組熱替換(HMR)的作用是,在應用執行時,無需重新整理頁面,便能替換、增加、刪除必要的模組。 HMR 對於那些由單一狀態樹構成的應用非常有用。因為這些應用的元