Hot Module Replacement
Hot Module Replacement (HMR) exchanges, adds, or removes modules while an application is running, without a full reload. This can significantly speed up development in a few ways:
- Retain application state which is lost during a full reload.
- Save valuable development time by only updating what's changed.
- Instantly update the browser when modifications are made to CSS/JS in the source code, which is almost comparable to changing styles directly in the browser's dev tools.
How It Works
Let's go through some different viewpoints to understand exactly how HMR works...
In the Application
The following steps allow modules to be swapped in and out of an application:
- The application asks the HMR runtime to check for updates.
- The runtime asynchronously downloads the updates and notifies the application.
- The application then asks the runtime to apply the updates.
- The runtime synchronously applies the updates.
You can set up HMR so that this process happens automatically, or you can choose to require user interaction for updates to occur.
In the Compiler
In addition to normal assets, the compiler needs to emit an "update" to allow updating from the previous version to the new version. The "update" consists of two parts:
- The updated manifest (JSON)
- One or more updated chunks (JavaScript)
The manifest lists the ids of the updated chunks, the removed chunks and the removed modules. Each update chunk contains the new code for its updated modules; the update for the runtime chunk also carries the new compilation hash.
The compiler ensures that module IDs and chunk IDs are consistent between these builds. A watching compiler keeps them in memory, which is all a dev server needs. To keep them consistent across separate compiler runs — a build on one machine that has to produce updates applicable to a build from another — write them to a JSON file with recordsPath.
In a Module
HMR is an opt-in feature that only affects modules containing HMR code. Styling is one example: webpack's built-in CSS support implements the HMR interface for you, so when a stylesheet update arrives it replaces the old styles with the new ones without a full reload.
Similarly, when implementing the HMR interface in a module, you can describe what should happen when the module is updated. However, 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: webpack walks from the changed module through the modules that imported it until it finds one that accepts the change. A single handler placed high enough can therefore cover a whole subtree.
What that handler covers is worth being precise about, because it decides which of your code runs again. Given top.js → middle.js → leaf.js, where only top.js accepts ./middle.js, editing leaf.js re-executes:
| Module | Re-executed? | Why |
|---|---|---|
leaf.js | yes | it is what changed |
middle.js | yes | it lies between the change and the handler |
a module middle.js imports but that didn't change | no | it is still in the module cache, and is reused as-is |
top.js | no | it is the module that accepted, so it keeps running |
top.js is not re-executed and its dispose handler does not run — only its accept callback is invoked, by which point its imported bindings already point at the new versions. Its own state therefore survives the update, which is exactly what makes it the right place to re-render from or to swap a value into a long-lived object. A module that instead wants to be re-executed on its own changes says so with a self-accept, and then it is disposed and re-run in place.
See the HMR API page for details on the module.hot interface, and the HMR guide for worked examples.
In the Runtime
Here things get a bit more technical... if you're not interested in the internals, feel free to jump to the HMR API page or HMR guide.
For the module system runtime, additional code is emitted to track module parents and children. On the management side, the runtime supports two methods: check and apply.
A check requests the update manifest. How it is fetched depends on the target — a browser build requests it over HTTP, a target: 'node' build reads it with fs.readFile — but the logic is the same: if the request fails, there is no update available. If it succeeds, 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 are stored in the runtime. When all update chunks have been downloaded and are ready to be applied, the runtime switches into the ready state.
The apply method flags all updated modules as invalid. For each invalid module, there needs to be an update handler in the module or in its parent(s). Otherwise, the invalid flag bubbles up and invalidates parent(s) as well. Each bubble continues until the app's entry point or a module with an update handler is reached (whichever comes first). If it bubbles up from an entry point, the process fails — the update is aborted with a message naming the file that nobody accepted, and the application is left running its old code. This is what a dev server turns into a full page reload.
Afterwards, all invalid modules are disposed (via the dispose handler) and unloaded. The current hash is then updated and all accept handlers are called. The runtime switches back to the idle state and everything continues as normal.
The runtime reports where it is in that sequence through status, which is how a custom HMR client knows when it is safe to ask for the next update.
On the wire
An update is a pair of files next to your normal output, named by output.hotUpdateMainFilename and output.hotUpdateChunkFilename:
[runtime].[fullhash].hot-update.json— the manifest, listing the chunks and modules the update touches.[id].[fullhash].hot-update.js— one per updated chunk, carrying the new module code.
The hash in those names is the hash of the build being updated from, not the one being updated to, which is what lets a client that has been idle ask for the update that follows the version it is actually running. A client whose build is too old to have a matching update file gets a failed request, falls back to a full reload, and picks up the current bundle.
Get Started
HMR can be used in development as a LiveReload replacement. webpack-dev-server supports a hot mode in which it tries to update with HMR before trying to reload the whole page. See the Hot Module Replacement guide for details.



