Frontend Development 14 min read

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.

WeDoctor Frontend Technology
WeDoctor Frontend Technology
WeDoctor Frontend Technology
Mastering Webpack HMR: From Basics to Custom Module Hot Updates

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-server

with the

--hot

flag.

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

ok

message. 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/emitter

to apply the new hash; otherwise it falls back to

applyReload

and calls

location.reload

.

Webpack Requests Latest Module Code via Hash

The dev‑server listens for

webpackHotUpdate

, the HMR runtime checks for updates, and uses

hotDownloadUpdateChunk

and

hotDownloadManifest

to 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.accept

for handling updates of arbitrary JavaScript modules.

Principle of module.hot.accept

Calling

module.hot.accept

registers 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.accept

to 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.

JavaScriptfrontend developmentWebpackDev Serverhot module replacement
WeDoctor Frontend Technology
Written by

WeDoctor Frontend Technology

Official WeDoctor Group frontend public account, sharing original tech articles, events, job postings, and occasional daily updates from our tech team.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.