Brunch - A build system without the Grunt-work

Written by Alexander Hill

29. August 2013

Brunch is a front end build system that lets you get stuff done without needing a 500 line configuration file *cough* Grunt *cough*.

However, there’s little in the way of an introduction to setting up Brunch in an existing project - the homepage only shows how to use Brunch with it’s ‘skeleton’ scripts. Thankfully, getting your own project to work with Brunch is simple and painless.

Brunch vs Grunt

Grunt tries to be everything to everyone. As a result, Gruntfiles end up huge and complex, requiring you to specify every plugin used, what files to apply it to, and exactly what comes out. If you’re using a tool like Yeoman, a Gruntfile is written for you - but good luck if your setup changes.

Comparatively, Brunch is simple. Brunch compiles, concats and (optionally) minifies your scripts and styles. It can also package JavaScript files into AMD or CommonJS modules. Brunch automatically applies plugins in the correct order to the right files - so with the right plugins, a .coffee file would be converted into a .js file and then minified, with no explicit setup necessary.

Brunch has a few conventions that help keep things simple - but you don’t have to follow all of them. Firstly, Brunch asks you to specify a folder called ‘assets’ that is directly copied into your output folder with no modifications. Secondly, most Brunch projects have two separate JavaScript files - app.js, which contains your code, and vendor.js for all external libraries, including bower packages. This allows you to package your files into modules without affecting external libraries.

Prequisites

The first thing to do is install the brunch command by running sudo npm install brunch -g. You’ll also want to be using Bower for external packages, which Brunch has excellent support for.

Application Structure

The application we’ll be converting uses CoffeeScript, AngularJS, and LESS, and has no current build system beyond running the CoffeeScript and LESS watchers on the app/ directory. Here’s what the application structure looks like before we install Brunch:

|- app/ # this folder is served statically, with the compiled files living alongside the originals
|-- images/
|-- scripts/ # contains .coffee files, which are converted to .js files by coffee -wc
|--- components/ # components, installed by bower. Currently
|-- styles/ # contains .less files, which are converted into .css files by the less watcher
|-- views/ # angularjs views and templates.
|- index.html # the main app file. Includes <script> tags for every .js file and bower component
|- test/
|- server.coffee # our server file - statically serves the app directory when run.
|- component.json # bower package folder - still using the old version of bower
|- .bowerrc # bower config file that tells it to install into the app/components folder
|- package.json # npm package folder

The first thing we need to do is modify the application structure to fit Brunch’s conventions. That means moving installed packages to outside the app/ directory, and creating an assets folder for static files.

The assets folder will live inside app/. It needs to contain any files that will be served statically - in this case, just index.html along with the images/ and views/ folders.

For this project, we also had to delete the existing bower configuration file (.bowerrc) and components/ folder, rename the component.json file to bower.json before updating bower to the latest version (currently 1.2.6). Running bower install created a bower_components/ folder in the root project directory. This allows Brunch to easily identify which files are part of our app and which files are external libraries without having to write any complex regular expressions.

Finally, we need to update our index.html to be aware of the changes in structure. Currently, we have this in the head:

<link rel="stylesheet" href="/styles/bootstrap.min.css">
<link rel="stylesheet" href="/styles/main.css">

and this in the body:

<script src="/components/jquery/jquery.js"></script>
<script src="/components/lodash/lodash.js"></script>
<script src="/components/angular-unstable/angular.min.js"></script>
<script src="/components/angular-bootstrap/ui-bootstrap-tpls.js"></script>
<script src="/components/angular-sanitize/angular-sanitize.js"></script>
<script src="/components/angular-resource/angular-resource.js"></script>
<script src="/components/angular-cookies/angular-cookies.js"></script>
<script src="scripts/app.js"></script>
<script src="scripts/directives.js"></script>
<script src="scripts/data.js"></script>
<script src="scripts/services.js"></script>
<script src="scripts/controllers/landing.js"></script>
<script src="scripts/controllers/decision-tree.js"></script>
<script src="scripts/controllers/resource.js"></script>
<script src="scripts/controllers/task.js"></script>

Ouch! With Brunch, we can replace the css files with this:

<link rel="stylesheet" type="text/css" href="/css/app.css">

and the ridiculous number of script files with this:

<script src="/js/vendor.js"></script>
<script src="/js/app.js"></script>

Much better!

Configuring Brunch

Brunch configuration is incredibly simple. Here’s the config.coffee file for this project:

exports.config =
  conventions:
    assets: /^app\/assets\//
  modules:
    definition: false
    wrapper: false
  paths:
    public: '_public'
  files:
    javascripts:
      joinTo:
        'js/vendor.js': /^bower_components/
        'js/app.js': /^app\/scripts/
      order:
        before: [
          'bower_components/lodash/lodash.js'
          'bower_components/jquery/jquery.js'
          'bower_components/angular/angular.js'
        ]
    stylesheets:
      joinTo:
        'css/app.css': /^app\/styles/

(N.B: We’re using CoffeeScript here, but you can use raw JavaScript instead if you prefer)

Compare this to the Gruntfiles of projects such as jQuery UI(230 lines), Brackets (333 lines), or the default Gruntfile provided by Yeoman’s generator-webapp(450 lines!).

The configuration file is simply specifying what folders Brunch should look for and what it should do with them. So, using regular expressions we tell Brunch that the app/assets folder should be copied directly into the output folder, which we name _public (see the paths property).

The rest of this is fairly self explanatory, although it’s important to note that Brunch uses the new bower.json files to find packages located in bower_components - so only one of jquery.js and jquery.min.js will be included in vendor.js.

Finally, we’re also telling Brunch to include certain scripts before others in the vendor.js file, mainly to make sure that angular.js uses jQuery rather than its jQLite implementation.

Installing Brunch Plugins

Looking at the config file again, you’ll notice there’s no configuration or ‘registering’ of plugins - although most plugins can be configured, the default behaviour usually works with no configuration.

Brunch plugins are just npm packages. Any Brunch plugin that’s installed will automatically be used. Using npm install --save-dev plugin-name will install the package and update package.json.

Looking at the package.json file, we can see the plugins we’ll be using in this project:

{
  "name": "project",
  "version": "0.0.0",
  "dependencies": {
    "express": "latest",
    "mongojs": "latest",
    "mongoose": "latest",
    "underscore": "latest",
    "consolidate": "~0.9.1",
    "q": "~0.9.6",
    "webshot": "~0.5.1",
    "gm": "~1.11.1"
  },
  "devDependencies": {
    "javascript-brunch": ">= 1.0 < 1.8",
    "coffee-script-brunch": ">= 1.0 < 1.8",
    "css-brunch": ">= 1.0 < 1.8",
    "ngmin-uglify-js-brunch": "~1.7.2",
    "clean-css-brunch": ">= 1.0 < 1.8",
    "auto-reload-brunch": "~1.6.5",
    "less-brunch": "~1.5.2"
  },
  "engines": {
    "node": ">=0.8.0"
  }
}

Here we include the ‘base’ plugins for JavaScript and CSS, the CoffeeScript and LESS compilation plugins, the CSS minification plugin clean-css-brunch, and the ngmin-uglify-js-brunch - a plugin that runs JavaScript code through ngmin, the AngularJS ‘pre-minifier’, then uglifyjs to compress the code. We also use the auto reload plugin, which sets up a web socket server and refreshes the page whenever any files on disk are changed.

And that’s it! Brunch is now configured and ready to use.

Using Brunch

The brunch command has two main commands - brunch build and brunch watch. build runs the Brunch compilation process and immediately quits, whilst watch compiles everything then waits for changes on any of your files, and then immediately compiles and updates the files. Unlike Grunt, Brunch caches your files, so after the initial compile, the watch command is incredibly fast .

By default, minification is disabled. Brunch has a --optimize flag that will enable minification. However, this is deprecated in favour of the --production flag, which minifies the output files, removes source maps and disable the auto reload plugin.

More Info

For a list of Brunch plugins, take a look at the page on the brunch wiki - there are plugins for most popular compile-to-JavaScript and CSS languages, as well as support for a variety of templating languages.

The Brunch home page has more information on using Brunch, as well as a list of the skeletons available.

If you want to write your own Brunch plugins, take a look here.

Finally, if I’ve made any mistakes, or if you’re still not sure how to set up Brunch (or if you just want to say hi!), feel free tweet me and I’d be happy to help.