Setup React Webpack

You will learn in this chapter how to setup React development environment starting from scratch. By the end of this chapter we will have a starter boilerplate to develop React apps.

We will cover following topics in this chapter.

  • How to install Node.js and use Node Version Manager
  • Setup package.json to manage your NPM dependencies
  • Quick access companion code for this book using Github
  • Install starter dependencies for React, Webpack, and Babel
  • Create Webpack configuration for development pipeline automation
  • Write a simple React app to run your Webpack setup

Code Along. You can clone the source for this entire book, change to app directory, checkout just the code for this chapter, install and start the app to launch the local version in your default browser.

Preview complete demo website hosted on Firebase, as you code this app by the end of this book.

View current chapter demo of the app we build in this chapter.

git clone https://github.com/manavsehgal/react-speed-book.git
cd react-speed-book
git checkout -b c01 origin/c01-setup-react-webpack
npm install
npm start

Installing Node.js

You will need Node.js to get started with React. Your Mac OS comes with Node pre-installed. However you may want to use the latest stable release.

Check you node release using node -v command.

We recommend installing or upgrading Node using Node Version Manager (NVM). Their Github repo documents install and usage.

To install NVM:

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.0/inst\
all.sh | bash

Now you can install a Node release by choosing one from Node releases page.

The command nvm install 5.10.1 installs a stable release for us.

One of the advantages of using NVM is you can switch between multiple node releases you may have on your system.

Here’s what our terminal looks like when using nvm ls to list installed node releases.

v4.2.3
v5.3.0
v5.10.0
->      v5.10.1
system
default -> 5.3.0 (-> v5.3.0)
node -> stable (-> v5.10.1) (default)
stable -> 5.10 (-> v5.10.1) (default)
iojs -> iojs- (-> system) (default)

Using nvm use x.y.z command we can switch to x.y.z installed node release.

Setting up package.json

You will require package.json to manage your NPM dependencies and scripts.

Create a new one using npm init command, selecting defaults where uncertain.

This is what our package.json looks like as we start off. Note that we added the private flag to avoid accidental publishing of the project to NPM repo, and also to stop any warnings for missing flags like project repo.

{
  "name": "react-speed-book",
  "version": "1.0.0",
  "description": "Companion code for React Speed Coding book",
  "main": "index.js",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/manavsehgal/react-speed-book.git"
  },
  "author": "Manav Sehgal",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/manavsehgal/react-speed-book/issues"
  },
  "homepage": "https://github.com/manavsehgal/react-speed-book#readme"
}

The dependencies section will start showing up as we add npm dependencies.

Installing starter dependencies

Before we start writing our React app we need to install starter dependencies for our development environment. React uses JSX as the XML-like syntax extension over JavaScript to specify component tree structure, data flow, and event handlers. JSX is processed by Webpack module bundler using specific loaders or convertors.

React Dependency Stack
React Dependency Stack

React recommends JavaScript ES6 for latest features and best practices. ES6 needs Babel for compiling to ES5 and maintain browser compatibility. Babel integrates with Webpack to stitch it all together for our app.

React and React DOM

React is available via NPM and this is the recommended way of using React in a project. React core is available in the react package. The react-dom package targets browser DOM for rendering React. React enables several targets including iOS, Android for rendering React apps.

npm install --save react
npm install --save react-dom

Webpack

Webpack is used for module packaging, development, and production pipeline automation. We will use webpack-dev-server during development and webpack to create production builds.

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

HTML generation

You can add functionality to Webpack using plugins. We will use automatic HTML generation plugins for creating index.html for our app.

The html-webpack-plugin webpack plugin will refer to template configured within webpack configuration to generate our index.html file.

npm install --save-dev html-webpack-plugin

Loaders

Webpack requires loaders to process specific file types. CSS loader resolves @import interpreting these as require() statements. Style loader turns CSS into JS modules that inject <style> tags. JSON loader enables us to import local JSON files when using data fixtures during prototyping our app.

npm install --save-dev css-loader
npm install --save-dev style-loader
npm install --save-dev json-loader

PostCSS and Normalize CSS

PostCSS loader adds CSS post-processing capabilities to our app. This includes Sass like capabilities including CSS variables and mixins using precss and automatic vendor prefixes using the autoprefixer plugin for PostCSS.

Normalize CSS offers sensible resets for most use cases. Most CSS frameworks include it.

The postcss-easy-import plugin enables processing @import statements with rules defined in webpack configuration.

npm install --save-dev postcss-loader
npm install --save-dev precss
npm install --save-dev autoprefixer
npm install --save-dev normalize.css
npm install --save-dev postcss-easy-import

Babel

Babel compiles React JSX and ES6 to ES5 JavaScript. We need babel-loader as Webpack Babel loader for JSX file types.

Hot loading using babel-preset-react-hmre makes your browser update automatically when there are changes to code, without losing current state of your app.

ES6 support requires babel-preset-es2015 Babel preset.

npm install --save-dev babel-core
npm install --save-dev babel-loader
npm install --save-dev babel-preset-es2015
npm install --save-dev babel-preset-react
npm install --save-dev babel-preset-react-hmre

Configure Babel .babelrc

Babel configuration is specified in .babelrc file. React Hot Loading is required only during development.

{
  "presets": ["react", "es2015"],
  "env": {
    "development": {
      "presets": ["react-hmre"]
    }
  }
}

Dependencies in package.json

The dependencies within package.json now lists all the installed dependencies for our app so far.

"dependencies": {
  "react": "^15.3.0",
  "react-dom": "^15.3.0"
},
"devDependencies": {
  "autoprefixer": "^6.4.0",
  "babel-core": "^6.13.2",
  "babel-loader": "^6.2.4",
  "babel-preset-es2015": "^6.13.2",
  "babel-preset-react": "^6.11.1",
  "babel-preset-react-hmre": "^1.1.1",
  "copy-webpack-plugin": "^3.0.1",
  "css-loader": "^0.23.1",
  "html-webpack-plugin": "^2.22.0",
  "json-loader": "^0.5.4",
  "normalize.css": "^4.2.0",
  "postcss-easy-import": "^1.0.1",
  "postcss-loader": "^0.9.1",
  "precss": "^1.4.0",
  "style-loader": "^0.13.1",
  "webpack": "^1.13.1",
  "webpack-dev-server": "^1.14.1"
}

Configure Webpack in webpack.config.js

Webpack configuration drives your development pipeline, so this is a really important file to understand. We will split various sections of the config file to aid step-by-step learning.

Initialization

To start off, you need to initialize the config file with dependencies. These include webpack itself, an HTML generation plugin, a webpack plugin to copy folders from development to build target, and Node path library for initializing default paths.

Next we initialize the default paths. We also configure defaults for our HOST and PORT configuration.

// Initialization
const webpack = require('webpack');

// File ops
const HtmlWebpackPlugin = require('html-webpack-plugin');

// Folder ops
const CopyWebpackPlugin = require('copy-webpack-plugin');
const path = require('path');

// PostCSS support
const postcssImport = require('postcss-easy-import');
const precss = require('precss');
const autoprefixer = require('autoprefixer');

// Constants
const APP = path.join(__dirname, 'app');
const BUILD = path.join(__dirname, 'build');
const STYLE = path.join(__dirname, 'app/style.css');
const PUBLIC = path.join(__dirname, 'app/public');
const TEMPLATE = path.join(__dirname, 'app/templates/index.html');
const NODE_MODULES = path.join(__dirname, 'node_modules');
const HOST = process.env.HOST || 'localhost';
const PORT = process.env.PORT || 8080;
  • APP path is used to specify the app entry point, location of the root component
  • BUILD path specifies the target folder where production compiled app files will be pushed
  • STYLES path indicates the root CSS file that imports other styles
  • PUBLIC path is a folder in development that is copied as-is to root of BUILD, used for storing web server root files like robots.txt and favicon.ico, among others
  • TEMPLATE specifies the template used by html-webpack-plugin to generate the index.html file
  • NODE_MODULES path is required when including assets directly from installed NPM packages

Entry points and extensions

Next section defines your app entry, build output, and the extensions which will resolve automatically.

module.exports = {
  // Paths and extensions
  entry: {
    app: APP,
    style: STYLE
  },
  output: {
    path: BUILD,
    filename: '[name].js'
  },
  resolve: {
    extensions: ['', '.js', '.jsx', '.css']
  },

Loaders

We follow this by defining the loaders for processing various file types used within our app.

React components will use .jsx extension. The React JSX and ES6 is compiled by Babel to ES5 using the babel webpack loader. We use the cacheDirectory parameter to improve repeat development compilation time.

The include key-value pair indicates where webpack will look for these files.

CSS processing is piped among style for injecting CSS style tag, css for processing import paths, and postcss for using various plugins on the CSS itself.

We need JSON processing much later in the book to enable loading of local JSON files storing data fixtures (sample data) for our app.

module: {
  loaders: [
    {
      test: /\.jsx?$/,
      loaders: ['babel?cacheDirectory'],
      include: APP
    },
    {
      test: /\.css$/,
      loaders: ['style', 'css', 'postcss'],
      include: [APP, NODE_MODULES]
    },
    {
      test: /\.json$/,
      loader: 'json',
      include: [APP, NODE_MODULES]
    }
  ]
},

PostCSS plugins configuration

PostCSS requires further configuration to handle how PostCSS plugins behave.

Now we add postcssImport which enables Webpack build to process @import even from node_modules directory directly.

Sequence matters as this is the order of execution for the PostCSS plugins. Process imports > compile Sass like features to CSS > add vendor prefixes.

// Configure PostCSS plugins
postcss: function processPostcss(webpack) {  // eslint-disable-line no\
-shadow
  return [
    postcssImport({
      addDependencyTo: webpack
    }),
    precss,
    autoprefixer({ browsers: ['last 2 versions'] })
  ];
},

Configure webpack-dev-server

Now that we have loaders configured, let us add settings for our development server.

Source maps are used for debugging information.

The devServer settings are picked up by webpack-dev-server as it runs.

The historyApiFallback enables local browser to be able to handle direct access to route URLs in single page apps.

The hot flag enables hot reloading. Using the inline flag a small webpack-dev-server client entry is added to the bundle which refresh the page on change.

The progress and stats flags indicate how webpack reports compilation progress and errors.

// Source maps used for debugging information
devtool: 'eval-source-map',
// webpack-dev-server configuration
devServer: {
  historyApiFallback: true,
  hot: true,
  progress: true,

  stats: 'errors-only',

  host: HOST,
  port: PORT,

  // CopyWebpackPlugin: This is required for webpack-dev-server.
  // The path should be an absolute path to your build destination.
  outputPath: BUILD
},

Plugins

We now wrap up by adding plugins needed during our development.

This section specifies the compilation workflow. First we use the DefinePlugin to define the NODE_ENV variable.

We then activate the Hot Reloading plugin to refresh browser with any app changes.

Then we generate any HTML as configured in the HtmlWebpackPlugin plugin.

plugins: [
  // Required to inject NODE_ENV within React app.
  new webpack.DefinePlugin({
    'process.env': {
      'NODE_ENV': JSON.stringify('development') // eslint-disable-line\
 quote-props
    }
  }),
  new webpack.HotModuleReplacementPlugin(),
  new CopyWebpackPlugin([
    { from: PUBLIC, to: BUILD }
  ],
    {
      ignore: [
        // Doesn't copy Mac storage system files
        '.DS_Store'
      ]
    }
  ),
  new HtmlWebpackPlugin({
    template: TEMPLATE,
    // JS placed at the bottom of the body element
    inject: 'body'
  })
]};

HTML webpack template for index.html

We can now add a custom template to generate index.html using the HtmlWebpackPlugin plugin.

This enables us to add viewport tag to support mobile responsive scaling of our app. We also add icons for mobile devices. Following best practices from HTML5 Boilerplate, we add html5shiv to handle IE9 and upgrade warning for IE8 users.

<!DOCTYPE html>
<html class="no-js" lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>React Speed Coding</title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1\
">

    <link rel="icon" href="/favicon.ico" />

    <!--[if lt IE 9]>
        <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></\
script>
        <script>window.html5 || document.write('<script src="js/html5s\
hiv.js"><\/script>')</script>
    <![endif]-->
  </head>
  <body>
    <!--[if lt IE 8]>
        <p class="browserupgrade">You are using an <strong>outdated</s\
trong> browser. Please <a href="http://browsehappy.com/">upgrade your \
browser</a> to improve your experience.</p>
    <![endif]-->

    <div id="app"></div>
    <!-- Google Analytics: change UA-XXXXX-X to be your site's ID. -->
  </body>
</html>

Configuring startup scripts in package.json

We can configure startup scripts in package.json to speed up our development even further.

Before we do that, let us create a copy of webpack.config.js as webpack.prod.config.js intended for production build version of our webpack configuration. We will only make one change in the production configuration. Changing NODE_ENV in plugins section from development to production value.

Now npm start will run webpack-dev-server using inline mode which works well with hot reloading browser when app changes without manually refreshing the browser.

Running npm run build will use webpack to create our production compiled build.

"scripts": {
  "start": "NODE_ENV=development webpack-dev-server --inline",
  "build": "NODE_ENV=production webpack --config webpack.prod.config.j\
s"
},

The webpack-dev-server will pick up the webpack.config.js file configuration by default.

Run webpack setup

index.js

Add index.js in the root of our development folder.

import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(
  <h1>App running in {process.env.NODE_ENV} mode</h1>,
  document.getElementById('app')
);

We will learn each aspect of a React component in the next chapter. For right now we are writing a minimal React app to test our Webpack configuration.

style.css

We also import the Normalize CSS library in our main style entry file.

@import 'normalize.css';

public/ folder

To test how are public folder is copied over to build, let us add favicon.ico file to public folder in root of our development folder.

Running webpack

We can now run the development server using npm start command in the Terminal. Now open your browser to localhost URL that your webpack dev server suggests.

The browser app displays message that you are running in development mode. Try changing the message text and hit save. Your browser will refresh automatically.

You can also build your app to serve it using any web server.

First add a file server like so.

npm install -g serve

Now build and serve.

npm run build
serve build

As you open your localhost URL with port suggested by the file server, you will note that the message now displays that you are running in production mode.

The build folder lists following files created in our webpack build.

app.js        <== App entry point
favicon.ico   <== Copied as-is from public/ folder
index.html    <== Generated by HTML webpack plugin
style.js      <== Contains our styles

Congratulations… You just built one of the most modern development environments on the planet!