Backend, Meet Frontend: Managing Assets with Bower

This post first appeared on the Big Nerd Ranch blog.

TL;DR: Don’t manage vendor assets with Rails gems; use Bower and NPM. Check out the gist for quick reference.

So you’ve knocked out a few epics in Pivotal Tracker and have an MVP ready for Heroku, when your project manager announces that the Bootstrap-based frontend and choice of colors will likely turn off investors.

To mitigate your “button-and-text soup,” a graphic designer and frontend developer are brought on. Your beautifully simple Rails app is clobbered with masses of minified JavaScript and Convoluted Style Sheets, and your slim templates are littered with classes and data attributes.

While you aren’t enthusiastic about gutting your clever Bootstrap forms to make way for some unheard of grid system (which doesn’t even have buttons), you acquiesce in hopes of a year-end bonus for your admirable cooperation. That is, until the frontend developer commits a mass of unreadable, minified JavaScript to public.

Naturally, you advocate for Rails’ asset pipeline. When jquery-2.1.0.min.js gets chucked under app/assets/javascripts, you set folks straight and tell them that only code written specifically for the app, not vendor code, belongs there. Next pull request, you find those assets have been moved to lib/assets, which is a definite no-go.

To bypass further confusion, you kindly alert your cohorts that jquery is bundled in the Gemfile, and in fact the tooltip library that was committed to lib/assets/javascripts is part of the jquery-ui-bootstrap-rails gem. The gem integrates with the asset pipeline, and will be far more elegant than copy-pasting an unversioned .min.js to random directories.

Your unenlightened cohorts, in a moment of disgust, declare that they will be adding NPM to the pipeline for asset vendoring. You offer up coffee-rails and sass-rails, to which they promptly retort that frontend tooling written with Node.js will do the job better and faster.

“Frontend tooling?” Don’t frontend developers just stick minified junk in a directory? After all, Rubyists know JavaScript as the class-deprived single-file language of popups and scrolling marquees. It is the veritable poster child for languages bereft of module managers and plagued by global scope.

Leverage Your Strengths: Going our Separate Ways

Although we are loath to admit it, Rails developers have an infamous reputation among the web community. turbolinks was perhaps the most ironic blow to the face of client-side programming, but day-to-day Rails developers have a habit of littering templates with copious IDs and divs in an effort to appease their leaky JavaScript files.

When it comes to vendor assets, the web community shrieks at this increasingly common style of Gemfiles:

gem 'uglifier', '>= 1.3.0'
gem 'coffee-rails', '~> 4.0.0'
gem 'jquery-rails'
gem 'remotipart'
gem 'jquery-ui-rails'
gem 'jquery-ui-bootstrap-rails'
gem 'bootstrap-sass'
gem 'bootstrap-datepicker-rails'
gem 'pagedown-bootstrap-rails'
gem 'font-awesome-sass-rails'
gem 'chosen-rails'

In light of our shadowy past, what tools and workflows can we adopt to leverage conventions in both platforms while maximizing developer happiness?

Hello, Bower

Your inconstant developer friend (from a startup you used to work at) tweets about Bower. Okay! You add gem "bower-rails" to your Gemfile to suppressed shouts of joy from your frontend cohort, who dives in to add normalize.css and a clean grid system, only to find there is no bower.json file in the project. Confuzzled, your cohort searches the directory tree and stumbles upon the empty and extensionless Bowerfile.

Distressed, your cohort decides to create a bower.json configuration from scratch, then runs bower install. However, Rails doesn’t seem to know anything about bower_components.

While your colleague is trampling your beautiful Bowerified-Rails setup, you come across some interesting configuration options in config/application.rb while looking for a way to add app/assets/fonts to the asset pipeline. As always in Rails, you find it’s as simple as:

config.assets.paths << Rails.root.join("app","assets","fonts")

On the call, your frontend cohort says your Bower setup is “unconventional” and represents a mixup of responsibilities. Managing a Bowerfile to generate a bower.json is the epitome of irony: who ever heard of a Ruby DSL being used to generate JavaScript?

Your cohort has already stripped out bower-rails on a private branch and is able to install all components, but doesn’t know where to dump them for Rails’ asset pipeline. Despite your offense, you are struck with an epiphany: why can’t the bower_components directory just be added to config.assets.paths?

But not to seem deterred (the best developers are strongly opinionated), you advise that bower_components is a nonsensical place to put assets, as Rails has a directory for just such a purpose: vendor/assets. However, Bower won’t unpackage JavaScript and CSS files into separate directories. As a compromise, you decide to install Bower packages under vendor/assets/components and add it to the asset pipeline:

config.assets.paths << Rails.root.join("vendor","assets","components")

Delighted, your cohort quickly adds a .bowerrc to tell Bower exactly where to dump these packages on any subsequent bower install:

{
  "directory": "vendor/assets/components"
}

Deployment

Over the lunch break, the Gemfile has been stripped of gems even remotely related to assets—including gem "jquery-rails" (incredibly, you discover there is a nice Bower package with the pertinent rails-ujs file you still need for remote: true links, and your cohort is only too eager to take control of the jquery version from bower.json).

Satisfied with the pleasant compromise (and simultaneously relieved to relinquish responsibility of frontend assets), you merge your cohort’s lengthy pull-request (primarily subtractions) and deploy master to Heroku before scootering to the kitchen for a few sticks of string cheese.

To your chagrin, the deployment failed: during the asset compilation step, it complained that it couldn’t find jquery. Of course, you forgot to commit the files in vendor/assets/components; but you certainly don’t want to pollute Git history with millions of vendor additions. Obviously, Heroku ought to run bower install to grab the assets before compilation. But how?

Another pal suggests you look into custom buildpacks, an interesting Heroku feature that lets you bypass the language auto-detection normally used to compile apps. With some investigation, you find you can instruct Heroku to compile first as a Node.js app, then finish compilation as a Rails app with the buildpack-multi. It’s as simple as adding a .buildpacks file:

https://github.com/heroku/heroku-buildpack-nodejs
https://github.com/heroku/heroku-buildpack-ruby

After a couple attempts (the first time, you forgot to pluralize .buildpacks), it seems the file is ignored. A quick search, and you quickly update your BUILDPACK_URL:

heroku config:add BUILDPACK_URL=https://github.com/ddollar/heroku-buildpack-multi.git

Just before you hit return, your smartphone buzzes with an email from Heroku: multi-buildpacks are now officially supported on their own fork, so you update the BUILDPACK_URL again:

heroku config:add BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-multi.git

It seems that Node.js apps expect a package.json manifest, but what for? Your cohort reminds you that Bower is itself an NPM package, so you should install and run it as part of the build process:

{
  "name": "my-app",
  "devDependencies": {
    "bower": "latest"
  },
  "scripts": {
    "postinstall": "./node_modules/bower/bin/bower install"
  }
}

On your last stick of string cheese, the deploy completes successfully with no apparent changes to your site. You and your cohort are so pleased, you decide to present it to your coworkers as a Tech Talk.

Wrap-up

Want the Cliff notes version? I’ve compiled the additions into a gist.

Do you have other patterns or tools you find enhance collaboration between frontend and backend?

Jonathan Martin

Jonathan Martin

Globe-trotting web developer, instructor, international speaker and fine art landscape photographer from Atlanta, GA.

Read More