Understanding Hot Module Replacement (HMR) in Webpack: Concepts, Workflow, and Manual Implementation
This article explains the background, basic concepts, workflow, configuration, server‑client communication, runtime mechanics, and manual implementation of Hot Module Replacement (HMR) in Webpack, helping developers preserve application state while developing with live updates.
HMR Background
When using Webpack Dev Server , developers can focus on coding because the server watches source files, recompiles them, and refreshes the browser automatically. However, a full page reload loses the application state, which is why Webpack provides Hot Module Replacement (HMR) as a solution.
Simple Concept of HMR
HMR replaces only the changed modules in the browser without a full reload, preserving the current state and improving development efficiency compared to traditional live reload.
HMR Process Overview
The following steps illustrate how HMR works:
Webpack compiles the source files and writes them to memory.
The bundle server serves the files to the browser.
The HMR server sends updated modules to the HMR runtime.
The HMR runtime injects the new code into the browser.
The final bundle is the output file.
Getting Started with HMR
Enabling HMR is straightforward because it is built into Webpack. There are two ways:
Run webpack-dev-server --hot to start the server with HMR.
Add the following configuration to webpack.config.js :
// ./webpack.config.js
const webpack = require('webpack');
module.exports = {
// ...
devServer: {
// Enable HMR; fallback to live reload if not supported
hot: true,
},
plugins: [
// ...
// HMR plugin
new webpack.HotModuleReplacementPlugin()
]
};Server and Client in HMR
devServer Notifies the Browser of File Changes
Webpack‑dev‑server uses sockjs to create a WebSocket connection between the server and the browser. When compilation finishes, it sends the new hash to the client via sendStats :
sendStats(sockets, stats, force) {
const shouldEmit =
!force &&
stats &&
(!stats.errors || stats.errors.length === 0) &&
(!stats.warnings || stats.warnings.length === 0) &&
stats.assets &&
stats.assets.every(asset => !asset.emitted);
if (shouldEmit) {
this.sockWrite(sockets, 'still-ok');
return;
}
this.sockWrite(sockets, 'hash', stats.hash);
if (stats.errors.length > 0) {
this.sockWrite(sockets, 'errors', stats.errors);
} else if (stats.warnings.length > 0) {
this.sockWrite(sockets, 'warnings', stats.warnings);
} else {
this.sockWrite(sockets, 'ok');
}
}Client Reacts to Server Messages
The client caches the hash when it receives a hash message and triggers a reload when it receives an ok message. The reload strategy is chosen based on the configuration:
function reloadApp({ hotReload, hot, liveReload }, { isUnloading, currentHash }) {
if (isUnloading || !hotReload) {
return;
}
if (hot) {
log.info('App hot update...');
const hotEmitter = require('webpack/hot/emitter');
hotEmitter.emit('webpackHotUpdate', currentHash);
if (typeof self !== 'undefined' && self.window) {
self.postMessage(`webpackHotUpdate${currentHash}`, '*');
}
} else if (liveReload) {
let rootWindow = self;
const intervalId = self.setInterval(() => {
if (rootWindow.location.protocol !== 'about:') {
applyReload(rootWindow, intervalId);
} else {
rootWindow = rootWindow.parent;
if (rootWindow.parent === rootWindow) {
applyReload(rootWindow, intervalId);
}
}
});
}
}
function applyReload(rootWindow, intervalId) {
clearInterval(intervalId);
log.info('App updated. Reloading...');
rootWindow.location.reload();
}How Webpack Requests Updated Modules
The HMR server listens for webpackHotUpdate messages, the runtime checks for updates using hotDownloadUpdateChunk and hotDownloadManifest , fetches the updated chunks via JSONP or AJAX, and then applies them.
HMR Runtime Hot Update Process
The core of the update is the hotApply method, which:
Identifies outdatedModules and outdatedDependencies .
Removes the old modules from the cache.
Inserts the new modules into the module map.
// remove module from cache
delete installedModules[moduleId];
// no need to call dispose handler
delete outdatedDependencies[moduleId]; for (moduleId in appliedUpdate) {
if (Object.prototype.hasOwnProperty.call(appliedUpdate, moduleId)) {
modules[moduleId] = appliedUpdate[moduleId];
}
}After this, the application can retrieve the latest module code.
HMR Hot Members
HotModuleReplacementPlugin
The plugin provides the HMR API, most importantly module.hot.accept , which registers a callback to run when a specific module updates.
Manual JS Module Replacement Example
Assume src/main.js imports a child module. By using module.hot.accept , we can replace the DOM element created by the child module without a full reload:
// ./src/main.js
import createChild from './child';
const child = createChild();
document.body.appendChild(child);
let lastChild = child;
module.hot.accept('./child', () => {
const value = lastChild.innerHTML;
document.body.removeChild(lastChild);
lastChild = createChild();
lastChild.innerHTML = value;
document.body.appendChild(lastChild);
});If an error occurs during HMR, switching the configuration from hot: true to hotOnly: true can prevent the entire HMR process from breaking.
Conclusion
This article deepens the understanding of Webpack's HMR mechanism, demonstrates how to enable it, explains the server‑client communication, runtime update logic, and shows a manual implementation for cases where framework‑level HMR is not available.
ByteFE
Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.