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:
- a websocket client that acts on the incoming messages, and
- 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
}
});