thereq

online coding interviews

[why?] [how?]

tech.thereq.com
the way we do the things we do

The Rails 3.1 asset pipeline in the real world: jQuery and the Google CDNs, page specific javascript, multiple JS manifests and more!

Overview

The new Rails 3.1 asset pipeline is based on a standardized set of best practices for managing CSS, Javascript and image assets. The fundamental guiding principles it follows are

  1. All assets URLs should have a unique “fingerprint” to prevent browsers from caching stale assets.
  2. An application should serve one single Javascript file and a single CSS file that contains all Javascript and CSS for the entire application. This includes third-party Javascript such as jQuery and jQuery UI.

Having the first principle satisfied “out the box” in Rails 3.1 is a little slice of web development heaven.

But, the second principle is more controversial. While we agree that having a single JS and CSS file is “a good place” to start, we feel that the real world is a little more subtle and requires a bit more finesse.

Here are the small adjustments we’ve made to make the Rails 3.1 asset pipeline more compatible with the real world.

Serving jQuery and jQuery UI from the Google CDN

Since the good folks at Google were kind enough to invite the world to pull the jQuery and jQuery UI libraries their CDN, it seems a shame not to take advantage of the offer.

Serving your base jQuery assets from Google’s CDN provides two benefits:

  1. First, it takes some load off your servers. If you have your own CDN, this probably isn’t terribly important.
  2. Second, and more importantly, it makes it much more likely that visitors to your website will already have these libraries in their browsers cache. If they do, the cost of loading these libraries becomes approximately zero.

To do this, in your Rails 3.1 application, first modify your application.js manifest so that it does not include jquery or jquery-ui. Leave the following line

//= require jquery_ujs

so that you still include the Rails UJS package.

Next, in your application.html.erb file, add the following:

<%= javascript_include_tag "https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js" %>
<%= javascript_include_tag "https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js" %>
<%= javascript_include_tag  "application" %>

Note - that the jQuery includes should come first. Also note, that you need to be a wee bit careful to keep your jQuery versions inline with the jquery_ujs javascript that comes bundled in the jquery-rails gem. To do this, just take a quick peek at what version of jQuery and jQuery-UI are currently being used by this gem and then use the same versions.

Managing controller or view specific Javascript

As soon as the Rails 3.1 asset pipeline made it painless to combine all CSS and Javascript into a single file, questions started popping up all over about how to get around this new philosophy, and continue to serve some controller/view specific Javascript.

We settled on the method first outlined here. Suppose we have a PostsController in our application and that we want to put all Javascript for the related views in a separate posts.js file. Furthermore, we do not want to include this Javascript for views associated with other controllers. To accomplish this:

  • Remove the //=require_tree . directive from application.js
  • Add the following helper in application_helper.rb

    def javascript(*files)
        content_for(:head) { javascript_include_tag(*files) }
    end
    
  • In the <head> section of your main layout file (application.html.erb) add

    <%= yield(:head) %>    
    
  • Create your posts.js file in the /app/assets/javascripts directory.

  • At the top of each view that should receive the posts.js Javascript add

    <% javascript 'posts.js' %>
    

Note that the posts.js file will still be managed by the asset pipeline and will still be treated with a fingerprinted URL and all that great asset pipeline stuff.

Multiple manifest files

In a Rails 3.1 application, the application.js file is really a manifest file. It does not, itself, contain any Javascript, instead it contains a set of directives that tell Sprockets how to assemble the compiled version from various pieces. For example, our application.js file looks like this:

//= require jquery_ujs
//= require common
//= require jquery-countdown

Which tells Sprockets to build a single application.js file that contains

  1. The Rails UJS library which it references from within the jquery-rails gem.
  2. The /app/assets/javascripts/common.js file we’ve created to hold all our non-controller specific Javascript
  3. The jQuery Countdown plugin which we have placed in /vendors/assets/javascripts/jquery-countdown.js.

For larger applications, one nice trick to know is that you can actually have multiple manifest files.

For example, suppose that we had one particularly complicated page in our application that requires five jQuery plugins. This page is not often accessed, and the plugins are not used anywhere else on the site.

One option is to simply include each of these plugins with require directives in the mainapplication.js file. This means that these plugins will be served and available to every page on our site. It also means that every page on the site will bear the cost of a larger Javascript payload.

A second option is to include each plugin manually at the top of our view:

        <% javascript 'plugin_1.js' %>
        <% javascript 'plugin_2.js' %>
        ...

There’s nothing wrong with this, but if this “set” of plugins actually needs to be used on two pages, you’ve created a bit of a maintenance burden.

A final option is to create a second manifest file, that uses Sprockets to group all these plugins. For example, create a manifest file /vendors/assets/javascripts/my_plugin_group.js which contains:

//= require plugin_1
//= require plugin_2
...

and then at the top of any view that needs to use that group of plugins you can include just one line:

<% javascript 'my_plugin_group.js' %>

Posted by cailinanne

Blog comments powered by Disqus