Webpack

By nathanharper

Saturday, Feb 21, 2015

In my last post, I outlined our move to Browserify, and how it was a huge win for us. Surely, with such obvious benefits and no apparent downsides, Browserify was here to stay. But every once in a while, a new technology comes along — a technology so futuristic, so amazing, so utterly essential, that you know you need to have it. Now. That technology? React Hot Loader.

hotloader-listings

mind_blown

There was just one problem. From the React Hot Loader installation instructions:

switch to Webpack for builds (instead of RequireJS or Browserify);

What is Webpack?

Webpack is a module bundler that is similar to Borwserify in many ways, but is a bit newer and packs a few interesting features that Browserify lacks. (See a full comparison of features)

The features we were primarily interested in are:

  • Multiple bundles created from separate entry points.
  • Faster build times in development.
  • Hot Module Replacement! A unique feature of Webpack that enables React Hot Loader.

Transitioning from Browserify to Webpack

Since we already had a perfectly fine, working module bundler, my first concern was that we could configure Webpack to do everything that Browserify did. The purpose of installing Webpack was a feature upgrade; the last thing I wanted was to replace a proven, functional part of our development stack and lose features.

The Webpack documentation has a handy guide for transitioning from Browserify. I was extremely pleased to find the transition quick and painless; no changes to application code were required, and many of the features that had to be added to Browserify with plugins (e.g. minification and shimming) were possible in Webpack’s base installation. With this webpack.config.js file, I was able to mimick our Browserify setup with no apparent regressions.

var webpack = require('webpack');

module.exports = {
  devtool: '#source-map',
  entry: './src/js/main.js',
  output: {
    path: './build/js',
    filename: '[name].min.js',
    sourceMapFilename: '[file].map'
  },
  externals: {
    'jquery': 'jQuery',
    'react/addons': 'React',
    'reflux': 'Reflux',
    'react-bootstrap': 'ReactBootstrap',
    'react-radio-group': 'RadioGroup',
    'intercom.io': 'Intercom',
    'moment': 'moment'
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin({minimize: true})
  ]
};

Unlike Browserify, there is no need to install any shim plugin, as the “externals” feature is built in. UglifyJsPlugin also comes as a default for easy minimization.

Multiple Entries

It should probably go without saying that you don’t want your application to be loading code that it doesn’t need. The Browserify approach is typically to provide a single entry-point file, and build a single, large bundle from that. Webpack makes it easy to provide multiple entry points, and it can even analyze the common code between these bundles and extract it into its own bundle. This can lead to significantly faster rebuild times if the common components of your code are not changing often.

var webpack = require('webpack');

module.exports = {
  /* A separate bundle is generated for each entry */
  entry: {
    entry1: './src/js/entry1.js',
    entry2: './src/js/entry2.js',
    entry3: './src/js/entry3.js'
  },
  output: {
    path: './build/js',
    filename: '[name].min.js'
  },
  plugins: [
    /* This plugin creates a new bundle for all the common code. */
    new webpack.optimize.CommonsChunkPlugin('common.min.js')
  ]
};

DefinePlugin

The Webpack DefinePlugin allows you to create “Magic” global variables for your app that Webpack will replace when it bundles your project. The cool thing about this is that it does a literal replacement of the variable with the string you assign. I used this functionality to eliminate a separate, conditionally included “development” JavaScript file.

First, we set the __DEV__ global.

  plugins: [
    new webpack.DefinePlugin({
      __DEV__: (environment === 'development').toString()
    })
  ]

Then in the code:

if (__DEV__) {
  // development only code
}

Which, after Webpack is finished with it, turns into:

if (false) {
  // development only code
}

We run all our code though UglifyJS, which will actually see the block of code in the conditional as unreachable, and strip it from the resulting bundle for production.

The Good Part (React Hot Loader)

Webpack uses dark magic and socket.io to send chunked updates to your browser without even reloading the page. React Hot Loader builds on this to update React components in real time without losing their state.

React Hot Loader is a pretty finicky tool; I found that it was much easier to get it working by adhering as closely to this example as possible, and then gradually adding on app-specific configuration options and checking that it still works at each step. After some playing around, we had our assets served with webpack-dev-server, and our App components reloading on the fly.

gulp.task('webpack-dev-server', function(callback) {
  new WebpackDevServer(webpack(webpackDevConfig), {
    hot: true,
    contentBase: __dirname + '/build',
    publicPath: webpackDevConfig.output.publicPath
  }).listen(3000, 'localhost', function(err) {
    if (err) {
      console.log(err);
    }
  });
});

Feel free to scroll back up and watch that awesome GIF a few more times. I know I will.

Wrapping up Webpack

So far, Webpack has been an excellent solution to our bundling needs. It’s easy to set up, packs a ton of powerful features, and should feel pretty familiar coming from Browserify. I’m looking forward to tightening up our build even more — Webpack can bundle CSS and image assets as well as Javascript — and continuing to learn about how we can leverage Webpack’s power at React Week.