模块热替换
Rspack 提供了与 webpack 相同的接口来实现模块热替换。
如果你通过 HotModuleReplacementPlugin 启用了模块热替换,它的接口将暴露在 module.hot 和 import.meta.webpackHot 对象下。
注意,在 ESM 中请使用 import.meta.webpackHot 来代替 module.hot。
示例
要接受已加载模块的更新,先检查 HMR 接口是否可用:
更新如何传播
模块变化时,Rspack 会检查能否在不刷新页面的情况下应用更新。模块可以通过 accept() 接受自身更新,也可以由引用它的模块通过 accept(dependency, callback) 接受该依赖的更新。否则,更新会继续向引用它的模块传播。
例如,三个模块的引用关系如下:
当 button.js 发生变化时:
- 如果
view.js接受button.js的更新,Rspack 会替换button.js并调用view.js中的回调,不会替换view.js或app.js。 - 如果只有
app.js接受view.js的更新,Rspack 会替换button.js和view.js,再调用app.js中的回调。
如果一个模块被多处引用,每条受影响的引用路径都需要接受更新。默认情况下,如果某个模块拒绝更新,或更新传播到入口模块仍未被接受,本次更新就会中止。此时 dev server 可以回退为刷新整个页面。
模块 API
module.hot 和 import.meta.webpackHot 支持以下方法。
accept
接受(accept)给定依赖模块(dependencies)的更新,并触发回调函数(callback)来响应更新,除此之外,你可以附加一个可选的 error 处理程序:
当使用 ESM import 时,所有从 dependencies 中导入的符号都会自动更新。注意:依赖项字符串必须与 import 中的 from 字符串完全匹配。在某些情况下,甚至可以省略 callback。在 callback 中使用的 require() 在这里没有任何意义。
在使用 CommonJS 时,你应该通过 callback 中的 require() 手动更新依赖模块。省略 callback 在这里没有任何意义。
为 accept 设置错误处理器
(err, {moduleId, dependencyId}) => {}
err:当使用 ESM 依赖项时,回调函数在第二个参数中或在依赖项执行期间抛出的错误。moduleId:当前模块 id。dependencyId:(第一个)被更改依赖项的模块 id。
accept(self)
接受自身更新。
在此模块或依赖模块更新时,可以在不通知父依赖的情况下,对此模块处理和重新取值。如果此模块没有导出(或以其他方式更新的导出),这是有意义的。
当执行此模块(或依赖模块)抛出异常时,会触发 errorHandler。
为 accept 自身设置错误处理器
(err, {moduleId, module}) => {}
err:计算新版本时的错误。moduleId:当前模块 id。module:当前模块实例。module.hot:允许访问出错模块实例的 HMR API。一个常见的场景是再次自我接收(accept)。添加一个 dispose 处理程序来传递数据也是有意义的。注意,错误的模块可能已经部分执行,所以请确保不要进入不一致的状态。你可以使用module.hot.data存储部分状态。module.exports:可以被重载,但是要小心,因为属性名在生产模式下可能会被破坏。
decline
拒绝给定依赖模块(dependencies)的更新,使用 'decline' 方法强制更新失败。
将依赖模块标记为不可更新(not-update-able)。在处理「依赖的导出正在更新」或「尚未实现处理」时,这是有意义的。取决于你的 HMR 管理代码,此依赖模块(或其未接受的依赖模块)更新,通常会导致页面被完全重新加载。
decline(self)
拒绝自身更新。
将依赖模块标记为不可更新(not-update-able)。当此模块具有无法避免的副作用(side-effect),或者尚未对此模块进行 HMR 处理时,这是有意义的。取决于你的 HMR 管理代码,此依赖模块(或其未接受的依赖模块)更新,通常会导致页面被完全重新加载。
dispose(或 addDisposeHandler)
注册一个处理函数,在模块被替换前清理定时器、事件监听等资源。将需要保留的状态写入 data 参数,更新后的模块就能通过 import.meta.webpackHot.data(CommonJS 中为 module.hot.data)读取它。
下面的示例在更新时清除旧定时器,并从保存的数值继续计数:
invalidate
当前模块无法自行处理热更新时,可以调用 invalidate(),让引用它的模块处理这次更新。即使当前模块已经调用 accept() 接受自身更新,更新也会继续向引用它的模块传播,详见更新如何传播。
更新被接受并开始应用后,Rspack 会执行当前模块的 dispose 回调,清理旧模块的资源。
如果希望拒绝当前模块的更新,请使用 decline()。
在 idle 状态下调用时,将创建一个包含此模块的新 HMR 更新。HMR 将进入 ready 状态。
在 ready 或 prepare 状态下调用时,此模块将添加到当前 HMR 的更新中。
在 check 状态期间被调用时,如果有可用更新,则此模块将添加到更新中。如果没有可用的更新,它将创建一个新更新。HMR 将进入 ready 状态。
在 dispose 或 apply 状态下调用时,HMR 将在退出这些状态后将其拾取。
用例
无法处理依赖更新时
在 accept() 回调中,如果发现无法处理依赖的更新,可以调用 invalidate(),将更新交给引用当前模块的模块处理:
无法处理自身更新时
下面的模块接受自身更新,并在更新时比较新旧导出的字符串。如果字符串发生变化,就调用 invalidate(),让引用它的模块也参与更新:
- 在
dispose回调中,把需要保留的数据写入data对象。更新后的模块可以通过import.meta.webpackHot.data(CommonJS 中为module.hot.data)读取这些数据。 - 首次执行时还没有旧状态,因此这个值是
undefined。热更新后,如果旧版本没有保存任何数据,它就是一个空对象。 - 不要在模块每次执行时都调用
invalidate()。否则,一次热更新可能又触发下一次热更新,形成循环。
removeDisposeHandler
删除由 dispose 或 addDisposeHandler 添加的回调函数。
API 管理
status
获取当前模块热替换进程的状态。
check
检查是否有可用的热更新,并下载更新内容。调用时,HMR 必须处于 idle(空闲)状态。
autoApply 用于控制下载完成后是否立即应用更新:
- 不传或设为
false:只下载更新,随后进入ready状态。准备好应用更新时,再调用apply()。 - 设为
true:下载完成后立即应用更新。 - 传入选项对象:下载完成后立即应用更新,并将该对象作为
apply()的选项。
在 .then() 回调中可以获取本次更新涉及的模块 ID 数组;如果没有更新,则收到 null。下载或应用更新出错时,可以在 .catch() 中处理错误。
apply
继续更新进程(当 module.hot.status() === 'ready' 时)。
可选的 options 对象可以包含以下属性:
ignoreUnaccepted(boolean):忽略对不可接受的模块所做的更改。ignoreDeclined(boolean):忽略对已拒绝的模块所做的更改。ignoreErrored(boolean):忽略在接受处理程序、错误处理器以及重新评估模块时抛出的错误。onDeclined(function(info)):拒绝模块的通知者。onUnaccepted(function(info)):不可接受的模块的通知程序。onAccepted(function(info)):可接受模块的通知者。onDisposed(function(info)):废弃模块的通知者。onErrored(function(info)):错误通知者。
info 参数将是一个包含以下某些值的对象:
例如,app.js 调用 module.hot.accept('./button.js', callback) 后,如果 callback 在 button.js 更新时抛出错误,moduleId 是 app.js 的 ID,dependencyId 是 button.js 的 ID。
如果错误处理函数随后又抛出一个新错误,type 为 'accept-error-handler-errored',error 是这个新错误,originalError 则是 callback 抛出的错误。
addStatusHandler
注册一个函数来监听 status 的变化。
请记住,当 status 处理函数返回一个 Promise 时,直到 Promise 完成兑现,HMR 系统都将继续等待。
removeStatusHandler
移除一个注册的状态处理函数。
本页改编自 webpack 文档,遵循 CC BY 4.0,且已作修改。

