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.
First let's get a couple myths about HMR out of the way.
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.
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.
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.
There are 2 steps to enabling HMR: config changes and code changes.
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.
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.
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.
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.
Run WebpackDevServer
from a node.js script. Requires some additions to your
webpack.config.js
.
webpack-hot-middleware is used for running a webpack dev server with HMR inside an express server.
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.
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.
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.
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.
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.
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
})
);
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.
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.
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.
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!