Webpack:Hot Module Replacement

2023-05-09 17:05 更新

如果已經(jīng)通過 HotModuleReplacementPlugin 啟用了 Hot Module Replacement, 則它的接口將被暴露在 module.hot 以及 import.meta.webpackHot 屬性下。請注意,只有 import.meta.webpackHot 可以在 strict ESM 中使用。

通常,用戶先要檢查這個接口是否可訪問, 再使用它。你可以這樣使用 accept 操作一個更新的模塊:

if (module.hot) {
  module.hot.accept('./library.js', function() {
    // 對更新過的 library 模塊做些事情...
  });
}

// or
if (import.meta.webpackHot) {
  import.meta.webpackHot.accept('./library.js', function () {
    // Do something with the updated library module…
  });
}

支持以下方法

模塊 API

accept

接受(accept)給定 依賴模塊(dependencies) 的更新,并觸發(fā)一個 回調(diào)函數(shù) 來響應(yīng)更新,除此之外,你可以附加一個可選的 error 處理程序:

module.hot.accept(
  dependencies, // 可以是一個字符串或字符串?dāng)?shù)組
  callback // 用于在模塊更新后觸發(fā)的函數(shù)
  errorHandler // (err, {moduleId, dependencyId}) => {}
);

// or
import.meta.webpackHot.accept(
  dependencies, // 可以是一個字符串或字符串?dāng)?shù)組
  callback, // 用于在模塊更新后觸發(fā)的函數(shù)
  errorHandler // (err, {moduleId, dependencyId}) => {}
);

當(dāng)使用 ESM import 時,所有從 dependencies 中導(dǎo)入的符號都會自動更新。注意:依賴項字符串必須與 import 中的 from 字符串完全匹配。在某些情況下, 甚至可以省略 callback。在 callback 中使用的 require() 在這里沒有任何意義。

在使用 CommonJS 時,你應(yīng)該通過 callback 中的 require() 手動更新依賴模塊。省略 callback 在這里沒有任何意義。

errorHandler for accept

(err, {moduleId, dependencyId}) => {}

  • err: 當(dāng)使用 ESM 依賴項時,回調(diào)函數(shù)在第二個參數(shù)中或在依賴項執(zhí)行期間拋出的錯誤。
  • moduleId: 當(dāng)前模塊 id。
  • dependencyId: (第一個)被更改依賴項的模塊id。

accept (self)

接受自身更新。

module.hot.accept(
  errorHandler // 在計算新版本時處理錯誤的函數(shù)
);

// or
import.meta.webpackHot.accept(
  errorHandler // Function to handle errors when evaluating the new version
);

在此模塊或依賴模塊更新時,可以在不通知父依賴的情況下,對此模塊處理和重新取值。如果此模塊沒有導(dǎo)出(或以其他方式更新的導(dǎo)出),這是有意義的。

當(dāng)執(zhí)行此模塊(或依賴模塊)拋出異常時,會觸發(fā) errorHandler。

errorHandler for self accept

(err, {moduleId, module}) => {}

  • err: 計算新版本時的錯誤。
  • moduleId: 當(dāng)前模塊 id。
  • module: 當(dāng)前模塊實例。module.hot: 允許訪問出錯模塊實例的 HMR API。一個常見的場景是再次自我接收(accept)。添加一個dispose處理程序來傳遞數(shù)據(jù)也是有意義的。注意,錯誤的模塊可能已經(jīng)部分執(zhí)行,所以請確保不要進入不一致的狀態(tài)。你可以使用 module.hot.data 存儲部分狀態(tài)。module.exports: 可以被重載,但是要小心,因為屬性名在生產(chǎn)模式下可能會被破壞。

decline

拒絕給定依賴模塊的更新,使用 'decline' 方法強制更新失敗。

module.hot.decline(
  dependencies // 可以是一個字符串或字符串?dāng)?shù)組
);

// or
import.meta.webpackHot.decline(
  dependencies // Either a string or an array of strings
);

將依賴模塊標記為不可更新(not-update-able)。在處理「依賴的導(dǎo)出正在更新」或「尚未實現(xiàn)處理」時,這是有意義的。取決于你的 HMR 管理代碼, 此依賴模塊(或其未接受的依賴模塊)更新,通常會導(dǎo)致頁面被完全重新加載。

decline (self)

拒絕自身更新。

module.hot.decline();

// or
import.meta.webpackHot.decline();

將依賴模塊標記為不可更新(not-update-able)。當(dāng)此模塊具有無法避免的外部作用(side-effect),或者尚未對此模塊進行 HMR 處理時,這是有意義的。取決于你的 HMR 管理代碼,此依賴模塊(或其未接受的依賴模塊)更新,通常會導(dǎo)致頁面被完全重新加載。

dispose (or addDisposeHandler)

添加一個處理函數(shù),在當(dāng)前模塊代碼被替換時執(zhí)行。此函數(shù)應(yīng)該用于移除你聲明或創(chuàng)建的任何持久資源。如果要將狀態(tài)傳入到更新過的模塊,請?zhí)砑咏o定 data 參數(shù)。更新后,此對象在更新之后可通過 module.hot.data 調(diào)用。

module.hot.dispose(data => {
  // 清理并將 data 傳遞到更新后的模塊...
});

// or
import.meta.webpackHot.dispose((data) => {
  // Clean up and pass data to the updated module...
});

invalidate

調(diào)用此方法將使當(dāng)前模塊無效,而當(dāng)前模塊將在應(yīng)用 HMR 更新時進行部署并重新創(chuàng)建。這個模塊的更新像冒泡一樣,拒絕自身更新。

在 idle 狀態(tài)下調(diào)用時,將創(chuàng)建一個包含此模塊的新 HMR 更新。HMR 將進入 ready 狀態(tài)。

在 ready 或 prepare 狀態(tài)下調(diào)用時,此模塊將添加到當(dāng)前 HMR 的更新中。

在 check 狀態(tài)期間被調(diào)用時,如果有可用更新,則此模塊將添加到更新中。如果沒有可用的更新,它將創(chuàng)建一個新更新。HMR 將進入 ready 狀態(tài)。

在 dispose 或 apply 狀態(tài)下調(diào)用時,HMR 將在退出這些狀態(tài)后將其拾取。

用例

Conditional Accepting

一個模塊可以接受一個依賴,但是當(dāng)依賴的改變無法處理時,可以調(diào)用 invalidate:

import { x, y } from './dep';
import { processX, processY } from 'anotherDep';

const oldY = y;

processX(x);
export default processY(y);

module.hot.accept('./dep', () => {
  if (y !== oldY) {
    // 無法處理,冒泡給父級
    module.hot.invalidate();
    return;
  }
  // 可以處理
  processX(x);
});

Conditional self accept

模塊可以自我接受,但是當(dāng)更改無法處理時可以使自身失效:

const VALUE = 'constant';

export default VALUE;

if (
  module.hot.data &&
  module.hot.data.value &&
  module.hot.data.value !== VALUE
) {
  module.hot.invalidate();
} else {
  module.hot.dispose((data) => {
    data.value = VALUE;
  });
  module.hot.accept();
}

Triggering custom HMR updates

const moduleId = chooseAModule();
const code = __webpack_modules__[moduleId].toString();
__webpack_modules__[moduleId] = eval(`(${makeChanges(code)})`);
if (require.cache[moduleId]) {
  require.cache[moduleId].hot.invalidate();
  module.hot.apply();
}

removeDisposeHandler

刪除由 dispose 或 addDisposeHandler 添加的回調(diào)函數(shù)。

module.hot.removeDisposeHandler(callback);

// or
import.meta.webpackHot.removeDisposeHandler(callback);

API 管理

status

獲取當(dāng)前模塊熱替換進程的狀態(tài)。

module.hot.status(); // 返回以下字符串之一...

// 或者
import.meta.webpackHot.status();
Status Description
idle 該進程正在等待調(diào)用 check(見下文)
check 該進程正在檢查以更新
prepare     該進程正在準備更新(例如,下載已更新的模塊)
ready 此更新已準備并可用
dispose 該進程正在調(diào)用將被替換模塊的 dispose 處理函數(shù)
apply 該進程正在調(diào)用 accept 處理函數(shù),并重新執(zhí)行自我接受(self-accepted)的模塊
abort 更新已中止,但系統(tǒng)仍處于之前的狀態(tài)
fail 更新已拋出異常,系統(tǒng)狀態(tài)已被破壞

check

測試所有加載的模塊以進行更新,如果有更新,則 apply 它們。

module.hot
  .check(autoApply)
  .then((outdatedModules) => {
    // 超時的模塊...
  })
  .catch((error) => {
    // 捕獲錯誤
  });

// or
import.meta.webpackHot
  .check(autoApply)
  .then((outdatedModules) => {
    // outdated modules...
  })
  .catch((error) => {
    // catch errors
  });

當(dāng)被調(diào)用時,傳遞給 apply 方法的 autoApply 參數(shù)可以是布爾值,也可以是 options,

apply

繼續(xù)更新進程(當(dāng) module.hot.status() === 'ready' 時)。

module.hot
  .apply(options)
  .then((outdatedModules) => {
    // 超時的模塊...
  })
  .catch((error) => {
    // 捕獲錯誤
  });

// or
import.meta.webpackHot
  .apply(options)
  .then((outdatedModules) => {
    // outdated modules...
  })
  .catch((error) => {
    // catch errors
  });

可選的 options 對象可以包含以下屬性:

  • ignoreUnaccepted (boolean): 忽略對不可接受的模塊所做的更改。
  • ignoreDeclined (boolean): 忽略對已拒絕的模塊所做的更改。
  • ignoreErrored (boolean): 忽略在接受處理程序、錯誤處理程序以及重新評估模塊時拋出的錯誤。
  • onDeclined (function(info)): 拒絕模塊的通知者。
  • onUnaccepted (function(info)): 不可接受的模塊的通知程序。
  • onAccepted (function(info)): 可接受模塊的通知者。
  • onDisposed (function(info)): 廢棄模塊的通知者。
  • onErrored (function(info)): 錯誤通知者。

info 參數(shù)將是一個包含以下某些值的對象:

{
  type: 'self-declined' | 'declined' |
        'unaccepted' | 'accepted' |
        'disposed' | 'accept-errored' |
        'self-accept-errored' | 'self-accept-error-handler-errored',
  moduleId: 4, // 有問題的模塊。
  dependencyId: 3, // 對于錯誤:擁有接受處理程序的模塊 ID。
  chain: [1, 2, 3, 4], // 對于拒絕/接受/不接受:傳播更新的 `chain`。
  parentId: 5, // 對于拒絕:下降的父模塊 ID。
  outdatedModules: [1, 2, 3, 4], // 對于接受:已過時且將被處置的模塊。
  outdatedDependencies: { // 對于接受:將處理更新的接受處理程序的位置。
    5: [4]
  },
  error: new Error(...), // 對于錯誤:拋出錯誤
  originalError: new Error(...) // 對于自我接受錯誤處理程序錯誤:
                                // 在錯誤處理程序嘗試處理該模塊之前,該模塊引發(fā)的錯誤。
}

addStatusHandler

注冊一個函數(shù)來監(jiān)聽 status 的變化。

module.hot.addStatusHandler((status) => {
  // 響應(yīng)當(dāng)前狀態(tài)...
});

// or
import.meta.webpackHot.addStatusHandler((status) => {
  // React to the current status...
});

請記住,當(dāng) status 處理函數(shù)返回一個 Promise 時,HMR 系統(tǒng)將會在繼續(xù)之前等待 Promise resolve。

removeStatusHandler

移除一個注冊的狀態(tài)處理函數(shù)。

module.hot.removeStatusHandler(callback);

// or
import.meta.webpackHot.removeStatusHandler(callback);

Further Reading

  • Concepts - Hot Module Replacement
  • Guides - Hot Module Replacement


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號