Skip to content

热模块替换

¥Hot Module Replacement

热模块替换 (HMR) 是一种在正在运行的应用内更新 javascript 模块的方法。这减少了开发时的反馈周期,因此你可以更快地查看和测试更改。在 Meteor 的实现中,应用甚至可以在构建完成之前进行更新。

¥Hot Module Replacement (HMR) is a method of updating javascript modules within a running application. This reduces the feedback cycle while developing, so you can view and test changes quicker. In Meteor's implementation, the app can be updated before the build has even finished.

提示

hot-module-replacement 包是在 Meteor 2.0 中引入的

¥hot-module-replacement package was introduced in Meteor 2.0

要为应用启用 HMR,它应该使用 hot-module-replacement 包。HMR 目前不支持软件包,但软件包可以依赖 hot-module-replacement 软件包来确保访问热门 API。当无法使用 HMR 接受更改时,Meteor 使用热代码推送来更新应用,这在未使用 HMR 时通常是这样做的。

¥To enable HMR for an app, it should use the hot-module-replacement package. HMR is currently not supported for packages, but packages can depend on the hot-module-replacement package to ensure access to the hot API. When a change can not be accepted with HMR, Meteor uses hot code push to update the app, as is normally done when HMR is not used.

HMR 目前支持现代 Web 架构。它在其他架构和生产中始终被禁用。

¥HMR currently supports the modern web architecture. It is always disabled in other architectures and in production.

应用如何更新

¥How the app is updated

在重建支持的架构时,Meteor 会检查哪些文件被修改了,并将修改后的文件发送到客户端。然后客户端使用此过程:

¥While rebuilding a supported architecture, Meteor checks which files were modified, and sends the modified files to the client. The client then uses this process:

  1. 它检查修改后的模块是否接受或拒绝更新。如果模块不执行任何操作,Meteor 将查看导入它的模块以查看它们是否接受或拒绝更新,然后查看导入这些模块的模块,依此类推。如果它遵循的所有路径都指向接受更新的模块,则它使用 HMR。否则,它会使用热代码推送。

    ¥It checks if the modified module accepts or declines updates. If the module does neither, Meteor looks at the modules that imported it to see if they accept or decline the update, and then the modules that import those, and so on. If all paths it followed leads to modules that accept the update, it uses HMR. Otherwise, it uses hot code push.

  2. 许多 js 模块执行的操作会对应用产生长期影响。它们可能会创建 Tracker 自动运行、注册事件监听器或具有已渲染的 UI 组件。模块可以注册 dispose 处理程序来清理模块的旧版本,使其不再影响应用。在更新过程的这一点上,将调用那些处置处理程序。

    ¥Many js modules do things that have a long term effect on the app. They might create Tracker autoruns, register event listeners, or have UI components that have been rendered. Modules can register dispose handlers to clean up the old version of a module so it no longer affects the app. At this point in the update process, those dispose handlers are called.

  3. Meteor 运行模块的新版本、接受更新的模块以及它们之间的所有模块。这可确保使用模块的新导出。因此,通常唯一接受更新的模块是没有导出的模块,或者有另一种方式更新父模块使用的导出。此时,这两个模块的两个版本正在运行,但如果旧版本被正确处置,则不再使用。

    ¥Meteor runs the new version of the module, the modules that accepted the update, and all of the modules between them. This ensures the module's new exports are used. Because of this, usually the only modules that accept updates are the ones that have no exports or that have another way of updating the exports used by parent modules. At this point, there are two versions of these modules running, but the old version is no longer in use if it was disposed properly.

为了使 HMR 正常工作,正确的模块必须接受或拒绝更新,并且必须编写处置处理程序。幸运的是,大多数应用都不需要手动执行任何操作。相反,你可以使用集成来自动检测可以接受更新的模块,以及如何清理特定类型的模块。例如,React 集成在 Meteor 应用中默认启用,并且能够自动更新 React 组件。

¥For HMR to work properly, the correct modules have to accept or decline updates, and dispose handlers have to be written. Fortunately, most apps do not need to manually do either. Instead, you can use integrations that automatically detect modules that can accept updates, and how to clean up specific types of modules. For example, the React integration is enabled by default in Meteor apps, and is able to automatically update React components.

API

hot-module-replacement 包提供了一个 API 来控制如何使用 HMR。API 在 module.hot 上可用。由于 API 并不总是可用(例如,在生产中或 HMR 不支持的架构中),因此代码应确保在使用 module.hot 之前已定义:

¥The hot-module-replacement package provides an API to control how HMR is used. The API is available at module.hot. Since the API isn't always available (for example, in production or in architectures not supported by HMR), the code should make sure module.hot is defined before using it:

js
if (module.hot) {
  module.hot.accept();
}

在未来的 Meteor 版本中,使用 if 语句将允许压缩器在为生产进行压缩时删除此块。

¥In a future Meteor version, using the if statement will allow minifiers to remove this block when minifying for production.

使用 module.hot api 的包应使用 hot-module-reload 包以确保访问 API。

¥Packages that use the module.hot api should use the hot-module-reload package to ensure access to the API.

module.hot.accept

Client only

Summary:

Accept updates to this module. Also applies to its dependencies, as long as the other modules that import the dependencies also accept updates.

HMR 重新运行导入修改后的模块的文件,因此文件使用新的导出。除了配置哪些模块可以使用 HMR 更新之外,module.hot.accept() 还控制重新运行的文件数量。Meteor 重新运行导入修改后的模块的文件、导入这些文件的文件等等,直到到达接受更新的模块。因此,通常唯一接受更新的模块是没有导出的模块,或者有另一种方式更新其父模块使用的导出。

¥HMR reruns files that import the modified modules so the files use the new exports. In addition to configuring which modules can be updated with HMR, module.hot.accept() also controls how many files are re-ran. Meteor re-runs the files that import the modified modules, the files that import those files, and so on, until it reaches the modules that accepted the update. Because of this, usually the only modules that accept updates are ones that have no exports, are have another way of updating the exports used by its parent modules.

module.hot.decline

Client only

Summary:

Disable updating this module or its dependencies with HMR. Hot code push will be used instead. Can not be overridden by calling module.hot.accept later.

module.hot.dispose

Client only

Summary:

Add a call back to clean up the module before replacing it

Arguments:

Source code
NameTypeDescriptionRequired
callbackmodule.hot.DisposeFunction

Called before replacing the old module.

Yes

当不再使用模块的这个实例时,将运行回调。主要用途是确保模块实例不再影响应用。下面是我们停止 Tracker 计算的示例:

¥The call back is run when this instance of the module will no longer be used. The main use is making sure the instance of the module no longer affects the app. Here is an example where we stop a Tracker computation:

js
import { setLocale } from '/imports/utils/locale';

const computation = Tracker.autorun(() => {
  const user = Meteor.user();

  if (user && user.locale) {
    setLocale(user.locale);
  }
});

if (module.hot) {
  module.hot.dispose(() => {
    computation.stop();
  });
}

如果它没有停止计算,则每次为 HMR 重新运行模块时,都会有额外的计算。这可能会导致意外行为,特别是如果我们修改了计算函数。

¥If it did not stop the computation, each time the module is reran for HMR, there would be an additional computation. This can lead to unexpected behavior, especially if we've modified the computation function.

回调接收一个数据对象,该对象可以修改以存储模块新实例的信息。这可用于保存类实例、状态或其他数据。例如,此模块将保留颜色变量的值:

¥The callback receives a data object that can be mutated to store information for the new instance of the module. This can be used to preserve class instances, state, or other data. For example, this module will preserve the value of the color variable:

js

let color = 'blue';

export function getColor() {
  return color;
}

export function changeColor(newColor) {
  color = newColor;
}

if (module.hot) {
  if (module.hot.data) {
    color = module.hot.data.color;
  }

  module.hot.dispose(data => {
    data.color = color;
  });
}

当模块首次运行时,module.hot.data 为空,因此它将 color 设置为蓝色。最终,应用会调用 changeColor 并将颜色设置为 purple。如果模块重新运行,模块的旧实例将颜色存储在 data.color 中。新实例从 module.hot.data.color 检索它,并为下次重新运行时注册一个新的 dispose 处理程序。

¥When the module first runs, module.hot.data is null, so it leaves color set to blue. Eventually the app calls changeColor and sets the color to purple. If the module is re-ran, the old instance of the module stores the color in data.color. The new instance retrieves it from module.hot.data.color, and registers a new dispose handler for the next time it is re-run.

module.hot.data

Client only

Summary:

Defaults to null. When the module is replaced, this is set to the object passed to dispose handlers.

module.hot.onRequire

Client only

Summary:

Add callbacks to run before and after a module is required

Arguments:

Source code
NameTypeDescriptionRequired
callbacksObject

Can have before and after methods, called before a module is required, and after it finished being evaluated

Yes

一些 HMR 集成使用它来检测可以使用 HMR 自动更新的文件,并处理清理旧模块实例和迁移状态。例如,React Fast Refresh 使用它来查找仅导出 React 组件的模块,并让这些模块接受更新。

¥This is used by some HMR integrations to detect files that can be automatically updated with HMR, and handle cleaning up the old module instances and migrating state. For example, React Fast Refresh uses this to find the modules that only export React components, and have those modules accept updates.

js
if (module.hot) {
  module.hot.onRequire({
    // requiredModule is the same object available in the
    // required module as `module`, including access to `module.hot`
    // and `module.exports`
    //
    // parentId is a string with the path of the module that
    // imported requiredModule.
    before(requiredModule, parentId) {
      // Anything returned here is available to the
      // after callback as the data parameter.
      return {
        importedBy: parentId,
        previouslyEvaluated: !requiredModule.loaded
      }
    },
    after(requiredModule, data) {
      if (!data.previouslyEvaluated) {
        console.log(`Finished evaluating ${requiredModule.id}`);
        console.log(`It was imported by ${data.importedBy}`);
        console.log(`Its exports are ${requiredModule.exports}`);
      }

      // canAcceptUpdates would look at the exports, and maybe the imports
      // to check if this module can safely be updated with HMR.
      if (requiredModule.hot && canAcceptUpdates(requiredModule)) {
        requiredModule.hot.accept();
      }
    }
  });
}