Backend, Meet Frontend: Managing Assets with Bower

TL;DR: Don’t manage vendor assets with Rails gems, use Bower and NPM. Checkout 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,” he decides to hire a graphic designer and frontend developer to clobber your beautifully simple Rails app with masses of minified JavaScript, Convoluted Style Sheets, and litter your slim templates 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. At least, until the frontend developer commits a mass of unreadable, minified JavaScript to public.

Naturally, you scold him and inform him of Rails’ asset pipeline. When he chucks jquery-2.1.0.min.js under app/assets/javascripts, you set him straight and tell him that only code written specifically for the app belongs there, not vendor code. 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 cohort that jquery is bundled in the Gemfile, and in fact the tooltip library he 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 cohort, in a moment of disgust, declares he will be adding NPM to the pipeline for asset vendoring. You offer up coffee-rails and sass-rails, to which he promptly cites frontend tooling written with Node.js that will do the job better and faster.

“Frontend tooling?” Don’t they just stick minified junk in a directory? After all, Rubyists know that JavaScript is 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. i.e. Going our Separate Ways.

Although we are wont to admit it, Rails developers have an infamous reputation among the web community. turbolinks was perhaps the most ironical 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. He dives in to add normalize.css and a clean grid system, only to find there is no bower.json file in the project. Confuzzled, he searches the directory tree and stumbles upon the empty and extensionless Bowerfile.

Distressed, he decides to create a bower.json configuration from scratch, then runs bower install. However, Rails doesn’t seem to know anything about bower_components, so he schedules a video conference with you.

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 upbraids your Bower setup on grounds that it is “unconventional” and represents a mixup of responsibilities. Managing a Bowerfile to generate a bower.json is to him the epitome of irony: who ever heard of a Ruby DSL being used to generate JavaScript?

He continues: he says he already stripped out bower-rails on his (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 him 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 frontend 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, Mr. Bower has stripped the Gemfile of gems in the least related to assets. He even has the audacity to strip out 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 (not the one from the startup) 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?