Webpack HMR Tutorial

November 19th, 2015
Updated: February 2nd, 2018

Struggling to set up HMR with Webpack?

Are you finding the Webpack documentation to be lacking - particularly in the HMR section? Does it seem like they've left out some important parts?

If so, read on! We'll fill in the gaps together.

By the way, HMR stands for "Hot Module Replacement", sometimes called "hot module swapping". It's a Webpack feature that updates your JavaScript in-place without a browser refresh - whenever you make code changes. I love it because it enables a very fast developer workflow.

HMR Myths

First let's get a couple myths about HMR out of the way.

Myth: HMR is only for React

Not true - it usually just takes 3 lines of code to enable HMR in arbitrary JavaScript. This Tutorial doesn't even mention React - I'll cover HMR in React in a subsequent post.

Myth: HMR is only for 'advanced' users

Not true. Without good documentation, HMR is difficult for beginners and advanced users alike. With the right information (this article), HMR can be enabled with very minor changes to config/code.

Myth: HMR is the same as 'live reload'

Not true. Live reload is when the browser automatically refreshes the page whenever you make a code change. HMR is faster because it updates code in-place without refreshing.

2 Steps to HMR Success

There are 2 steps to enabling HMR: config changes and code changes.

1. Config / Project Setup

There are 3 ways to configure your project for HMR. I go into each way in detail in Part 1. Pick the way that's right for you and either use the provided github repository, or follow the directions to adapt your existing code base.

2. JavaScript / HMR API

To get HMR to work you have to add a little bit of extra code to your JavaScript. In the simplest case this is just 3 lines of code. I'll show you everything you need to add for your use-case in Part 2.

Part 1: Configuring Your Project

There are exactly 3 ways that you can configure your HMR project. Let's go through each one. I'll help you pick which one to use in the next section.

1. webpack-dev-server CLI

Run the webpack-dev-server on the command-line. With this approach you don't need to change your webpack.config.js file, so it's the simplest.

2. webpack-dev-server API

Run WebpackDevServer from a node.js script. Requires some additions to your webpack.config.js.

3. webpack-hot-middleware w/ express

webpack-hot-middleware is used for running a webpack dev server with HMR inside an express server.

The Non-Way

If you are used to running the webpack CLI or the Webpack API (without express or webpack-dev-server), then you'll have to change how you do things a little bit. HMR requires a server to work. To find out why, read my understanding HMR post.

But how do I choose?

Follow this simple guide to pick one of the 3 ways above.

  • If you are using a task runner like grunt or gulp you'll want to use the webpack-dev-server API. You can run the server from one of your gulp/grunt tasks.

  • If you use your own node scripts to run webpack, you'll also want to use the webpack-dev-server API.

  • If you use express to host your site: use webpack-hot-middleware. It will be integrated with your express server, so you won't have to run another server for your bundled JS.

  • If none of the above apply to you or you just want the simplest possible setup: use the webpack-dev-server CLI. It requires no configuration.

Okay, I picked one, but how do I start?

Glad you asked! Below is a separate 'Configuring X' section for each of the 3 approaches.

Scroll forward to the approach you decided to use, follow the directions, and then scroll down to Part 2.

1. Configuring webpack-dev-server CLI

Read this section if you've decided to use the webpack-dev-server CLI. Otherwise, scroll to the next section.

If you are starting a new project, use the server CLI directory of my webpack-3-ways repo. It has a README and lots of comments to explain what's going on.

If you are working with an existing project: just make sure you have a valid webpack.config.js and run webpack-dev-server.

Here's an example command:

webpack-dev-server --content-base=www --inline --watch --hot

You may want to modify the above command for your use-case based on the CLI Docs.

That's it! When you use the webpack-dev-server CLI it does all the configuration for you.

Gotcha: When using this approach, make sure your webpack.config.js does NOT contain any references to webpack-dev-server or HotModuleReplacementPlugin. You'll get errors if you do.

2. Configuring WebpackDevServer API

Read this section if you've decided to use the WebpackDevServer API. Otherwise, scroll to the next section.

If you are starting a new project, use the server API directory of my webpack-3-ways repo. It has a README and lots of comments to explain what's going on.

If you are working with an existing project, you'll have to make 3 changes:

Change #1: Add entry points to your webpack.config.js. Here's an example. You need webpack/hot/dev-server and webpack-dev-server/client as shown, but index.js should be the name of your usual entry point.

entry: [
  './index.js',
  'webpack/hot/dev-server',
  'webpack-dev-server/client?http://localhost:8080/',
],

Change #2: Add the HotModuleReplacementPlugin to your your webpack.config.js, like this:

plugins: [new webpack.HotModuleReplacementPlugin()];

For a complete example, see the starter project webpack.config.js.

Change #3: Run WebpackDevServer with the hot: true option. For an example, see the starter project server.js.

3. Configuring webpack-hot-middleware & express

Read this section if you've decided to use express and webpack-hot-middleware. Otherwise, scroll on to Part 2.

If you are starting a new project, use the middleware directory of my webpack-3-ways repo.

If you are working with an existing express project, you'll need to do 3 things:

Change #1: Install webpack-hot-middleware and webpack-dev-middleware. I'm assuming you already have webpack and express.

npm install --save-dev webpack-hot-middleware webpack-dev-middleware

Change #2: Adjust your webpack.config.js to look similar to this. The important bits are the entry and plugins section. You'll want all the plugins I have listed:

var path = require("path");
var webpack = require("webpack");

var config = {
  context: path.join(__dirname, "js"),
  entry: [
    "webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000",
    "./index.js"
  ],
  output: {
    path: path.join(__dirname, "www"),
    filename: "bundle.js",
    publicPath: "/assets/"
  },
  plugins: [
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin()
  ]
};
module.exports = config;

Change #3: Add the middleware to your express server.

Add these requires:

var webpackDevMiddleware = require("webpack-dev-middleware");
var webpackHotMiddleware = require("webpack-hot-middleware");

And these middleware .use statements:

app.use(
  webpackDevMiddleware(compiler, {
    hot: true,
    filename: "bundle.js",
    publicPath: "/assets/",
    stats: {
      colors: true
    },
    historyApiFallback: true
  })
);

app.use(
  webpackHotMiddleware(compiler, {
    log: console.log,
    path: "/__webpack_hmr",
    heartbeat: 10 * 1000
  })
);

Part 2: Code Changes

Your Webpack project should now be configured for HMR. But if you fire up a browser at this point and make a code change, it won't work. Either you won't see the update, or you'll get a full browser refresh.

Why isn't it working yet? Webpack doesn't know when it is acceptable to reload a particular JS file.

To let Webpack know which files can be updated, we'll use the HMR JavaScript API a.k.a. the module.hot API.

If you used any of my github starter projects, they are already using the module.hot API and you will see changes reflected immediately.

The Simple Way

To start, find your entry point (often called index.js or main.js) and add the following to the end:

if (module.hot) {
  module.hot.accept();
}

This tells Webpack that this file and all of its dependencies can be replaced.

Now make a code change that you could see the effect of onscreen. You SHOULD have seen an update!

For many projects, that's all there is to it.

The Catch: Side Effects

If any of your files produce side effects when they run, for instance if they add elements to the DOM, then you'll need to use module.hot.dispose to dispose of those side effects.

Why? If you don't, when Webpack reloads the module, all the side effects will be repeated.

Here's an example, box-creator.js:

var sideEffectNode = document.createElement("div");
sideEffectNode.textContent = "Side Effect";
document.body.appendChild(sideEffectNode);

// Remove the most recently-added <div> so that when the code runs again and
// adds a new <div>, we don't end up with duplicate divs.
if (module.hot) {
  module.hot.dispose(function() {
    sideEffectNode.parentNode.removeChild(sideEffectNode);
  });
}

This is a file that simply adds a <div/> to the DOM whenever it is loaded. It uses module.hot.dispose to remove the <div/> when it is unloaded by the Webpack HMR Runtime.

Conclusion

That's all you need to know to get HMR working. If you want to dig deeper to solidify your understanding, read my article on understanding HMR.

Coming soon: next I'll post about how to apply HMR to your React project. Sign up for my list to be notified when that comes out!