Notes on Webpack Hot Module Replacement
Tags: javascript, webpack, node
Published: 2019-04-08 06:22:09

---
title: Notes on Webpack Hot Module Replacement
published: true
tags: Javascript,Webpack,Node
canonical_url: https://kaspars.net/blog/webpack-hmr-vagrant-docker
---

It took me a few days to read through the relevant parts of the [webpack](https://github.com/webpack/webpack/tree/master/hot) and [webpack-dev-server](https://github.com/webpack/webpack-dev-server/tree/master/client-src/default) source code and figure out how to enable [Webpack Hot Module Replacement](https://webpack.js.org/concepts/hot-module-replacement/) for any project that doesn’t use the `webpack-dev-server` for serving the JS bundle. I wanted to use <abbr title="Hot Module Replacement">HMR</abbr> with Vagrant running Docker for a local WordPress theme development workflow.

## How Does It Work?

The main requirement for <abbr title="Hot Module Replacement">HMR</abbr> 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](https://github.com/webpack/webpack/blob/7f11210cffc58820b4cdd086934398306882c338/hot/dev-server.js#L51-L57) instead of the websocket messages sent by the `webpack-dev-server`.

Therefore, <abbr title="Hot Module Replacement">HMR</abbr> 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](https://github.com/webpack/webpack-dev-server/blob/ef5598440ebf6847ac02a2d44d7fec70efee0aa1/lib/Server.js#L569-L623) at `/sockjs-node` and [sends out a few messages to the listening clients](https://github.com/webpack/webpack-dev-server/blob/ef5598440ebf6847ac02a2d44d7fec70efee0aa1/lib/Server.js#L601-L607) 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](https://github.com/webpack/webpack-dev-server/blob/ef5598440ebf6847ac02a2d44d7fec70efee0aa1/client-src/default/index.js) `webpack-dev-server/client` which is [injected into your bundle automatically](https://github.com/webpack/webpack-dev-server/blob/3a7f7d543889707725e5d60fc21fe2de975d627d/bin/webpack-dev-server.js#L375) if you enable `--hot` and [don’t explicitly set](https://github.com/webpack/webpack-dev-server/blob/3a7f7d543889707725e5d60fc21fe2de975d627d/lib/util/addDevServerEntrypoints.js#L8-L39) `--inline` to `false`. The websocket client parses all incoming messages from the dev server and [triggers a native Node.js event](https://github.com/webpack/webpack-dev-server/blob/ab4eeb007283fdf3e8950ff1f3ff8150a4812061/client-src/default/index.js#L216):

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

and also [sends out a websocket message](https://github.com/webpack/webpack-dev-server/blob/ab4eeb007283fdf3e8950ff1f3ff8150a4812061/client-src/default/index.js#L219):

```js
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](https://github.com/webpack/webpack/blob/7f11210cffc58820b4cdd086934398306882c338/hot/dev-server.js) 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.

```js
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
  }
});
```