知海

热模块替换 API

webpackjsorg-mainAPI 参考

热模块替换 API

如果已通过 HotModuleReplacementPlugin 启用了热模块替换,其接口将暴露在 module.hot 属性以及 import.meta.webpackHot 属性下。请注意,只有 import.meta.webpackHot 可以在严格 ESM 中使用。

通常,用户会先检查该接口是否可访问,然后开始使用它。例如,下面展示了如何 accept 一个更新后的模块:

js 复制代码
if (module.hot) {
  module.hot.accept("./library.js", () => {
    // 使用更新后的 library 模块做一些事情……
  });
}

// 或
if (import.meta.webpackHot) {
  import.meta.webpackHot.accept("./library.js", () => {
    // 使用更新后的 library 模块做一些事情……
  });
}

以下方法均受支持……

模块 API

accept

接受给定 dependencies 的更新,并触发 callback 来响应这些更新;此外,你还可以附加一个可选的错误处理函数:

js 复制代码
module.hot.accept(
  dependencies, // 一个字符串或字符串数组
  callback, // 当依赖更新时触发的函数
  errorHandler, // (err, {moduleId, dependencyId}) => {}
);

// 或
import.meta.webpackHot.accept(
  dependencies, // 一个字符串或字符串数组
  callback, // 当依赖更新时触发的函数
  errorHandler, // (err, {moduleId, dependencyId}) => {}
);

当使用 ESM import 时,来自 dependencies 的所有导入符号都会自动更新。注意:依赖字符串必须与 import 中的 from 字符串完全匹配。在某些情况下,callback 甚至可以省略。这里的 callback 中使用 require() 没有意义。

当使用 CommonJS 时,你需要通过 callback 中的 require() 手动更新依赖。此时省略 callback 没有意义。

accept 的 errorHandler

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

  • err:第二个参数中的回调抛出的错误,或在 ESM 依赖执行期间抛出的错误。
  • moduleId:当前模块的 ID。
  • dependencyId:(第一个)发生变更的依赖的模块 ID。

accept(自身)

接受自身更新。

js 复制代码
module.hot.accept(
  errorHandler, // 处理评估新版本时发生的错误的函数
);

// 或
import.meta.webpackHot.accept(
  errorHandler, // 处理评估新版本时发生的错误的函数
);

当此模块或其依赖更新时,该模块可以被 dispose 并重新评估,而无需通知父模块。如果该模块没有导出(或导出以其他方式更新),这样做是有意义的。

当此模块(或依赖)的评估抛出异常时,将触发 errorHandler

自身 accept 的 errorHandler

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

  • err:评估新版本时发生的错误。
  • moduleId:当前模块的 ID。
  • module:当前模块实例。
    • module.hot:允许使用出错模块实例的 HMR API。常见场景是再次对自身 accept。添加 dispose 处理器以传递数据也是有意义的。请注意,出错的模块可能已经部分执行,因此请确保不要进入不一致的状态。你可以使用 module.hot.data 来存储部分状态。
    • module.exports:可以被覆盖,但要小心,因为在生产模式下属性名可能被混淆。

decline

拒绝给定 dependencies 的更新,强制更新以 'decline' 代码失败。

js 复制代码
module.hot.decline(
  dependencies, // 一个字符串或字符串数组
);

// 或
import.meta.webpackHot.decline(
  dependencies, // 一个字符串或字符串数组
);

将依赖标记为不可更新。当该依赖导出的变更无法被处理或尚未实现处理逻辑时,这很有意义。根据你的 HMR 管理代码,对这些依赖(或其未接受的依赖)进行更新通常会导致页面完全重新加载。

decline(自身)

拒绝自身更新。

js 复制代码
module.hot.decline();

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

将此模块标记为不可更新。当此模块具有不可逆的副作用,或尚未实现该模块的 HMR 处理逻辑时,这很有意义。根据你的 HMR 管理代码,对此模块(或未接受的依赖)进行更新通常会导致页面完全重新加载。

dispose(或 addDisposeHandler)

添加一个处理器,在当前模块代码被替换时执行。这应该用于移除你已声明或创建的任何持久资源。如果要将状态传递给更新后的模块,请将其添加到给定的 data 参数中。该对象将在更新后通过 module.hot.data 可用。

js 复制代码
module.hot.dispose((data) => {
  // 清理资源并将数据传递给更新后的模块……
});

// 或
import.meta.webpackHot.dispose((data) => {
  // 清理资源并将数据传递给更新后的模块……
});

invalidate

调用此方法将使当前模块失效,当 HMR 更新被应用时,该模块会被 dispose 并重新创建。这会像该模块的正常更新一样向上冒泡。invalidate 不能被此模块自身 accept。

当在 idle 状态下调用时,将创建一个包含此模块的新 HMR 更新。HMR 将进入 ready 状态。

当在 readyprepare 状态下调用时,此模块将被添加到当前的 HMR 更新中。

当在 check 状态下调用时,如果存在可用更新,此模块将被添加到更新中;如果没有可用更新,则会创建一个新更新。HMR 将进入 ready 状态。

当在 disposeapply 状态下调用时,HMR 会在退出这些状态后处理它。

使用场景

条件接受

一个模块可以接受某个依赖,但当依赖的变更无法处理时可以调用 invalidate

js 复制代码
import { processX, processY } from "anotherDep";
import { x, y } from "./dep";

const oldY = y;

processX(x);

export default processY(y);

module.hot.accept("./dep", () => {
  if (y !== oldY) {
    // 无法处理,向上冒泡
    module.hot.invalidate();
    return;
  }
  // 可以处理
  processX(x);
});

条件自身接受

一个模块可以自身 accept,但当归变更无法处理时可以自身 invalidate:

js 复制代码
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();
}

触发自定义 HMR 更新

{/* eslint-disable no-eval */}

js 复制代码
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();
}

T> 当调用 invalidate 时,dispose 处理器最终会被调用并填充 module.hot.data。如果未注册 dispose 处理器,则会向 module.hot.data 提供一个空对象。

W> 不要陷入 invalidate 循环,即反复调用 invalidate。这将导致堆栈溢出并使 HMR 进入 fail 状态。

removeDisposeHandler

移除通过 disposeaddDisposeHandler 添加的处理器。

js 复制代码
module.hot.removeDisposeHandler(callback);

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

管理 API

status

获取热模块替换过程的当前状态。

js 复制代码
module.hot.status(); // 将返回以下字符串之一……

// 或
import.meta.webpackHot.status();
状态 描述
idle 过程正在等待调用 check
check 过程正在检查更新
prepare 过程正在为更新做准备(例如下载更新后的模块)
ready 更新已准备就绪并可用
dispose 过程正在调用将被替换模块上的 dispose 处理器
apply 过程正在调用 accept 处理器并重新执行自身接受的模块
abort 更新被中止,但系统仍处于之前的状态
fail 更新抛出了异常,系统状态已受到破坏

check

测试所有已加载模块的更新,如果存在更新,则 apply 它们。

js 复制代码
module.hot
  .check(autoApply)
  .then((outdatedModules) => {
    // 过期的模块……
  })
  .catch((error) => {
    // 捕获错误
  });

// 或
import.meta.webpackHot
  .check(autoApply)
  .then((outdatedModules) => {
    // 过期的模块……
  })
  .catch((error) => {
    // 捕获错误
  });

autoApply 参数可以是布尔值,也可以是调用 apply 方法时传入的 options

apply

继续更新过程(只要 module.hot.status() === 'ready')。

js 复制代码
module.hot
  .apply(options)
  .then((outdatedModules) => {
    // 过期的模块……
  })
  .catch((error) => {
    // 捕获错误
  });

// 或
import.meta.webpackHot
  .apply(options)
  .then((outdatedModules) => {
    // 过期的模块……
  })
  .catch((error) => {
    // 捕获错误
  });

可选的 options 对象可以包含以下属性:

  • ignoreUnaccepted(布尔值):忽略对未接受模块所做的更改。
  • ignoreDeclined(布尔值):忽略对已拒绝模块所做的更改。
  • ignoreErrored(布尔值):忽略在 accept 处理器、错误处理器以及重新评估模块时抛出的错误。
  • onDeclined(函数(info)):已拒绝模块的通知器。
  • onUnaccepted(函数(info)):未接受模块的通知器。
  • onAccepted(函数(info)):已接受模块的通知器。
  • onDisposed(函数(info)):已处理(disposed)模块的通知器。
  • onErrored(函数(info)):错误的通知器。

info 参数将是一个包含以下部分值的对象:

ts 复制代码
{
  type: 'self-declined' | 'declined' |
        'unaccepted' | 'accepted' |
        'disposed' | 'accept-errored' |
        'self-accept-errored' | 'self-accept-error-handler-errored',
  moduleId: 4, // 涉及的模块。
  dependencyId: 3, // 对于错误:拥有 accept 处理器的模块 ID。
  chain: [1, 2, 3, 4], // 对于 declined/accepted/unaccepted:更新传播的链。
  parentId: 5, // 对于 declined:拒绝更新的父模块的模块 ID。
  outdatedModules: [1, 2, 3, 4], // 对于 accepted:已过期并将被 dispose 的模块。
  outdatedDependencies: { // 对于 accepted:将处理更新的 accept 处理器位置。
    5: [4]
  },
  error: new Error(...), // 对于错误:被抛出的错误。
  originalError: new Error(...) // 对于 self-accept-error-handler-errored:
                                // 错误处理器尝试处理之前模块抛出的错误。
}

addStatusHandler

注册一个函数来监听 status 的变化。

js 复制代码
module.hot.addStatusHandler((status) => {
  // 响应当前状态……
});

// 或
import.meta.webpackHot.addStatusHandler((status) => {
  // 响应当前状态……
});

请注意,当状态处理器返回一个 Promise 时,HMR 系统将等待该 Promise resolve 后再继续。

removeStatusHandler

移除已注册的状态处理器。

js 复制代码
module.hot.removeStatusHandler(callback);

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

帮助我们改进文档

发现翻译问题或内容错误?请告诉我们。