The new asset pipeline is the single most important new feature in Rails 3.1. DHH used most of his keynote at RailsConf 2011 to explain its new features. In this blog post, I’ll go through its implications to an existing application and explain how we integrated it to our workflow at Flowdock.

The Rails 3.1 asset pipeline integrates well with CDNs like Amazon’s CloudFront: just point your distribution to your web server. Most likely, you won’t need your old hacks for assets.

What’s new

The new asset pipeline in Rails 3.1 makes assets first class citizens in Rails. Assets are moved from public directory under apps/assets and they receive much more attention from Rails. Asset files can inline other asset files using requires and gem dependencies can provide asset files for the application to use.

This means that you don’t have to include jquery.js to your repository. Generators don’t have to worry about creating proper static assets under your public folder. Assets from other sources work just like those which are under your app/assets folder.

The static asset URL scheme has changed. Compiled assets are served under /assets. In production mode, generated files include an MD5 hash as part of the file name, which is a better cache buster than the previous timestamp scheme. Asset pipeline also generates URLs inside stylesheets so that they can link to generated files.

A stylesheet stored at app/assets/application.css will be served as /assets/application.css. Most likely, this layout will contain dependency declarations such as inclusion of CSS reset. Thus, your new application.css would become

/*
 *= require blueprint/reset
 *= require blueprint/typography
 */

body {
  background: url(<%= asset_path "sunshine-rainbows-and-lollipops.png" %>);
  /* You have to use ERB to link to asset pipelined resource */
}
/* Rest of stylesheet */

At the heart of the pipeline is Sprockets 2.0. In addition to dependency management, Sprockets will allow any number of filters to be used with asset files. By adding extensions to the files names one can control which filters are used. For example, it is possible to run a file through SASS and ERB by naming it stylesheet.css.scss.erb. JS files can also be filtered using both CoffeeScript and ERB.

Among the more controversial changes, CoffeeScript and SASS are now included in the default template. It is possible to use both CoffeeScript and SASS with old style assets. Not much has changed in this respect, DHH just is more vocal about what you should use.

Deal with it

Additionally, support for JS minification comes built-in with Rails. The default is to use UglifyJS via the Uglifier gem. Existing apps can use this by adding it in their Gemfile and setting the minificator in the (production) environment configuration.

Gemfile:

gem "uglifier"

config/enfironments/production.rb:

config.assets.js_compressor  = :uglifier

Upgrading existing application

Running rake rails:update will show how your configuration files should be updated for them to work with new asset pipeline. At a bare minimum, you’ll only need to add

config.assets.enabled = true

to your application.rb. After that stylesheets, javascripts and images can be moved under app/assets.

There are a few things to change to make your assets work. All absolute paths to images in stylesheets and asset helpers are broken and should be replaced with call to relative helpers, e.g. image_path /images/subdir/rails.png should be asset_path "subdir/rails.png". This way Sprockets will be able to figure out where the asset is served. It is especially neat in production where file names will have MD5 hash suffix for more efficient caching.

You’ll also want to put this to your .gitignore file:

.sass-cache/
public/assets/

Serving assets in production

Serving assets in production environment is much more pleasant with the new pipeline. First of all, instead of a timestamp, an MD5 hash is used to bust caches. Second, this is not part of the query string but used as suffix in file name. These MD5 suffixed files really exist and you can have multiple versions coexist at the same time.

Because the URIs are unique not only by query string but also file name, CloudFront can be easily used to serve your assets. Create a new custom origin distribution with your host as origin DNS and use the CloudFront domain as your asset hosts. That’s really all you need barring some web server configuration: check CloudFront custom origin best practices.

One thing to notice is that the assets should be precompiled before your site goes live to reduce load. There is a rake task for this, @rake assets:precompile@. We put this in our deploy.rb:

before :"deploy:symlink", :"deploy:assets";

desc "Compile asets"
task :assets do
  run "cd #{release_path}; RAILS_ENV=#{rails_env} bundle exec rake assets:precompile"
end

If you have multiple CSS and JS files, you might have to specify manually which of them are precompiled by adding them to the config.assets.precompile array.

Compiling your assets require that you have a JS runtime available if you use either CoffeeScript or Uglifier. We use therubyracer, but your mileage may vary. Installing node.js is also a good alternative.

Retrospective

Is this really better than the old setup? Did we learn anything?

First, the development and production setups are now more similar. Previously multiple JS files containing our application code were loaded in development, but now they’re concatenated to one big file. We are more likely to run into weird bugs before code is deployed to QA, but on the other hand line numbers aren’t meaningful for debugging purposes. When using CoffeeScript, this point is moot.

Second, we are happy that we could remove our old hacks to get Flowdock deployed. Instead of the combination of patched cloudfront_asset_host gem and some other hacks, we can compile our assets to be served via CloudFront using the built-in rake task.

Third, our deployment times were reduced by over a minute as we don’t need to sync our asset files with the S3 bucket.