模块热替换(Hot Module Replacement)

模块热替换(HMR)在应用程序运行时调换、添加或删除模块,而无需完全重新加载。 这可以通过以下几种方式显著加快开发速度:

  • 保留在完全重新加载时会丢失的应用程序状态。
  • 只更新发生改变的内容,节省宝贵的开发时间。
  • 更快地调整样式 - 几乎可以与在浏览器调试器中改变样式相媲美。

工作方式

让我们从不同的角度观察,来准确理解 HMR 的工作原理......

在应用程序中

通过以下步骤,可以做到在应用程序中置换模块:

  1. 应用程序要求 HMR 运行时检查更新。
  2. 运行时异步下载更新并通知应用程序。
  3. 应用程序要求运行时应用更新。
  4. 运行时同步应用更新。

你可以设置 HMR 来自动执行此过程,或者你可以选择要求与用户交互来进行更新。

在编译器中

除了普通资源,编译器还需要发出“更新”以允许从先前的版本更新到新版本。“更新”包括两部分:

  1. 更新后的 清单(JSON)
  2. 一个或多个更新的块(JavaScript)

清单(manifest)包含新的编译哈希和所有更新的块的列表。这些块中的每一个都包含所有更新模块(或指示模块已被删除的标志)的新代码。

编译器确保模块 ID 和块 ID 在这些构建之间保持一致。它通常将这些 ID 存储在内存中(例如,使用webpack-dev-server),但也可以将它们存储在 JSON 文件中。

在模块中

HMR 是可选功能,仅影响包含 HMR 代码的模块。一个例子是通过样式加载器修补样式。为了使补丁工作,style-loader 实现了HMR接口; 当它通过 HMR 收到更新时,它会用新的样式替换旧样式。

同样,在模块中实现 HMR 接口时,您可以描述更新模块时应该发生的情况。 但是,在大多数情况下,并不是必须在每个模块中编写 HMR 代码。 如果模块没有HMR处理程序,则更新会冒泡。 这意味着单个处理程序可以更新完整的模块树。 如果更新了树中的单个模块,则会重新加载整个依赖关系集。

有关 module.hot 接口的详细信息,请参阅 HMR API 页面。

在运行时中

此处涉及的内容有点技术性......如果你对其内部实现不感兴趣,可以跳转到 HMR API 页面HMR 指南

模块系统运行时会通过额外的代码来跟踪模块的 parents (父项)和 children(子项)。在管理侧,运行时支持两种方法:checkapply

check 向更新清单发出 HTTP 请求。如果此请求失败,则没有可用的更新。如果成功,则将更新的块列表与当前已加载的块列表进行比较。对于每个已加载的块,下载其相应的更新块。所有模块更新都存储在运行时中。当所有更新块都下载完毕并可被应用时,运行时将切换到 ready 状态。

apply 方法将所有更新的模块标记为无效的。对于每个无效模块,在模块或其父模块中都需要有一个更新处理器。否则,无效标志会向上冒泡并使父模块无效。冒泡一直持续直到应用程序的入口点或具有更新处理器的模块(不管先到哪一个)。如果它从入口点冒出,则该处理过程失败。

之后,所有无效模块都被处理(通过 dispose 处理器)并卸载。然后更新当前哈希并调用所有 accept 处理器。运行时切换回 idle 状态,一切照常继续。

上手

HMR 可以作为 LiveReload 替代品用于开发过程中。 webpack-dev-server 支持热(hot)模式,在试图重新加载整个页面之前,该模式尝试使用 HMR 进行更新。有关详细信息,请参阅 模块热更新指南

与许多其他功能一样,webpack 的强大功能在于其可定制性。根据特定项目的需要,有许多配置 HMR 的方法。但是,对于大多数场景而言,webpack-dev-server 是一个好的选择,可以让你快速上手 HMR。