Skip to content
This repository was archived by the owner on Mar 22, 2019. It is now read-only.

hot module replacement.cn

e-cloud edited this page Jul 12, 2016 · 2 revisions

"Hot Module Replacement" (HMR) is a feature to inject updated modules into the active runtime. “热模块替换”("Hot Module Replacement")特性,是将更新了的模块注入到运行时代码

It's like LiveReload for every module. 就像是针对每个模块的动态重载

HMR is "opt-in", so you need to put some code at chosen points of your application. The dependencies are handled by the module system.

HMR 是 自主管理的,所以你需要放置一些管理代码在你的应用的某些位置来处理模块替换。依赖树的更新由模块系统来处理

I. e. you place your hot replacement code in module A. Module A requires module B and B requires C. If module C is updated, and module B cannot handle the update, modules B and C become outdated. Module A can handle the update and new modules B and C are injected.

譬如,模块A放置了热替换逻辑的代码。模块A依赖模块B,而模块B依赖模块C。如果模块C更新了,模块B不能处理更新,模块B和C都会变成过期模块。模块A可以处理该更新,则新的模块B和C会被注入。

Examples

Example 1: hot replace request handler of http server

#### 例1:http服务器的热替换请求处理器

var requestHandler = require("./handler.js");
var server = require("http").createServer();
server.on("request", requestHandler);
server.listen(8080);

// check if HMR is enabled
if(module.hot) {
	// accept update of dependency
	module.hot.accept("./handler.js", function() {
		// replace request handler of server
		server.removeListener("request", requestHandler);
		requestHandler = require("./handler.js");
		server.on("request", requestHandler);
	});
}

Example 2: hot replace css

例2:热替换 css

// addStyleTag(css: string) => HTMLStyleElement
var addStyleTag = require("./addStyleTag");

var element = addStyleTag(".rule { attr: name }");
module.exports = null;

// check if HMR is enabled
if(module.hot) {

	// accept itself
	module.hot.accept();

	// removeStyleTag(element: HTMLStyleElement) => void
	var removeStyleTag = require("./removeStyleTag");

	// dispose handler
	module.hot.dispose(function() {
		// revoke the side effect
		removeStyleTag(element);
	});
}

Example 3: Hot module replace with require.context

例3: 结合 require.context 的热替换

var context = require.context("./filesToLoad", false, /\.js$/); //filesToLoad is a directory with .js files
var modules = {};
context.keys().forEach(function (key) {
  var module = context(key);
  modules[key] = module;
  customReloadLogic(key, module, false);
})

if (module.hot) {
  module.hot.accept(context.id, function () {
    //You can't use context here. You _need_ to call require.context again to
    //get the new version. Otherwise you might get errors about using disposed
    //modules
    var reloadedContext = require.context("./filesToLoad", false, /\.js$/);
    //To find out what module was changed you just compare the result of the
    //require call with the version stored in the modules hash using strict
    //equality. Equal means it is unchanged.
    var changedModules = reloadedContext.keys()
      .map(function (key) {
        return [key, reloadedContext(key)];
      })
      .filter(function (reloadedModule) {
        return modules[reloadedModule[0]] !== reloadedModule[1];
      });
    changedModules.forEach(function (module) {
      modules[module[0]] = module[1];
      customReloadLogic(module[0], module[1], true);
    });
  });
}

function customReloadLogic(name, module, isReload) {
  console.log("module " + name + (isReload ? " re" : " ") + "loaded");
}

(see https://github.com/jauco/webpack-hot-module-reload-with-context-example for a full working version) (参见一个完整可运行的例子 https://github.com/jauco/webpack-hot-module-reload-with-context-example)

API

If HMR is enabled for a module module.hot is an object containing these properties: 如果某个模块开启了 HMR,module.hot是一个包含下列属性的对象:

accept

accept(dependencies: string[], callback: (updatedDependencies) => void) => void
accept(dependency: string, callback: () => void) => void

Accept code updates for the specified dependencies. The callback is called when dependencies were replaced. 接受特定依赖的代码更新。当依赖被替换后回调会被调用。

accept([errHandler]) => void

Accept code updates for this module without notification of parents. This should only be used if the module doesn't export anything. The errHandler can be used to handle errors that occur while loading the updated module. 接收此模块的更新而不用通知其父级模块。应仅用于当模块没有导出任何东西。errHandler 可用来处理加载更新模块时出现的错误。

decline

decline(dependencies: string[]) => void
decline(dependency: string) => void

Do not accept updates for the specified dependencies. If any dependencies is updated, the code update fails with code "decline". 不接受特定依赖的更新。如果有依赖被更新了,decline对应的代码更新无效

decline() => void

Flag the current module as not update-able. If updated the update code would fail with code "decline". 标记当前模块不可更新。即使更新了,也不会进行更新。

dispose/addDisposeHandler

dispose(callback: (data: object) => void) => void
addDisposeHandler(callback: (data: object) => void) => void

Add a one time handler, which is executed when the current module code is replaced. Here you should destroy/remove any persistent resource you have claimed/created. If you want to transfer state to the new module, add it to data object. The data will be available at module.hot.data on the new module. 添加一个一次性处理器,在当前模块被替换时执行。你需要销毁/移除任何已经声明/创建的持久化资源。如果你想转移状态到新的模块,将其添加到data对象。data在新的模块里可通过module.hot.data访问。

removeDisposeHandler

removeDisposeHandler(callback: (data: object) => void) => void

Remove a handler. 移除一个处理器

This can useful to add a temporary dispose handler. You could i. e. replace code while in the middle of a multi-step async function. 添加一个临时性处理器时可能有用。例如,你可以在一个多步骤的异步函数的中间替换代码

Management API

管理API

Also on the module.hot object. 同样位于 module.hot 对象中。

check

check([autoApply], callback: (err: Error, outdatedModules: Module[]) => void

Throws an exceptions if status() is not idle. 如果status() 返回的不是 idle,抛出一个异常

Check all currently loaded modules for updates and apply updates if found. 为更新模块去检查当前所有已加载的模块,然后找到了需要的就进行更新操作。

If no update was found, the callback is called with null. 如果没有找到需要更新的,回调函数会传入null

If autoApply is truthy the callback will be called with all modules that were disposed. apply() is automatically called with autoApply as options parameter. 如果autoApply 是 true,回调函数会传入所有被处理了的模块。apply()会被自动调用,并以autoApply 作为 options 参数

If autoApply is not set the callback will be called with all modules that will be disposed on apply(). 如果applyApply没有设置,所有会在apply()调用时被处理的模块将作为回调函数的参数。

apply

apply([options], callback: (err: Error, outdatedModules: Module[]) => void

If status() != "ready" it throws an error. 如果status() != "ready",会抛出错误。

Continue the update process. 继续更新流程。

options can be an object containing these options: options可以是一个包含以下属性的对象:

  • ignoreUnaccepted: If true the update process continues even if some modules are not accepted (and would bubble to the entry point).
  • ignoreUnaccepted: 如果为真,即使某些模块的更新没有被接收,更新流程会依然继续(并会冒泡到入口点)

status

status() => string

Return one of idle, check, watch, watch-delay, prepare, ready, dispose, apply, abort or fail. 返回下列其一:idle, check, watch, watch-delay, prepare, ready, dispose, apply, abort or fail.

idle

The HMR is waiting for your call the check(). When you call it the status will change to check. HMR 正等待check的调用。一旦其被调用,status会转为check

check

The HMR is checking for updates. If it doesn't find updates it will change back to idle. HMR 正在检测更新。如果其找不到更新,status会转回idle

If updates were found it will go through the steps prepare, dispose and apply. Than back to idle. 如果找到更新,status会依次经历prepare, disposeapply。然后回到idle状态。

watch

The HMR is in watch mode and will automatically be notified about changes. After the first change it will change to watch-delay and wait for a specified time to start the update process. Any change will reset the timeout, to accumulate more changes. When the update process is started it will go through the steps prepare, dispose and apply. Than back to watch or watch-delay if changes were detected while updating. HMR 处于监视模式,有变更会被自动通知。第一次更新后,status会转为wathc-delay并等待至指定时间开启更新流程。为了加速响应,任何变更都会重置 timeout。当更新流程开启后,status会经历prepare, disposeapply。然后回到watch或者watch-delay(当更新时再次检测到变更)

prepare

The HMR is prepare stuff for the update. This may means that it's downloading something. HMR 在为更新准备一些东西。这可能意味着正在下载某些模块。

ready

An update is available and prepared. Call apply() to continue. 一个更新已经准备好并可用。应调用apply()以继续更新流。

dispose

The HMR is calling the dispose handlers of modules that will be replaced. HMR 正在调用需要更新模块的处理器。

apply

The HMR is calling the accept handlers of the parents of replaced modules, than it requires the self accepted modules. HMR 正在调用被替换的模块的父节点的接受处理器,然后其调用自我管理接收的模块。

abort

A update cannot apply, but the system is still in a (old) consistent state. 某次更新无法应用,但系统仍处于旧有的一致的状态。

fail

A update has thrown an exception in the middle of the process, and the system is (maybe) in a inconsistent state. The system should be restarted. 更新过程中抛出了异常,系统可能处于不一致的状态。系统需要被重启。

status/addStatusHandler

status(callback: (status: string) => void) => void
addStatusHandler(callback: (status: string) => void) => void

Register a callback on status change. 注册一个状态变更时的回调函数。

removeStatusHandler

removeStatusHandler(callback: (status: string) => void) => void

Remove a registered status change handler. 移除一个已注册的状态变更处理器。

How to deal with ...

如何处理...

... a module without side effects (the standard case)

... 没有副作用的模块(标准情况)

Nothing to do in the module. Any parent can accept it. 该模块不需要做任何事。任何父级模块都可以接收其。

... a module with side effects

... 有副作用的模块

The module needs a dispose handler, then any parent can accept it. 该模块需要一个处理器,然后任何父级模块都可接收其。

... a module with only side effects and no exports

... 只有副作用没有导出值的模块

The module needs a dispose handler and can accept itself. No action is required in the parent. 该模块需要一个处理器并可以自行接收。父级模块不需要任何处理。

If the module's code is not in your hand, the parent can accept the module with some custom dispose logic. 如果模块的代码不受掌控,该模块的父级模块可以用一些自定义处理逻辑进行接收。

... the application entry module

... 应用的入口模块

As it doesn't export it can accept itself. A dispose handler can pass the application state on replacement. 既然其没有输出,其可以自行接收。一个处理器可以在替换时传递应用的状态。

... external module with not handleable side effects

... 有着无法处理的副作用的外部模块

In the nearest parent you decline the dependency. This makes your application throw on update. But as it's an external module, an update is very rare. 在最近的父级模块,否决该依赖。这会更新整个应用。但既然是一个外部模块,应该很少更新才对。

Clone this wiki locally