Hot Module Replacement (HMR) is a feature to swap out code while the application is running. It allows you to edit code while preserving the state of the application. This is especially useful for styling, where you usually just want to update styles without reloading the browser.
This, however, is only possible if the code provides special hooks to remove the previous code, undo all side-effects and inject the new code. Typical side-effects: are registering event listeners, storing data in objects, modifying global state.
Replacing CSS while the application is running, for instance, is an easy task since CSS is side-effect free per definition. In order to understand the internals of HMR, let's take a look at the style-loader:
The style-loader appends this special code to handle HMR (I've removed some code that is not important for this example):
if (module.hot) {
// When the styles change, update the <style> tags
module.hot.accept(loaderUtils.stringifyRequest(this, !!remainingRequest), function () {
var newContent = require(loaderUtils.stringifyRequest(this, !!remainingRequest));
update(newContent);
});
// When the module is disposed, remove the <style> tags
module.hot.dispose(function () {
update();
});
}
if (module.hot) {
checks, if HMR is enabled
module.hot.accept(<module identifier>, handler)
registers a handler to inject new code
module.hot.dispose(handler)
registers a handler to dispose old code
The update
function is hard to read, but it basically just adds new stylesheets to document.head
and removes unused stylesheets.
HMR in React is a bit more complex and has gone through some serious refactoring in the last couple of month. But the basic principle is that every exported component is wrapped inside a proxy. A proxy is an object that acts like another object. This can either be achieved by forwarding all functions to the "real" object or by using ES2015 proxies (which are obviously much more powerful). This way, the original object can easily be swapped out without updating any other components. I've described this a bit more in detail in another SO answer.
In order to make HMR work, there are some requirements to be met:
Your code requires hooks for module.hot.accept
and module.hot.dispose
to handle code updates. This is usually achieved through a loader (for instance, the style-loader) or a babel transformation (for instance, the babel-preset-react-hmre preset). Technically, you could also write these hooks in every module for yourself... maybe that's a good way to start learning the internals. If an updated module does not contain this code or is unable to handle the update, the update will be rejected and webpack will reload the browser window. This might look like HMR but is actually just a browser refresh.
You need some infrastructure on the client and the server to establish a WebSockets connection, to push new code to the client and to inform all outdated modules to handle the update. The most easy way to achieve this is by using the webpack-dev-server with webpack-dev-server --hot --inline
. There is no other code required. Especially don't add the HMR plugin or any webpack-dev-server stuff to the webpack.config.js
– it will all be enabled with --hot --inline
. There are more ways to configure this – which might actually be required depending on your setup. That's usually why this part is often so confusing for newcomers.
Webpack needs to run in watch mode since we only want to handle changed files. You don't need to add anything to the config when you're using the webpack-dev-server
or the webpack-dev-middleware
, it already puts the webpack compiler into watch-mode (only in case of lazy: false
, of course).