Notes on Webpack Hot Module Replacement

It took me a few days to read through the relevant parts of the webpack and webpack-dev-server source code and figure out how to enable Webpack Hot Module Replacement for any project that doesn’t use the webpack-dev-server for serving the JS bundle. I wanted to use HMR with Vagrant running Docker for a local WordPress theme development workflow.

How Does It Work?

The main requirement for HMR is for the browser to receive notifications when a new bundle file is built as you edit the source files. Unfortunately, the default notification logic in webpack/dev-server.js relies on the native Node.js events instead of the websocket messages sent by the webpack-dev-server.

Therefore, HMR will never work if the bundle file is not server by the same webpack-dev-server which also watches for the file changes and triggers the builds.

Native Node Events and Websockets

Fortunately, the webpack-dev-server also starts a websocket server at /sockjs-node and sends out a few messages to the listening clients to indicate that it will report changes related to HMR.

So your bundled script needs only two things to work with the data coming from the websocket server:

  1. a websocket client that acts on the incoming messages, and
  2. a hot module replacement logic that can apply the incoming code patches.

The webpack-dev-server comes with a websocket client webpack-dev-server/client which is injected into your bundle automatically if you enable --hot and don’t explicitly set --inline to false. The websocket client parses all incoming messages from the dev server and triggers a native Node.js event:

const hotEmitter = require('webpack/hot/emitter');
hotEmitter.emit('webpackHotUpdate', currentHash);

and also sends out a websocket message:

if (typeof self !== 'undefined' && self.window) {
  // broadcast update to window
  self.postMessage('webpackHotUpdate${currentHash}', '*');
}

However, only the native Node.js event webpackHotUpdate is used by the HMR client side logic in webpack/hot/dev-server to apply the patch. This is why your bundle will never receive notifications about HMR events if it’s not served by the webpack-dev-server because native Node events are not supported outside the Node environment.

How to Fix It?

Clone the HMR client side logic in webpack/hot/dev-server and make it listen for webpackHotUpdate messages on a websocket instead of the native node events.

window.addEventListener('message', (e) => {
  if (typeof e.data === 'string' && e.data.includes('webpackHotUpdate')) {
    var hmrHash = e.data.replace('webpackHotUpdate', '');
    // Run check() from webpack/hot/dev-server.js
  }
});

Leave a Reply