Setting Up Webpack for React and Hot Module Replacement

Blake Williams

Recently I’ve been toying with React and trying to find the best way to get up and running. After a lot of trial and error I decided to go with webpack as my build tool and share what I’ve learned from my experience with it.

Initializing a project

Since React doesn’t have any tools to generate and manage projects, it’s up to us to set everything up. We need to create the directory, initialize Git, and initialize npm.

mkdir react-project && cd react-project
git init
npm init

After running npm init, it’s a good idea to add "private": true to package.json so we can’t accidentally publish our project to npm.

Setting up webpack

Webpack is the build tool that takes our application code and generates static assets as well as a development server. Webpack has several advantages over other build tools: for example, it strips out unused code, supports hot module replacement, and is easily configured.

To get started we need to install webpack both globally and in the project. We install it globally to make the webpack command available and we install it locally so we can specify which version of webpack the project should use instead of relying on the global install.

npm install webpack --global
npm install webpack --save-dev

Basic configuration

Let’s start with a basic configuration in webpack.config.js and build on it as we go.

module.exports = {
  context: __dirname + "/app",
  entry: "./app.js",

  output: {
    filename: "app.js",
    path: __dirname + "/dist",
  },
}

At the moment we’re telling webpack that our application lives in the app directory and the entry point to our application is app.js. We also tell webpack to output the resulting JavaScript in dist/app.js.

We can add a basic app/app.js to make sure it’s working:

console.log("webpack rocks!");

Now we can run webpack again and we can see that it creates dist/app.js and includes the code that we wrote.

Adding loaders

At the moment our configuration is basically just copying our file to the dist folder. The real power of webpack is the loaders that it provides. In this case we’re going to use loaders to send our code through Babel and transform our JSX into JavaScript.

Fortunately the Babel loader supports transforming both ES2015 and JSX which means we can get away with using a single loader instead of requiring both the babel-loader and the jsx-loader.

We can install the babel loader with the following command:

npm install babel-loader --save-dev

To use the loader we need to add a loaders object nested in a module object in webpack.config.js.

module: {
  loaders: [
    {
      test: /\.js$/,
      exclude: /node_modules/,
      loaders: ["babel-loader"],
    }
  ],
},

Webpack accepts an array of loader objects which specify loaders to apply to files that match the test regex and exclude files that match the exclude regex. In this case we’re applying the babel-loader to all files with a .js extension that aren’t in node_modules.

If we run webpack we’ll see that webpack still bundles our JavaScript successfully.

Writing React components

Now that we have a basic webpack configuration set up we can add React to the mix. Let’s install React and write a simple greeting component.

First we need to install React:

npm install react --save

Now we can write a greeting component in app/greeting.js:

import React from "react";

export default React.createClass({
  render: function() {
    return (
      <div className="greeting">
        Hello, {this.props.name}!
      </div>
    );
  },
});

Inside app/app.js we need to import our new greeting component and render it on the page.

import React from "react";
import Greeting from "./greeting";

React.render(
  <Greeting name="World"/>,
  document.body
);

We’re using ES6 modules to import React and the greeting component we just wrote. The first argument to React.render is using JSX to render our Greeting component with the name property being passed in as World.

JSX is just a more friendly syntax for rendering React components. We could write it in JavaScript too. When compiled, the babel-loader will turn our JSX into the following:

React.createElement(Greeting, {name: "World"})

Now when the application is loaded our Greeting component will be rendered with “World” passed in as the name property.

Adding an index page

React is ready to go, but we need a page to render the content. Let’s create a basic page in app/index.html.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Webpack + React</title>
  </head>
  <body></body>
  <script src="app.js"></script>
</html>

Webpack needs to copy this file over to our dist folder for us to use it which means we’ll need to modify our entry property in our webpack config and add an additional loader.

entry: {
  javascript: "./app.js",
  html: "./index.html",
},

To actually copy the file over we need the file-loader package, which is a loader that copies files.

npm install file-loader --save-dev

In our webpack.config.js file below our first loader object, we can add a loader object for the index page:

{
  test: /\.html$/,
  loader: "file?name=[name].[ext]",
},

Run webpack one more time and open up the dist/index.html file in a browser and you can see our greeting component rendered on the page.

Setting up the webpack dev server

It’s pretty tedious to continue having to type webpack and refresh the page each time we make a change. Lucky for us webpack has another module that provides a development server.

We need to install webpack-dev-server both locally and globally for the same reasons we installed webpack that way. We can install it with the following command:

npm install webpack-dev-server --global
npm install webpack-dev-server --save-dev

To run the dev server run webpack-dev-server and visit http://localhost:8080.

Hot module replacement

Using the webpack-dev-server we can set up hot module replacement with React. This means whenever we modify a component and save the file webpack will replace the module on the page without reloading, without losing component state.

To get hot reloading working with React we have to install react-hot-loader:

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

To use the react-hot-loader loader, we can just prepend it to the loaders array in our first loader object.

{
  test: /\.js$/,
  exclude: /node_modules/,
  loaders: ["react-hot", "babel-loader"],
},

Whenever matching files are found they’re now passed through react-hot first and then through babel-loader.

Finally we need to run webpack-dev-server with two new options:

webpack-dev-server --hot --inline

Now when serving our project webpack injects JavaScript code that connects to the server and waits for module updates to be pushed. When an update is received the module is replaced.

It’s worth noting that not all modules can be replaced. The code in app/app.js cannot be reloaded and will cause a full page reload but changing the Greeting component will trigger a hot module replacement.

To see it in action we can modify our greeting component by wrapping the text in an h1 tag. Now the render function in app/greeting.js will look like this:

render: function() {
  return (
    <div className="greeting">
      <h1>Hello, {this.props.name}!</h1>
    </div>
  );
},

When you save the file you’ll see the page update automatically without a page reload.

For convenience we can add an npm script to run the server with the --hot --inline flags so we don’t have to type them out each time.

We can add the command under the scripts object in package.json:

"scripts": {
  "start": "webpack-dev-server --hot --inline"
},

To run the server all we have to do is run npm start and we have a development server with hot module replacement up and running.

What’s next?

  • Learn more about webpack loaders.
  • Dive deeper into React’s JSX templates.
  • Learn about building React applications with Flux, a design pattern by Facebook.