af83

Managing Rails assets with Bower

Web applications have been shifting from server to client, and with them a lot more Javascript code and stylesheets are written and, hopefully, re-used. While this is quite an obvious statement, it means that we need an efficient way to manage those assets.

I'd like to write then that this is a simple matter, and that there is one way to do it. But let's face it, every Javascript coder out there has their own ideal solution. So yes we have plenty, which is probably a good thing in a not-immediately-obvious way.

This article is to be pragmatic and short however. As a consequence, I will write about accommodating Rails and Bower, without mentioning AMD, or projects like component that try to solution the “assets problem” in Rails lingo. I hope, we can write about these later on though.

How do you manage vendor assets in Rails?

Well as rubyists go, gem search is a common approach to solving problems. And surely enough there are rubygems that bundle Rails-engines with a bunch of static assets.

For example, you can add the following line to your Gemfile to have the BackboneJS library served through a Rails engine:

gem 'backbone-rails', '~> 0.9.2'

When the asset pipeline requires the Backbone library, it is served by backbone-rails. It looks a little like a case of hammer-nail syndrome: using Ruby packages for Javascript? However some gems also provide Rails generators to save developers the hurdle of bootstrapping their SPA (and a handful of mkdirs). I have mixed feelings about this whole deal: it feels like a hack, but it is a very practical one.

There is another way of managing assets around there. I call it the ostrich approach and it's quite possibly the dirtiest. It merely consists of using wget, or curl, to throw those annoying Javascript files into your vendor directory, and forget about them. Great idea! (j/k it's stupid, don't do that.)

All in all, rubyists do not have much tools to manage static assets.

At af83 we've been playing with Twitter's Bower to manage our Rails app assets for a little while. If you never heard of Bower, imagine Bundler for Javascript code, stylesheets, images: components. And just maybe, have a crash course on how it's used.

Wait… What about Sprockets?

Sprockets is the core of the Rails asset pipeline. It is a very nice tool to help compile/compress your assets in a bandwidth-friendly way. For all the trouble it saves you from, you should love Sprockets. Say it loud now: “I love Sprockets”.

Then again, to be honest, it would be nice if Sprockets also did Bower's job: for example, it could just work should I throw a component.json at it — or, since we're in Ruby land have it load some YAML with a list of components. Maybe that's akin to feature creep though, and a separate gem would keep things simpler.

But that's for another post I hope. Meanwhile, let's see how we can use Bower with Rails.

Setup Bower with Rails

Bower is a step in the right direction: it lets you declare a browser application's dependencies, and their versions, explicitly. It is a (young) nodejs program though, so it doesn't integrate seamlessly with your vanilla Ruby/Rails stack.

The hacker way

Let's start by doing things the hacker-way, and have Bower speak Railsish.

Setup Bower

Using bower to fetch static assets for rails is simple. The first step is to setup a .bowerrc in your Rails root that looks like this:

{
  "directory": "vendor/assets/components",
  "json": "component.json"
}
  • The directory key sets the destination path,
  • the json key sets the name of the configuration file.

If the .bowerrc file is missing, Bower will merely reset to its defaults, and install components in the ./components directory.

Second step: specifying Bower dependencies. Use your favorite editor and open the component.json file. Here's a sample that will load the jQuery, Backbone and Lodash libraries:

{
  "dependencies": {
    "jquery": "~1.8.3",
    "backbone": "0.9.9",
    "lodash": "https://raw.github.com/bestiejs/lodash/v1.0.0-rc.3/lodash.underscore.js"
  }
}

Dependencies can be set by version, git repository, local directory, or even links to remote files: check Bower's documentation for a complete list of what you can do here.

Finally, run bower install to fetch and install the dependences:

$ bower install
bower downloading https://raw.github.com/bestiejs/lodash/v1.0.0-rc.3/lodash.underscore.js
bower cloning git://github.com/documentcloud/backbone.git
bower cached git://github.com/documentcloud/backbone.git
bower fetching backbone
bower cloning git://github.com/components/jquery.git
bower cached git://github.com/components/jquery.git
bower fetching jquery
bower checking out jquery#1.8.3
bower copying /home/oz/.bower/cache/jquery/cf68c4c4e7507c8d20fee7b5f26709d9
bower checking out backbone#0.9.9
bower copying /home/oz/.bower/cache/backbone/f184345e8f03dbe160c843ce1b7248eb
bower installing jquery#1.8.3
bower installing backbone#0.9.9
bower installing lodash

If you check the vendor/assets/components directory, each dependency is now sitting under its own subdirectory.

Setup Rails

Now that we fetched some static files, they must be loaded through Rails' asset-pipeline. Since we installed those files under vendor/assets/components, we need to add a line to config/application.rb to add the directory to our assets path:

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

(Don't forget to restart your Rails server after this one.)

Your assets are now ready for consumption. Sprockets will happily load them…

But there's a catch: unless you're running the latest release of Sprockets (starting at ~> 2.6) you can not require jQuery by applying the usual //= require jquery. Instead, you have to //= require jquery/jquery.

Why is that? Well, Bower stores the libraries where you tell him, under neatly ordered directories. Bower also expects the library to come with his own component.json to describe itself: name, authors, version, etc. More importantly in our case, this file should point to an entry point. In jQuery's case, you will have to require the library with //= require jquery/jquery. Meh.

In fact, you may be left on your own to have a look a each component's directory in order to require the appropriate file; that is, unless you're running a "recent" release of Sprockets that will do just that for you (hang in there, Rails 4 is coming). Did I mention how much I love Sprockets?

Setup your VCS

Ignoring the vendor/assets/components directory is a good idea since you don't want to commit more than your components.json file. If you're using Git, the following should do the trick:

$ mkdir vendor/assets/components
$ touch vendor/assets/components/.gitkeep
$ git add vendor/assets/components/.gitkeep
$ echo /vendor/assets/components >> .gitignore
$ git add .gitignore
$ git commit -m 'Setup bower components directory'

And that's all there is to it.

Using the bower-rails gem

Why, of course there is a gem that does all the tedious work for you. And it's quite simple too.

Ross Harrison's bower-rails adds two rake tasks to read a list of components from a bower.json file. Those are then installed under vendor/assets/javascripts/, or lib/assets/javascripts directories. The naming is a bit off when/if you're installing things like Twitter Bootstrap, that contain stylesheets, but in the sake of simplicity let's ignore this for now.

Here is a sample bower.json file straight from the project's README:

{
    "lib": {
        "dependencies": {
            "threex"      : "git@github.com:rharriso/threex.git",
            "gsvpano.js"  : "https://github.com/rharriso/GSVPano.js/blob/master/src/GSVPano.js"
        }
    },
    "vendor": {
        "dependencies": {
            "three.js"  : "https://raw.github.com/mrdoob/three.js/master/build/three.js"
        }
    }
}

What the gem really does is chdir here and there, rewrite this JSON to a valid Bower format, and exec bower install. It's easy as pie, and painless for Ruby developers.

Wrapping up

Although I really like Javascript (and its bizarre quirks), I don't like to add heterogeneous tools to a developer's environment when it can be avoided: increased complexity, and maintenance, are not helping your productivity. I won't argue in favor or against nodejs here. However I doubt you prefer downloading files into vendor/, over having a program do the job better that you would. Bower is simply too good to ignore. :)

blog comments powered by Disqus