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
directorykey sets the destination path, - the
jsonkey 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. :)
