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

rajaraodv
8 min readApr 30, 2016

--

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.

webpack.config.js w/ HMR

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.

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

custom-web-dev-server.js

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.

Showing react-hot-loader and style-loader

“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.

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.

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

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.

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

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).

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:

index.ejs at localhost:3000

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.

Shows the scenario of API+Web server

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

proxy everything w/ *

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).

Web + API Server

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 *).

since it matches /static/, “/static/bundle.js” is served from localhost:8080 itself instead of proxying it

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🎉🎉🎉

--

--