Mastering Webpack HMR: From Basics to Custom Module Hot Updates
This article explains the background, concepts, workflow, and hands‑on setup of Webpack’s Hot Module Replacement (HMR), detailing server‑client communication, runtime mechanics, and how to manually implement module hot‑updates without a framework, complete with code examples and configuration tips.
HMR Background
When using
Webpack Dev Server, developers can focus on coding because it watches file changes, recompiles, and automatically refreshes the browser. However, a full page reload loses application state, so Webpack provides Hot Module Replacement (HMR) to update modules without refreshing.
Simple Concept of HMR
HMR re‑packs changed code and sends the new modules to the browser, which replaces the old ones without a full reload, preserving state and improving development efficiency compared with traditional live reload. The following diagram illustrates the process.
Webpack Compile: watch files and write to memory.
Bundle Server: serve files to the browser.
HMR Server: output hot‑updated files to the HMR runtime.
HMR Runtime: inject files into the browser memory.
Bundle: output built files.
Getting Started with HMR
Enabling HMR is straightforward because it is built into Webpack. There are two ways:
Run
webpack-dev-serverwith the
--hotflag.
Add the following configuration to
webpack.config.js:
<code>// ./webpack.config.js
const webpack = require('webpack');
module.exports = {
// ...
devServer: {
hot: true,
},
plugins: [
// ...
new webpack.HotModuleReplacementPlugin()
]
};
</code>Server and Client in HMR
devServer Notifies Browser of File Changes
Webpack‑dev‑server creates a WebSocket connection using
sockjs. When compilation finishes, it sends the new hash or status messages (hash, errors, warnings, ok) to the client.
<code>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');
}
}
</code>Client Responds to Server Messages
The client caches the hash and triggers a reload when it receives an
okmessage. An illustration:
Reload Strategy Selection
<code>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();
}
</code>If HMR is enabled, the client uses
webpack/hot/emitterto apply the new hash; otherwise it falls back to
applyReloadand calls
location.reload.
Webpack Requests Latest Module Code via Hash
The dev‑server listens for
webpackHotUpdate, the HMR runtime checks for updates, and uses
hotDownloadUpdateChunkand
hotDownloadManifestto fetch updated chunks via JSONP or AJAX. The updated code is then processed by the runtime.
HMR Runtime Performs Hot Updates
The core step is
hotApply, which identifies outdated modules and dependencies, removes them from the cache, and inserts the new modules.
<code>// remove module from cache
delete installedModules[moduleId];
delete outdatedDependencies[moduleId];
// add new modules
for (moduleId in appliedUpdate) {
if (Object.prototype.hasOwnProperty.call(appliedUpdate, moduleId)) {
modules[moduleId] = appliedUpdate[moduleId];
}
}
</code>Hot Members in HMR
HotModuleReplacementPlugin
The plugin provides APIs such as
module.hot.acceptfor handling updates of arbitrary JavaScript modules.
Principle of module.hot.accept
Calling
module.hot.acceptregisters a callback in
hot._acceptedDependencies. When the watched module changes, the runtime invokes the callback.
<code>accept: function (dep, callback, errorHandler) {
if (dep === undefined) hot._selfAccepted = true;
else if (typeof dep === "function") hot._selfAccepted = dep;
else if (typeof dep === "object" && dep !== null) {
for (var i = 0; i < dep.length; i++) {
hot._acceptedDependencies[dep[i]] = callback || function () {};
hot._acceptedErrorHandlers[dep[i]] = errorHandler;
}
} else {
hot._acceptedDependencies[dep] = callback || function () {};
hot._acceptedErrorHandlers[dep] = errorHandler;
}
},
</code>Implementing JS Module Replacement
In
main.js, after importing a child module, you can use
module.hot.acceptto replace the DOM element while preserving its state.
<code>// ./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(child);
lastChild = createChild();
lastChild.innerHTML = value;
document.body.appendChild(lastChild);
});
</code>Conclusion
This article aims to deepen understanding of HMR and help solve development scenarios such as implementing hot updates without a framework.
WeDoctor Frontend Technology
Official WeDoctor Group frontend public account, sharing original tech articles, events, job postings, and occasional daily updates from our tech 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.