Rails to Phoenix: taking the asset pipeline out for brunch

Like many developers that have been focused on web development using Rails, I've been excited about the arrival of the Phoenix Framework, with its promise of the productivity without trading off speed.

Going through the guides, it was easy to get up and running quickly. Phoenix feels very familiar to Rails, but only on the surface. Digging deeper, a new language, a different programming paradigm, and new frameworks and libraries all offer a lot of to get acquainted to.

This post and subsequent ones will be aimed at become productive in Phoenix from a Rails perspective.

So what's (for) brunch?

If you've used Rails to build more than an API, you're probably familiar with the asset pipeline. Although it's quite powerful, the tight integration with Rails can be problematic.

Phoenix takes a different approach - it vendors a JavaScript tool called brunch. Brunch differs from the more commonly-known grunt and gulp by being laser-focused; while grunt and gulp can be used as more generic task runners, brunch is only intended to be used as a build tool.

Phoenix's direct integration with brunch is very loose - it expects static assets to be made available in a specific directory, and it runs brunch watch in development mode to automatically recompile assets as they're changed. In fact, you can easily create a Phoenix project without brunch by running mix phoenix.new --no-brunch when creating a project, and use another tool for assets. You can even configure your own watch command.

I'll take one of everything

In a newly-created Phoenix project, Brunch is configured to watch static assets in web/static. It builds those assets into priv/static, where Phoenix serves them from. Phoenix includes js, css, vendor, and assets subdirectories. Here's the part of brunch-config.js that makes that possible:

exports.config = {
  conventions: {
    assets: /^(web\/static\/assets)/
  paths: {
    watched: [
    public: "priv/static"

If you examine app.html.ex, you'll see that Phoenix includes the app.js and app.css bundles in your pages. Brunch produces these files by concatenating the appropriate file types from the js, css, and vendor directories. The assets directory I mentioned above is copied wholesale into priv/static - this directory is intended for files that don't need to be processed.

The concatenation of the two asset bundles is done with this config:

files: {
  javascripts: {
    joinTo: "js/app.js"
  stylesheets: {
    joinTo: "css/app.css"

When I saw this configuration, I was struck by how little there was, but I wondered how Brunch properly orders files. For example, we'd run into an error if we loaded jQuery after JavaScript that uses it.

Brunch's philosophy of convention-over-configuration really comes through here. Brunch understands vendored scripts should come first, and it understands the "vendor" prefix in file paths. With the Rails pipeline, you'd have to require every file. Brunch just assumes a reasonable default for you. If you'd like to customize the order, you can, for example:

javascripts: {
  joinTo: "js/app.js",
  order: {
    before: [

Later, we'll see that ordering concerns become less important since brunch wraps your files in CommonJS modules.

Also, if you'd like more than one bundle for a particular file type, brunch can do that too:

javascripts: {
  joinTo: {
    'js/app.js': /^app/
    'js/vendor.js': /^vendor/      

How about some coffee?

Out of the box, new Rails project's include support for CoffeeScript and SASS via the asset pipeline, through the sass-rails and coffee-rails gems.

We can add CoffeeScript support by installing a brunch plugin:

npm install coffee-script-brunch --save 

You're all set - your CoffeeScript files within your watched paths will be compiled automatically. While we're at it, let's add support for sass as well:

npm install sass-brunch --save 

And add your *.sass and *.scss files and go! A big improvement that brunch brings here is automatically creates source maps for your compiled sass and CoffeeScript files, which the asset pipeline doesn't do out-of-the-box.

Get tomorrow's brunch, today

However, in moving to brunch, you may end up deciding to abandon CoffeeScript altogether. New Phoenix projects include babel, a JavaScript compiler that lets you use ES2015 features in your applications.

Babel lets you use cutting-edge javascript safely in any browser - which includes features around classes, string interpolation, and destructuring that you may be familiar with from CoffeeScript. For me, one of the most compelling features is javascript modules.

JavaScript modules let you organize your code, specify dependencies explicitly, and avoid polluting the global namespace. Brunch automatically wraps each JavaScript file (except those in the vendor directory) in a CommonJS module, and sets up CommonJS's require function that lets you pull in dependencies.

For example, suppose you've implemented a basic calculator in a file named calculator.js:

class Calculator {
  static add(x, y) {
    return x + y;

With Babel integrated, you can simply export your class with ES2015 syntax by adding:

export default Calculator;

When you want to use your module, you import it:

import Calculator from "./calculator";
Calculator.add(12, 20)

In its seamless support for modules, brunch goes beyond being a build tool and helps you change the way you structure your JavaScript application.

Bring your friends along

Most front-end development these days involves working with libraries and frameworks. For Rails, the solution usually amounts to downloading files into vendor/assets or finding a gem with rails assets, and including the JavaScript with a sprockets require.

Brunch has built-in support for using bower as a package manager (support for npm is a work in progress). Let's add bootstrap-sass to our project, so we can customize it by tweaking its variables.

bower init 
bower install bootstrap-sass --save

Brunch will read your bower.json and concatenate all of your dependencies into your bundles automatically, including them in the appropriate order.

You'll notice jQuery and bootstrap JavaScript have been loaded. However, if you try to @import 'bootstrap' from within your stylesheets, you'll notice sass is unable to find the import. To fix this, we'll need to configure the plugin in brunch-config.js:

plugins: {
  babel: ...,
  sass: {
    options: {
      includePaths: ['bower_components/bootstrap-sass/assets/stylesheets']

A note about digestion

If you're familiar with the asset pipeline, you know that it generates digested files to enable aggressive caching headers.

Although brunch has plugins that can accomplish this task, Phoenix chooses to handle it through its own mix task. Phoenix does this so that you can use functions like static_path in your templates and get proper URLs in development as well as production. For the most part, it works as you'd expect - however, it's worth noting that it does not currently digest URLs in your stylesheets.

I couldn't eat another bite...

Coming from the asset pipeline, it took me a bit to get used to brunch. But now that it's familiar, I find it preferable, over the asset pipeline as well as the grunt and gulp alternatives I've used in the past. The simplicity, healthy assortment of plugins, and speedy recompilation is excellent - combined with Phoenix's built-in livereload, front-end development is a pleasure.

If you'd like to learn more about brunch, there's a great guide available on GitHub.