Webpack’s HMR & React-Hot-Loader — The Missing Manual

rajaraodv
rajaraodv
Apr 30, 2016 · 8 min read

Webpack’s HMR along with React-Hot-Loader makes developing React apps very productive. But depending on the type of the React app (client and server) you are building, setting them up could be challenging. Further, Webpack itself is very flexible and provides various ways to enable HMR and some of them may not be good for YOUR app.

So in this blog I’ll go over 3 ways of enabling up Webpack’s HMR and then go over 3 React app scenarios and show how to set them up. I’ll also cover some of the “confusing” parts along the way 😀.

But first, a quick refresher about HMR..

Webpack HMR Brief Overview

Webpack’s Hot Module Replacement (HMR) allows you to replace updated modules without reloading the browser. It needs webpack-dev-server and the following 4 parts in order to work.

  1. Libraries injected into the browser to perform HMR (webpack hmr)
  2. Libraries in the browser to listen to changes in the server( i.e. webSocket client)
  3. Needs to know if “hot” is enabled and other info from the server (webSocket info from webpack-dev-server)
  4. Needs a plugin to generate hot chunks that contain the changed parts (HMRPlugin).

You can learn more from my previous post Webpack And The HMR

OK, let’s go over different ways of enabling Webpack’s HMR…

There are 3 different ways to enable Webpack’s HMR itself. And then depending on your app’s scenario, you can choose one of them and then use React Hot Loader along with it.

Enable HMR Method 1 — Webpack’s CLI

This is probably the simplest that works for most cases but not for all. You need to pass inline and hot to enable HMR (all 4 part mentioned earlier).

1. inline option

→ This injects all the libraries required to monitor and reload the browser

2. hot option

→ Adds HotModuleReplacementPlugin that generates update chunks.

→ Adds ‘webpack/hot/dev-server’ to every entry (single or multiple).

→ Sets WDS’ “hot” to true {hot:true} so relevant code for HMR is enabled

Below are the ways to run HMR via CLI.

//1. WDS is installed globally
webpack-dev-server --inline --hot
//2. WDS is installed as a dev-dependency
node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot
//3. from package.json's script
{
...
"scripts": {
"start": "webpack-dev-server --inline --hot"
}
...
}
You can run by from CLI by doing: npm start

Enable HMR Method 2 — webpack.config.js

Instead of passing hot and inline via CLI, we can configure all of that inside the webpack.config.js file.

One of the advantages is that you can choose between “/hot/dev-server” Vs “/hot/only-dev-server” (You’ll learn the difference later in this blog)

Below is an example webpack.config.js that enables HMR in it’s simplest form.

Image for post
Image for post

You can then run this from CLI like below. Notice that there is no “inline” and “hot”.

//1. WDS is installed globally
webpack-dev-server
//2. WDS is installed as a dev-dependency
node_modules/webpack-dev-server/bin/webpack-dev-server.js
//3. from package.json's script
{
...
"scripts": {
"start": "webpack-dev-server
}
...
}

Important: Use either CLI or Config file but never mix and match the above two ways.

FYI — webpack.config.js (contentBase)

If your index.html is in a different folder like “public”, make sure to set contentBase to point to that.

Image for post
Image for post

Enable HMR Method 3 — Via NPM Module

Webpack-dev-server itself is a wrapper around an Express server and deals with core Webpack and WebSocket. Webpack-dev-server exposes itself and the Express server as a Node module. So you can create a JS file and use WDS like another Express server (i.e. a custom WDS) and pass arguments.

The below picture shows how to do that. Notice that it still needs webpack.config.js for core Webpack configuration details.

Note: you can click on the picture to zoom and read

Image for post
Image for post

You run this custom webpack-dev-server from the terminal by calling: “npm custom-web-dev-server.js”

The above code is from Dan Abramov’s React Hot Reloader example.

Note: Some projects use this capability to add the above WDS code to their Node.js server’s file itself so they can run both servers in a single Terminal (in different ports) and with a single command.

OK, now let’s see how to add different loaders for 3 different App scenarios…

App Scenario 1 — A Simple React App

In a very basic React app that doesn’t even make API calls like Dan Abramov’s React Hot Reloader example, it’s very simple to add and use loaders like style-loader & react-hot-loader that implement Webpack’s HMR feature.

Adding Style-Loader or React-Hot-Loader

In order to enable HMR for CSS and React modules, all you need to do is to add the following loaders.

npm install react-hot-loader --save-dev
npm install style-loader --save-dev

And then add the loaders to the Webpack's config file like below.

Image for post
Image for post

“loaders” Vs “loader” inside “loaders”

In the above picture, if you look carefully, you might notice that for react-hot-loader added as part of an Array where as for style-loader is part of a String! In addition, the property name for the former is called “loaders” (plural) and the latter is called “loader”(singular). 😱

It turns out, they both are the SAME but just different ways of piping multiple loaders for a single file format.

Image for post
Image for post

Thanks Sokra (Webpack creator) for clarifying that.

“/hot/only-dev-server” Vs “/hot/dev-server”

They both are simple JS libraries and provide HMR interface for webpack-dev-server’s client JS(part of WDS) that’s also loaded into the browser(See my Webpack And The HMR for more details).

You can use just one of them. The main difference is as follows:

  1. only-dev-server doesn’t reload the browser upon syntax errors. This is recommended for React apps because it keeps the state.
  2. dev-server tries HMR (default). If there is any issue, it reloads the entire browser.
Image for post
Image for post

Note: If you are using the CLI with — inline and — hot, dev-server is automatically added (and not the only-dev-server)

App Scenario 2— A React App With Backend API calls (proxy)

Let’s say we have a React app that makes an API call to the server that’s running in port 3000. For example, the below picture tries to get the server’s time by calling “/api/serverTime”. Without WDS, it will be calling http://localhost:3000/api/serverTime

Image for post
Image for post

But, when running inside webpack-dev-server that’s running on port 8080 means we are calling http://localhost:8080/api/serverTime! As you know WDS only serves static files has no idea about that API.

Image for post
Image for post

Even if we hardcoded the path like: “http://localhost:3000/api/serverTime”, then we’ll see cross-domain error like so:

Image for post
Image for post

To solve this situation, which is very common BTW, you can use WDS’s “proxy” property like below. Now, every time the browser makes the call to /api/*, WDS sends that info to the real backend server (running at 3000).

Image for post
Image for post

Note: You can use /api/v1/* or /api/v2/* to switch and test different versions of the backend server APIs.

App Scenario 3— A React App With Backend Server That’s BOTH API AND Web Server

This is also a common scenario where the server is acting as both API and web server.

Imagine you have an Express server that’s running in port 3000 that serves html by compiling index.ejs at https://localhost:3000 and serves html by compiling users.ejs at https://localhost:3000/users

Now, Imagine these ejs files simply include React’s bundle.js like so:

Image for post
Image for post

In production it’s fine because the webserver will compile the ejs and return the html but during development w/ HMR we have a problem! We NEED Express (at 3000) to generate HTML from ejs.

The below picture shows the scenario.

Image for post
Image for post

The solution is to proxy everything with a “*” star.

Image for post
Image for post

FYI — publicPath overrides proxy settings

The below picture shows the Express server logs(at port 3000) when we refresh the browser(at 8080). Notice that the webpack-dev-server has proxied:

1. “/” (to load index file from compiled index.ejs),

2. “/stylesheets/style.css” (to load style.css)

3. “/api/serverTime” (to return serverTime api value).

Image for post
Image for post

But, it has not proxied the /static/bundle.js”. This is because in our config file, we have publicPath set to ‘/static/’ and webpack-dev-server gives priority to this over the proxy (even though we have set it to *).

Image for post
Image for post

This means if you are loading bundle.js from a different directory that doesn’t match publicPath settings (you’ll have to setup ANOTHER proxy on the Express server that’s running on port 3000 (that points back to localhost:8080). This will be super confusing.

Also, you don’t have to use publicPath at all. If you don’t set it or set it to ‘/’, just make sure that the ejs files load bundle.js like so:

<script src=”/bundle.js”></script>

That’s it! 🙏

My Other Posts

ES6

  1. 5 JavaScript “Bad” Parts That Are Fixed In ES6

WebPack

  1. Webpack — The Confusing Parts
  2. Webpack & Hot Module Replacement [HMR] (under-the-hood)
  3. Webpack’s HMR And React-Hot-Loader — The Missing Manual

Draft.js

  1. Why Draft.js And Why You Should Contribute
  2. How Draft.js Represents Rich Text Data

React And Redux :

  1. Step by Step Guide To Building React Redux Apps
  2. A Guide For Building A React Redux CRUD App (3-page app)
  3. Using Middlewares In React Redux Apps
  4. Adding A Robust Form Validation To React Redux Apps
  5. Securing React Redux Apps With JWT Tokens
  6. Handling Transactional Emails In React Redux Apps
  7. The Anatomy Of A React Redux App

Salesforce

  1. Developing React Redux Apps In Salesforce’s Visualforce

🎉🎉🎉 If you like this post, please 1. ❤❤❤ it below on Medium and 2. please share it on Twitter. You may retweet the below card🎉🎉🎉

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store