The Brunch Build Tool

One of the most important parts of CodeCombat’s development workflow is Brunch. In this post I’ll tell you why CodeCombat chose Brunch, run through setting up a basic Brunch project, explain CodeCombat’s Brunch configuration, and compare Brunch to several popular alternative build systems.

Why CodeCombat uses Brunch

In early 2013, CodeCombat was just getting started. One of the cofounders, Nick, wanted a workflow skeleton to kickstart development. After examining several different tools, including Brunch and Yeoman, he ended up choosing Brunch, mainly because of the awesome project skeletons available.

As time went on and the build times grew and grew, we appreciated one design choice of Brunch more and more: incremental rebuilding and caching. For small projects, the time difference between partial and complete rebuilds isn’t very significant. However, for large projects, long build times can be very detrimental to developer efficiency and happiness. Doing a complete rebuild of CodeCombat can take a few minutes on older computers, whereas partial rebuilds only take a few seconds, if that. Such responsiveness is integral to our development process.

Setting up a basic Brunch project

First, we need to install Brunch. Once you have npm installed, just run:

All we have to do now is start Brunch. Move into the new directory, and then start the Brunch watcher/server.

1

2

3

cdjetbrains

brunch watch--server

Now any time we make a change to our app, Brunch will automatically recompile the files that changed, rebuild the app, and refresh the page. On my machine, this takes only about 70ms.

Let’s change the header on the front page. Open up your favorite IDE and start editing app/partials/todo.jade. In addition, open a web browser to the URL Brunch provided, in my case, http://localhost:3333.

Notice how, if you change something, for instance the <h2>, the app automatically is rebuilt and the page refreshes.

The file starts off by importing the path module and declaring a utility function, startsWith.

1

2

3

4

5

6

7

8

exports.config=

paths:

'public':'public'

conventions:

ignored:(path)->startsWith(sysPath.basename(path),'_')

sourceMaps:true

Then, we tell Brunch to place all compiled files in the public folder, ignore all paths which start with an underscore, and generate source maps.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

files:

javascripts:

defaultExtension:'coffee'

joinTo:

'javascripts/world.js':///^(

(app[\/\\]lib[\/\\]world(?![\/\\]test))

|(app[\/\\]lib[\/\\]CocoClass.coffee)

|(app[\/\\]lib[\/\\]utils.coffee)

|(vendor[\/\\]scripts[\/\\]Box2dWeb-2.1.a.3)

|(vendor[\/\\]scripts[\/\\]string_score.js)

)///

'javascripts/app.js':/^app/

'javascripts/vendor.js':///^(

vendor[\/\\](?!scripts[\/\\]Box2d)

|bower_components[\/\\](?!aether)

)///

'javascripts/vendor_with_box2d.js':///^(

vendor[\/\\]

|bower_components[\/\\](?!aether)# include box2dweb for profiling (and for IE9...)

)///

'javascripts/lodash.js':///^(

(bower_components[\/\\]lodash[\/\\]dist[\/\\]lodash.js)

)///

'javascripts/aether.js':///^(

(bower_components[\/\\]aether[\/\\]build[\/\\]aether.js)

)///

'javascripts/test-app.js':/^test[\/\\]app/

This section of code specifies how the various production JavaScript files should be compiled.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

order:

before:[

'bower_components/jquery/dist/jquery.js'

'bower_components/lodash/dist/lodash.js'

'bower_components/backbone/backbone.js'

# Twitter Bootstrap jquery plugins

'bower_components/bootstrap/dist/bootstrap.js'

# CreateJS dependencies

'vendor/scripts/easeljs-NEXT.combined.js'

'vendor/scripts/preloadjs-NEXT.combined.js'

'vendor/scripts/soundjs-NEXT.combined.js'

'vendor/scripts/tweenjs-NEXT.combined.js'

'vendor/scripts/movieclip-NEXT.min.js'

# Validated Backbone Mediator dependencies

'bower_components/tv4/tv4.js'

# Aether before box2d for some strange Object.defineProperty thing

'bower_components/aether/build/aether.js'

'bower_components/d3/d3.min.js'

'vendor/scripts/async.js'

]

This section specifies all of the files that should be loaded before the others are compiled.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

stylesheets:

defaultExtension:'sass'

joinTo:

'stylesheets/app.css':/^(app|vendor|bower_components)/

order:

before:[

'app/styles/bootstrap.scss'

'vendor/styles/nanoscroller.scss'

]

templates:

defaultExtension:'jade'

joinTo:'javascripts/app.js'

framework:'backbone'

This section specifies how the stylesheets and templates should be compiled, as well as the skeleton framework.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

plugins:

autoReload:

delay:300

coffeelint:

pattern:/^app\/.*\.coffee$/

options:

line_endings:

value:"unix"

level:"error"

max_line_length:

level:"ignore"

no_trailing_whitespace:

level:"ignore"

no_unnecessary_fat_arrows:

level:"ignore"

uglify:

output:

semicolons:false

This specifies the plugins we use: auto-reload is used to refresh the browser whenever Brunch recompiles, coffeelint-brunch is used to keep our CoffeeScript files clean, and uglify-js-brunch is used to, well, Uglify our code.

1

2

3

4

5

6

7

8

9

onCompile:(files)->

exec=require('child_process').exec

regexFrom='\\/\\/# sourceMappingURL=([^\\/].*)\\.map'

regexTo='\\/\\/# sourceMappingURL=\\/javascripts\\/$1\\.map'

regex="s/#{regexFrom}/#{regexTo}/g"

forfileinfiles

c="perl -pi -e '#{regex}' #{file.path}"

execc

The final step is to replace the source map URLs in the compiled files with a small Perl regex.

Why Brunch

One of the first decisions that developers of JavaScript web applications have to make is which build tool to use; they’ve never had so much choice. They can use Grunt, Gulp, Brunch, Broccoli, Mimosa, or Jake, just to name a few. So how does one choose the right tool for the job?

Let’s compare Brunch with Grunt and Gulp, two of the most popular JavaScript build tools.

Grunt

Grunt is arguably the most popular build tool for JavaScript projects. It sports the largest ecosystem and a great amount of flexibility (WebStorm even has built-in Grunt integration!). However, Grunt configuration can often be complicated and result in larger configuration files than Gulp or Brunch. Grunt is great out of the box for small to medium sized projects, but the lack of out-of-the-box incremental builds and file caching makes it necessary to use plugins like grunt-newer to get short build times with very large projects, which may further increase configuration complexity. In addition, Grunt doesn’t have the level of support for project skeletons that Brunch does; often another tool is used, such as Yeoman.

Gulp

Gulp, the newest system of the three, is a bit different from either Brunch or Grunt in that it uses Node streams to achieve high performance. Its ecosystem isn’t as large as Grunt’s, but it’s still pretty large with 600+ plugins. The configuration files are very simple. Like Grunt, it doesn’t have great skeleton support or caching or incremental rebuilds built in, but has well-supported plugins which provide that functionality. Those plugins combined with the streams make Gulp the winner in terms of pure performance.

In Conclusion

You really can’t go wrong these days with build tools as long as you consider the anticipated scope, size, and technology requirements of your projects. Grunt has the largest ecosystem and most flexibility, Gulp has the best performance, and Brunch provides the easiest setup and great out-of-the-box performance. At CodeCombat, we’re very satisfied with Brunch, but can’t wait to see what the future holds in terms of new and improved build tools.