One of the “complementary tools” to React (and the one recommended by many example apps I’ve encountered) is Webpack: a self-described “module bundler” that’s also commonly used to compile and process images, CSS, and other soon-to-be-static assets. Though Webpack is often suggested, I encountered several problems that led me to look elsewhere for setting up a build task. In this post, we’ll look at a few of those problems and what I ended up replacing Webpack with to solve them.
TL;DR: If you want to jump straight to the good parts, I’ve created an example app hosted on GitHub that covers everything in this post.
A (Web)pack of Trouble
When I was first getting started with React, I found the guides to be lacking with regards to setting up and maintaining a local development environment. Admittedly, I’m very spoiled by the Ember ecosystem and Ember CLI, but I eventually happened on “The React.js Way” blog series, which advocates using Webpack for this purpose. I started using it, but quickly ran into a number of issues:
Regular expressions. Web pack uses “loaders” to transform files, which rely on regular expressions to select the files to be transformed. Coming from the world of Grunt and friends, which uses very readable strings like
src/**/*.js to represent files, expressions like
/src\/.+.js$/ make my head hurt, and I’m utterly unable to create new expressions without a reference guide.
Magic strings. Once you’ve managed to select your files using arcane RegEx strings, you’ll need to tell Webpack how to transform them. Webpack implicitly imports these loaders, then relies on string parsing to figure out what to do to the files, and in what order. Here’s an example from the Webpack documentation:
Confusing documentation. Your mileage may vary, but I had a heck of a time deciphering the official Webpack documentation. I mostly relied on other confused individuals who had divined the secrets of Webpack’s functionality and blogged about it.
I normally try to stick with new concepts for a while when learning a new language/library/framework; after all, these ideas and tools typically exist for a reason, and it can sometimes take a while to really grok them. With Webpack, though, I kept running into the same issues, with nothing but hacky-feeling workarounds to “solve” them. So, instead, I went back to the drawing board to look for an alternative.
Eat Your Vegetables
If you’ve spent any length of time talking shop with me, you’ve probably heard me mention Broccoli. If you haven’t, here’s the gist: I’m a huge fan. I find it insanely simple to start with, yet solidly scalable when the need arises. Massive build systems have been built from it (read more about the aforementioned Ember CLI if you’re interested), yet even at that level of complexity, it’s pretty straightforward to work with.
When I finally made the decision to ditch Webpack, I first thought of Broccoli, even though I honestly wasn’t sure if it would solve all of my issues. I wanted, in no particular order, something that would:
- Let me use React and libraries written for it (most of which used calls to
require(), meaning I couldn’t just stick more
<script>tags on a page);
- Run all of this stuff in a web server.
As it happens, Broccoli’s pretty amazing at all of these, even with the JSX compilation (the cause for most of my uncertainty). Let’s see how to set up a simple React app with it.
Replacing Webpack with Broccoli
Taking the requirements from the previous section, here’s what we want Broccoli to do:
- Bundle the scripts together, respecting any dependencies using
- Process our CSS from, say, Sass.
- Serve everything from a web server.
To start with, let’s install Broccoli. It’s an npm package, so you’ll probably want to run
npm init and get yourself a
package.json file you can save these dependencies to first. After that, install Broccoli:
$ npm install -g broccoli-cli
The first command installs the Broccoli binary, while the second tells our app that it’ll need Broccoli as a dependency. Next, let’s make a simple Broccoli build file, named
Brocfile.js. This file is a node module, so we’ll be using calls to
require() to bring in dependencies, and expose any outputs with
Broccoli works primarily with directory paths, called “trees”, that represent files and folders in your app. If we wanted Broccoli to act as a passthrough and not do any real processing, all we’d need in
Brocfile.js—assuming our app assets are in an
module.exports = 'app';
We need a bit more than this, so let’s continue. First, we’ll need to install React, since our code depends on it. You’ll use a similar method if you want to install other packages on which your runtime code depends. Install it with the following npm command:
$ npm install --save react
--save-dev, not just
--save, since they’re used for building, and not running, this app.
$ npm install --save-dev broccoli-babel-transpiler
require() calls. Here’s what our Brocfile looks like now, after we’ve imported and used these two packages:
var browserify = require('broccoli-browserify');
Broccoli lets us pass trees (like
app in the above example) through varying processing steps, which will be executed in order (and, if you ask me, with far less confusion than Webpack’s approach).
Let’s move on to Objective #3: processing CSS. There are a number of CSS preprocessors, and a number of Broccoli plugins for them, but let’s go with one of my favorites, Sass. Install the
broccoli-sass package with
npm install --save-dev broccoli-sass, and point it at your CSS directory in
var compileSass = require('broccoli-sass');
I’ll leave the explanation of this plugin to the
broccoli-sass documentation, but, in essence, we grab the uncompiled Sass code from
app/styles and transform it, starting with
At this point, we’ve run into a bit of a problem. With
broccoli-merge-trees. This package, hopefully unsurprisingly, can merge our two (or more!) trees together. Here’s an example where we also bring in a
var merge = require('broccoli-merge-trees');
Let’s then create an
index.html file that will represent the starting point of our app. We’ll stick this file in the
public directory since it doesn’t require any special processing or compilation. You may notice that the build steps we’ve created output two files:
app.css. We’ll want to reference these in this HTML file like so:
What about Objective #4? You’ll be pleased to know Broccoli comes with a simple web server preinstalled! To start it, run:
$ broccoli serve
http://localhost:4200/ in your favorite web browser (I don’t know where the port number comes from, but I like to think it’s inspired by the late Douglas Adams). If all goes well, you should see your React app in all its compiled and bundled glory!
If you’ve been fighting with Webpack for building your React apps—or if you, too, struggle with setting up a React dev environment—why not give Broccoli a try? Alternatively, if you’re a die-hard Webpacker, or if you’ve found another build tool to solve Webpack’s issues, I’d love to hear what’s keeping your React apps built!