Hot Module Replacement with webpack

Note that Hot Module Replacement (HMR) is still an experimental feature.

介绍

Hot Module Replacement (HMR) exchanges, adds, or removes modules while an application is running without a page reload.

Prerequirements

如何工作?

从应用视图

The app code asks the HMR runtime to check for updates. The HMR runtime downloads the updates (async) and tell the app code that an update is available. The app code asks the HMR runtime to apply updates. The HMR runtime applies the update (sync). The app code may or may not require user interaction in this process (you decide).

从编译(webpack) 视图

In addition to the normal assets the compiler needs to emit the "Update" to allow updating from previous version to this version. The "Update" contains two parts:

  1. the update manifest (json)
  2. one or multiple update chunks (js)

The manifest contains the new compilation hash and a list of all update chunks (2.).

The update chunks contains code for all updated modules in this chunk (or a flag if a module was removed).

The compiler additionally makes sure that module and chunk ids are consistent between these builds. It uses a "records" json file to store them between builds (or it stores them in memory).

从模块视图

HMR is a opt-in feature, so it only affects modules that contains HMR code. The documentation describes the API that is available in modules. In general the module developer writes handlers that are called when a dependency of this module is updated. He can also write a handler that are called when this module is updated.

In most cases it's not mandatory to write HMR code in every module. If a module has no HMR handlers the update bubbles up. This means a single handler can handle an update to a complete module tree. If a single module in this tree is updated, the complete module tree is reloaded (only reloaded not transferred).

从HMR运行视图(technical)

For the module system runtime is additional code emitted to track module parents and children.

On the management side the runtime supports two methods: check and apply.

A check does a HTTP request to the update manifest. When this request fails, there is no update available. Otherwise the list of updated chunks is compared to the list of currently loaded chunks. For each loaded chunk the corresponding update chunk is downloaded. All module updates as stored in the runtime as update. The runtime switches into the ready state, meaning an update has been downloaded and is ready to be applied.

For each new chunk request in the ready state the update chunk is also downloaded.

The apply method flags all updated modules as invalid. For each invalid module there need to be a update handler in the module or update handlers in every parent. Else the invalid bundles up and mark all parents as invalid too. This process continues until no more "bubble up" occurs. If it bubbles up from an entry point the process fails.

Now all invalid modules are disposed (dispose handler) and unloaded. Then the current hash is updated and all "accept" handlers are called. The runtime switches back to the idle state and everything continues as normal.

生成的文件 (technical)

The left side represents the initial compiler pass. The right side represents an additional pass with module 4 and 9 updated.

generated update chunks

我用它可以做啥?

You can use it in development as LiveReload replacement. Actually the webpack-dev-server supports a hot mode which try to update with HMR before trying to reload the whole page. You only need to add the webpack/hot/dev-server entry point and call the dev-server with --hot.

webpack/hot/dev-server reloads the entire page after the HMR update fails. If you want to reload the page on your own, you can add webpack/hot/only-dev-server to the entry point instead.

You can also use it in production as update mechanisms. Here you need to write you own management code that integrates HMR with your app.

Some loaders already generate modules that are hot-updateable. I. e. the style-loader can exchange the stylesheet. You don't need to do something special.

用它需要什么?

A module can only be updated if you "accept" it. So you need to module.hot.accept the module in the parents or the parents of the parents... I. e. a Router is a good place or a subview.

If you only want to use it with the webpack-dev-server, just add webpack/hot/dev-server as entry point. Else you need some HMR management code that calls check and apply.

You need to enable records in the Compiler to track module id between processes. (watch mode and the webpack-dev-server keep records in memory, so you don't need it for development)

You need to enable HMR in the Compiler to let it add the HMR runtime.

什么使它如此酷?

  • It's LiveReload but for every module kind.
  • You can use it in production.
  • The updates respect your Code Splitting and only download updates for the used parts of your app.
  • You can use in for a part of your application and it doesn't affect other modules
  • If HMR is disabled all HMR code is removed by the compiler (wrap it in if(module.hot)

警告

  • It's experimental and not tested so well.
  • Expect some bugs
  • Theoretically usable in production, but it maybe too early to use it for something serious
  • The module ids need to be tracked between compilations so you need to store them (records)
  • Optimizer cannot optimize module ids anymore after the first compilation. A bit impact on bundle size.
  • HMR runtime code increase bundle size.
  • For production usage additional testing is required to test the HMR handlers. This could be pretty difficult.

教程

To use hot code replacement with webpack you need four things:

  • records (--records-path, recordsPath: ...)
  • global enable hot code replacement (HotModuleReplacementPlugin)
  • hot replacement code in your code module.hot.accept
  • hot replacement management code in your code module.hot.check, module.hot.apply

A small testcase:

/* style.css */
body {
    background: red;
}
/* entry.js */
require("./style.css");
document.write("<input type='text' />");

That's enough to use hot code replacement with the dev-server.

npm install webpack webpack-dev-server -g
npm install webpack css-loader style-loader
webpack-dev-server ./entry --hot --inline --module-bind "css=style\!css"

The dev server provides in memory records, which is good for development.

The --hot switch enables hot code replacement.

This adds the HotModuleReplacementPlugin. Don't add it to your webpack.config.js if you are using the CLI.

There is special management code for the dev-server at webpack/hot/dev-server, which is automatically added by --inline. (You don't have to add it to your webpack.config.js)

The style-loader already includes hot replacement code.

If you visit http://localhost:8080/bundle you should see the page with a red background and a input box. Type some text into the input box and edit style.css to have another background color.

Voilà... The background updates but without full page refresh. Text and selection in the input box should stay.

Read more about how to write you own hot replacement (management) code: [[hot module replacement]]

Check the example-app for a demo without coding. (Note: It's a bit old, so don't look at the source code, because the HMR API changed a bit in between)