Setting up Webpack + Babel + ReactJS

I started using Webpack module bundler recently and thought I would document some of my learning. In the past couple of posts I wrote about bundling JavaScript projects using Browserify module bundlers. Webpack can be used as a replacement for Browserify; but it has some nice additional features like bundling of static files and code splitting.

This post is not meant to be tutorial on Webpack, I just wanted to describe how to configure Webpack, specifically using Webpack loaders for Bablel and ReactJS. I will show how to build a JS project using standalone Webpack and also using Webpack APIs in Gulp. The project is available on Github at https://github.com/ramkulkarni1/WebpackBlitzReactStarter

Module Dependencies

Node packages required to build the project are, of course, in package.json. If you are using above starter project from Github, then run ‘npm install’ to install all the required packages. Else make sure you install all the packages listed in devDependencies of package.json . The list contains babel, required babel presets for ES6 and ReactJS, webpack and webpack-dev-server, webpack loaders for babel, LESS and CSS.

Webpack Configuration

Webpack configuration is specified in webpack.config.js. This config file is read when you run webpack or webpack-dev-server command. Optional gulpfile.js (to build the project using Gulp) also loads this file and passes to Webpack APIs.

Here is a brief description of some of the entries in the config file –

context : path.join(__dirname, 'src')

Context sets the parent path from where all paths in the entry element (which tells starting scripts for Webpack to process) are resolved.

entry :[
  './js/index.js',
  //include following entries only if you want to use webpack-dev-server and its hot module replacement feature
  'webpack-dev-server/client?http://localhost:' + serverPort + '/',
  'webpack/hot/dev-server'
]

Entry point for this application is src/js/index.js. Since we have already set the path to src in the context, we specify relative path here. The next two webpack entries are required only if you are using webpack-dev-server (which provides a web server using which you can test your application) and want to use its hot deployment feature.

output : {
  path : outputPath,
  filename : outputFileName,
}

This entry tells Webpack where to save output file and name of the file.

outputPath = path.join(__dirname, 'build'),
outputFileName = 'bundle.js'

At the top of the script outputPath is set to build folder and file name to bundle.js

devtool : isProduction ? null : 'source-map',

This parameter tells Webpack if sourcemap to be generated for the output file. We set this option if we are not packaging for production (by setting NODE_ENV environment variable to ‘production’).

devServer : {
  outputPath : outputPath,
  contentBase: './build',
  port : serverPort,
  hot: true,
  stats: { colors: true},
  filename: outputFileName
}

These are the settings passed to webpack-dev-server. We are again setting path of output file (probably not require), folder from where the server will serve files (as contentBase), server port and output file name. Note that when you use webpack-dev-server, generated output files are not written to the disk, but they are served from the memory. So if you just run webpack-dev-server, you may not find bundle.js (output filename) in build folder.

module : {
  loaders :[
    {
      test: /\.jsx?$/,
      exclude: /node_modules/,
      loader: 'babel',
      query: {
        presets: ['es2015', 'stage-0', 'react', 'react-hmre']
      }
    },
    {
      test : /\.less$/,
      loader : extractCSS ? ExtractPlugin.extract('style', 'css!less') : 'style-loader!css-loader!less-loader'
    },
    {
      test: /\.css$/,
      loader: extractCSS ? ExtractPlugin.extract('style', 'css') : 'style-loader!css-loader'
    },
    {
      test: /\.(png|jpg|svg)$/,
      loaders: ['url', 'image-webpack']
    }
  ]
}

Next we configure Webpack loaders. We tell Webpack how to process files with certain extensions by specifying loaders. So in the above snippet we have loaders for processing js/jsx, .less, .css and image files. I have added image loader just to show how images can be packaged in the output JS file using Webpack. It may not be a good idea to load large images using image loader upfront, because images are included as data url (content is embedded as base64 in the output JS file). But it may be a great way to package small icons. We will see sample of this later.

Just like images, you can also package CSS inside the output JS file. However we can make Webpack output them seperately too, by using extract-text-webpack-plugin. In the above configuration, this options is controlled by a flag extractCSS. If this is set to true, CSS files will output separately.

resolve: {
  extensions: ['', '.js', '.jsx', '.css', '.less']
}

In the resolve->extensions option we till Webpack what file extensions to be considered for processing.

plugins : [
  failPlugin,
  new copyWebpackPlugin([
    {from : 'html', to: __dirname + '/build'},
  ]),
  //include following plugin only if you want to use webpack-dev-server and its hot module replacement feature
  new webpack.HotModuleReplacementPlugin()
]

Here we add a plugin to return status code 1 if Webpack fails (using webpack-fail-plugin), a plugin to copy static files in html folder to build folder (copy-webpack-plugin) and a plugin to enable hot module replacement in Webpack.

serverPort : serverPort

The last parameter is not for Webpack, but used in the gulpfile in this project. It just for passing serverPort value set in this config file to Gulp.

See Webpack configuration docs for more detailed information.

Application Source Code

The sample app is very simple; the main JS entry point is index.js that creates React view called MainView. This view displays a textbox, a button and a image icon.

index.js –

import React from 'react';
import ReactDOM from 'react-dom';
import MainView from "./mainView";

var mainView = React.createElement(MainView,{name:'there'},null);

ReactDOM.render(mainView, document.getElementById("main"));

and mainView.js

import React from 'react';
import '../css/app.less';
import homeImage from '../html/home.png';

export default class MainView extends React.Component {
  constructor(props) {
    super(props);
	this.state = {
	  name : props.name
	}
}

onSetName = () => {
  this.setState({
    name : this.refs.nameTxt.value
  })
}

render() {
  return <div className="main-view">
	Hello {this.state.name} !
	<p/>
    Enter Name : <input type="text" ref="nameTxt" />
    <button type="button" onClick={this.onSetName}>Set Name</button>
    <p/>
    <img src={homeImage}/> - Example of loaging image with webpack image loader
	</div>
  }
}

Notice that static assets like app.less and home.png are also imported in the above file. As mentioned earlier, CSS output after processing app.less can be made to be saved as a separate file using extract-text-webpack-plugin – but the above code won’t change if we do that. Of course, if you create a seperate CSS file, you will have to include that in the html file using link tag.

Building and Running the Application

To build the application run webpack command. Webpack will read webpack.config.js file and generate output file, bundle.js in build folder. It will also copy index.html from html folder. This application really does not need a webserver, so you can run index.html directly in a browser. But if you run it using webpack-dev-server, you can take advantage of its hot module replacement feature and you can test you code without manually building the application after you make any changes to the source code. Run webpack-dev-server and browse to http://localhost:8080 to run the application. Note that you should install webpack and webpack-dev-server packages globally (npm install webpack webpack-dev-server -g)

If you make any changes to the source code, for example you change styles in app.less or change the markup in mainView.js, you don’t need to rebuild the app manually – webpack-dev-server automatically detects changes and rebuilds the app and refreshes the page in the browser (because of hot module replacement feature).

Using Webpack APIs with Gulp

Webpack provides JS APIs too, which you can use with Gulp. gulpfile.js shows example of this.

var gulp = require('gulp'),
  webpack = require('webpack'),
  devServer = require('webpack-dev-server')
;

var webpackConfig = require('./webpack.config');

function compile() {
  var compiler = webpack(webpackConfig);

  compiler.run(function(err, stat){
	if (err) {
	  console.log('Error building application - ', err);
	  return;
	}
	var statJson = stat.toJson();
	if (statJson.errors.length > 0) {
	  console.log('Error building application - ', statJson.errors);
	  return;
	}

	console.log('Application built successfully !');
  });
}

function startServer() {
  var compiler = webpack(webpackConfig);
  var server = new devServer(compiler, webpackConfig.devServer);
  server.listen(webpackConfig.serverPort);
}

gulp.task('default', function(){
  startServer();
});

gulp.task('build', function() {
  compile();
});

Run ‘gulp’ command to build the project and start webpack-dev-server. Run ‘gulp build’ if you just want to build the project and generate output files in build folder.

-Ram Kulkarni

3 Replies to “Setting up Webpack + Babel + ReactJS”

  1. i just wonder if i build the js files and view html in browser it will still connect WDS . so if i should disbale HRM if i build the file?

    1. Yes, you should not include HMR if you are not using webpack-dev-server. I realized it is is not a good idea to add HMR settings to webpack-config.js, because the built files will also include HMR code which does not make sense. So I moved them to startServer task of the gulpfile. I have updated the project on GitHub.

  2. Guys, this video follows the above article in a very similar fashion. Hopefully, the
    following link helps for future readers, who want to see every step as they develop:

Leave a Reply

Social