Broccoli is a build tool that provides a simple API that can be used to construct your own asset pipeline that runs build steps on your source code as it changes. Broccoli comes with a simple static file server built-in that watches your source files and performs incremental rebuilds as files change, making it perfect for front-end development.

The Broccoli Github page provides the following summary.

A fast, reliable asset pipeline, supporting constant-time rebuilds and compact build definitions. Comparable to the Rails asset pipeline in scope, though it runs on Node and is backend-agnostic.

The central concept in Broccoli is the tree, which can be thought of as similar to a filesystem directory. Each tree in the build consumes one or more trees as input and produces a single (directory) tree as output, and this process may alter its input in some way as part of the process. For example, a Broccoli plugin for compiling SASS will take one or more trees containing .scss files as input, and produce a .css file as output.

What About Grunt or Gulp?

Broccoli is a new entrant into the already well-serviced Javascript build tool space, following on the popularity of Grunt and more recently Gulp. Although there's definitely overlap between these tools, Broccoli is really quite a different beast: it's actually a dedicated build tool, as opposed to the others which are task runners, in the tradition of make, rake and friends.

Those familiar with the Rails ecosystem, can think of Broccoli as being more similar to the Rails asset pipeline, as opposed to Grunt and Gulp, which are more similar to rake (a task runner).

Many projects do use Grunt or Gulp as a build tool as well as a task runner, but it can be difficult to keep builds fast as the size of the project and complexity of the build process grows. That's where Broccoli shines, its architecture allows for fast builds as the size and complexity of the project grows.

Architecture

Broccoli is broken into the CLI and the tool itself. This allows each project to target a particular version of the tool in the package.json. The broccoli-cli package simply provides an executable broccoli that invokes the Broccoli module that's installed in the current directory.

The simplest way to specify a tree is to simply pass a string path - this will create a tree containing the contents of that path (all paths are relative to the package.json) that watches and rebuilds on changes. In general, wherever a tree can be provided you can specify a string path instead.

A Broccoli tree has a remarkably simple API - it's an object that provides two methods:

These two methods (in conjunction with some simple rules) are all that's required to integrate with Broccoli.

Here's an example of a very simple but entirely useful type of Broccoli tree:

function unwatchedTree(dir) {  
  return {
    read:    function() { return dir; },
    cleanup: function() {}
  };
}

This function returns a tree containing the contents of dir, and doesn't watch for changes.

Getting Started

npm install broccoli-cli --global # install the broccoli executable  
npm install broccoli --save-dev   # add broccoli to the current project  

A Simple Example

The following example is simple, but actually pretty darn useful. Save the following code into Brocfile.js to serve the public/ directory:

// Brocfile.js
module.exports = 'public/';  

Now you simply run broccoli serve to fire up a webserver that watches your files for changes, rebuilds on the fly, and has LiveReload support built-in. Any files that you put into public/ will be available at http://localhost:4200.

Then run broccoli build dist to produce a build in dist/ when you're ready to deploy.

The code for the following examples can be accessed using the tags on this Github repo. This example is tagged as example-1.

n.b. You'll need to use the LiveReload plugin for Chrome or Firefox unless you want to add the LiveReload script snippet into your index.html.

The Basics

Okay, let's concatenate all our CSS and Javascript into single files. These examples assume the following directory structure:

app/  
  app.js
  styles/
    app.css
public/  
  index.html

Here's the Brocfile (and the full code here):

// Brocfile.js
var concat = require('broccoli-concat'),  
    pickFiles = require('broccoli-static-compiler'),
    mergeTrees = require('broccoli-merge-trees');

// concat the JS
var scripts = concat('app/', {  
  inputFiles: ['**/*.js'],
  outputFile: '/assets/scripts.js'
});
// concat the CSS
var styles = concat('app/styles', {  
  inputFiles: ['**/*.css'],
  outputFile: '/assets/styles.css'
});
// grap any static assets
var public = pickFiles('public', {  
  srcDir: '.',
  destDir: '.'
});
// and merge all the trees together
module.exports = mergeTrees([scripts, styles, public]);  

Add a Pinch of SASS

Now we're going to add the broccoli-sass plugin, which uses node-sass for lightning-fast SASS compilation (and also provides support for source maps too).

Now we just add var compileSass = require('broccoli-sass') to the requires at the top of our Brocfile and then replace the var styles = ... with the following (code here):

var styles = compileSass(['app/styles'], 'app.scss', '/assets/styles.css');  

compileSass() takes an array of input trees (hence the ['app/styles'] argument), and this is also how you provide include paths to the SASS compiler.

And a Dash of Sweet ES6

Why wait for the browsers to add arrow functions, destructuring assignment and class syntax when you can start using them right now? There's broccoli plugins for jstransform and esnext, and I like to use jstransform because it's minimal and preserves line numbers.

Now we just add var compileES6 = require('broccoli-jstransform') to the requires at the top of our Brocfile and then add the following after var scripts = ... (code here):

scripts = compileES6(scripts);  

Huzzah! 21st century JavaScript is here at last ;)

In Summary

I've found Broccoli to be a really great tool. It's got a wonderfully simple API and a wide range of good quality plugins. We use it for a variety of tasks including:

While Broccoli can be used directly, I think developers will interact with higher-level tools that provide a predefined asset pipeline based on Broccoli, providing a set of conventions targeted at a specific use case. This has already happened with ember-cli (the main tooling for building Ember.js projects), and I expect we'll see similar things for AngularJS, React and friends.