Icelab

Asset Pipeline Tips

By Hugh Evans21 Jun 2012

After speaking to a few of the attendees at Red Dot Ruby Conf a few weeks ago in Singapore I promised them I’d document the techniques we use at Icelab to get the most from Rails asset pipeline. So here it is.

Gemfile

If the gem does not have any functionality that needs to be run on the server then we place it in the assets group. I’m repeatedly seeing some of the below gems in the main group of gems in some people’s projects. This was likely because of bugs in earlier versions of the asset pipeline which don’t exist now.

group :assets do
  gem 'bootstrap-sass'
  gem 'coffee-rails'
  gem 'compass-rails'
  gem 'jquery-rails'
  gem 'sass-rails'
  gem 'uglifier'
end

Just a note about compass-rails. If you’re upgrading this library from an older version you’ll be happy to know that a config file is no longer required for standard functionality.

Compiled Files

By default there is a compiled application.js and an application.css. We typically expand on this in application.rb to something like:

config.assets.precompile += [
  'admin.css',
  'admin.js',
  'fallback_jquery.js',
  'modernizr.js'
]

We have the admin.js and admin.css files in app/assets/javascripts|stylesheets next to their application counterparts. We place the fallback_jquery.js and modernizr.js in vendor/assets/javascripts. The reason we have Modernizr in a separate file is so we can load it in the head of the document (although I’ve recently been informed that simply having Modernizr inline in the header results in slightly faster loading). Then in the footer we async load jQuery and the application.js or admin.js using Modernizr.load():

<!DOCTYPE html>
<html lang="en">
  <head>
    <%= stylesheet_link_tag 'application', :media => 'all' %>
    <%= javascript_include_tag 'modernizr' %>

  <body>
    <%= yield %>

    <script>
      Modernizr.load([
        {
          load: ['timeout=3000!http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js', '<%= javascript_path "application" %>'],
          callback: function () {
            if (!window.jQuery) {
              Modernizr.load('<%= javascript_path "fallback_jquery" %>');
            }
          }
        }
      ]);
    </script>
  </body>
</html>

As you can see we first attempt to load jQuery from the Google CDN and if that fails to load in 3 seconds it requests the local fallback version. Because we have the jquery-rails gem all that is required inside the vendor/assets/javascripts/fallback_jquery.js file is:

//= require jquery

Why not just add jquery to the list of precompiled files you ask? Because we simply wanted to be specific about it’s purpose. Which leads me onto the next point. Rename your vendor JS libs for consistency. Rather than just sticking jQuery.whizbang-2.213.js in the vendor directory and doing //= require jQuery.whizbang-2.213 call the file jquery_whizbang.js and paste in the non-minified version that includes the headers/version information. Having the vendor files named in a consistent fashion to the rest of your Rails application makes things a lot tidier.

Sprocket Requiring Files

We tend to stick to the simple //= require x command and not require_tree as it pays to be specific about load order as it will inevitably bite you one day. We leave the application JS/CoffeeScript files in the root of app/assets/javascripts and place the admin stuff in a subdirectory which is then loaded in admin.js with //= require admin/file. All the vendor JS libs get renamed for consistency and placed in vendor/assets/javascripts. Some will be loaded in application.js, some in admin.js and some in both.

Do not sprocket require CSS/SASS files. Only use SASS @import. Any vendor CSS we wish to use we place in vendor/assets/stylesheets, rename for consistency and add a .css.scss extension.

Asset Sync

In most situations these days we use the fantastic asset_sync gem to upload our assets up to S3. It is all pretty straight forward and the gem is very well documented, just be sure to enable gzip compression as it will make a big difference to the S3 download sizes.

In the next section I’m going to detail how we precompile assets locally and if you choose to go this route then you can place the asset_sync gem in the Gemfile’s assets group as it needn’t run on the server. Then in the asset_sync config initializer you need to wrapper everything in an if defined?(AssetSync) end.

Heroku

This seems to be the biggest grievance amongst the people I have talked to about the Rails asset pipeline. When pushing to Heroku either the precompilation of assets fails completely or it simply drives them insane with how long it takes to deploy. Especially so if they are only wanting to deploy a quick code fix as Matt alludes to in his tweet. The technique we have come up with, and we acknowledge it isn’t perfect, is to force git add the pubic/assets/manifest.yml while still git ignoring the entire public/assets directory, and then running rake assets:precompile locally which uploads to S3 with the asset_sync gem. Next you will be required to commit any changes to the manifest.yml and then you are good for a deploy. When pushing the code to Heroku it will detect the presence of the manifest.yml file and will now not attempt to precompile the assets.

From our experience this workflow is much more reliable. It is however a bit of a pain remembering to precompile the assets and commit the changed manifest.yml when you have been working on them. It is a huge win though for speeding up quickfix code deploys, so we think it is worth it.

These are the notes we throw into the Readme of every project where we use this technique:

Asset Compiling

Assets are precompiled locally and automatically uploaded to 3S with the asset_sync gem before deploying with the command:

bundle exec rake assets:precompile

For this to work you need in place:

  • The config/config.yml file with the AWS details (see config/config.example.yml)
  • A production database connection adapter (see config/database.example.yml)

Following compilation the public/assets/manifest.yml will have changes in git which need committing before deployment. The compiled files will be in public/assets but they are git ignored.

Note: when developing locally if there are files in public/assets they will get loaded so you won’t see your changes (not good). To fix this:

bundle exec rake assets:clean && git checkout public/assets/manifest.yml

When deploying/pushing to Heroku it detects the presence of the manifest.yml file and does not attempt to compile the assets itself. The asset_host has been set to the S3 domain in production.rb.

Feel free to copy it into yours.

Work with us, we’re good peopleGet in touch