LaunchSchool - An Online School for Developers /

Blog

Everything You Should Know About the Rails Asset Pipeline

What is the asset pipeline?

If you’re building a Rails application, you’ve probably heard of the asset pipeline. The asset pipeline can be thought of as the tools and mechanisms by which Javascript files, stylesheets, and images are processed and prepared for use by the browser. These processes can minify and compress assets, as well as pre-process assets that are written in other languages such as Coffeescript or Sass.

The asset pipeline was created to solve a variety of problems related to static assets. One such issue is that each asset specified separately in the HTML markup must be retrieved separately, resulting in a higher number of HTTP requests and, in the end, a longer load time. Raw Javascript and CSS files can also waste a lot of bandwidth with comments, extra white space, and long variable names. Another issue that comes up involves caching. When you serve up a Javascript file from your server, for example, the browser will automatically cache that file for a period of time. That improves page load time, but what if that asset changes at a later point in time? The browser won’t know about it, so it will continue to use the cached asset until its cache life has expired. Finally, languages such as Coffeescript, Sass, Less, and Erb have made it easier to organize and write Javascript and CSS, but the browser can’t interpret them directly, so a pre-processor is needed to convert those files into their appropriate counterparts before they are sent off to the browser.

The asset pipeline, in its benevolence, can solve all of the above problems when used properly. It can compile multiple assets into one, minify and compress assets, provide digesting of assets to avoid caching issues, and can pre-process alternative languages and turn them into Javascript and CSS.

This post covers a variety of topics related to the asset pipeline:

  • the basics of how to use the asset pipeline
  • best practices for structuring where to put your assets
  • how to use the precompile array to specify what files are processed by the asset pipeline
  • how Sass and Coffeescript can be leveraged
  • how to use Rails asset helper methods, and
  • some gotchas

Basic Usage

There are two basic ways that the asset pipeline is used:

  1. When running a server in development mode, it automatically pre-processes and prepares your assets on-the-fly.
  2. In production mode, you’ll probably use it to pre-process, versionize, and compress and compile your assets. You can do so by running the following command:
1
bundle exec rake assets:precompile

This will create (by default) an assets directory in your public/ folder. It will then add all the compressed and compiled files into that directory, in the appropriate formats and with the new digested versions. You can then set up Nginx or Apache to server those files directly so that Rails doesn’t have to deliver them (and run the on-the-fly preprocessing, etc.) itself.

Remember that defaults can be changed, so if things aren’t working as expected, check your application configuration file in config/application.rb. In Rails 4, asset handling is typically configured in config/initializers/assets.rb.

File Structure

It’s important to organize your assets in a way that is understandable to you, and facilitates the existing functionality of the asset pipeline. The first thing you should know is that all of your custom Javascript, stylesheets, and images should go in the app/assets/ directory. By default, there is a folder each for javascripts, stylesheets, and images. You can also add fonts, audios, and videos to the app/assets/ directory for those types of assets. All third-party code that you are using (e.g. jQuery, backbone.js, etc.) should be placed in the vendor/assets/ directory:

1
2
3
4
5
6
7
8
9
10
11
rails-app/
    app/
        assets/
            images/      # Image assets
            javascripts/ # Custom Javascript/coffeescript
            stylesheets/ # Custom CSS/Sass
    ...
    vendor/
        assets/
            javascripts/ # Javascript libraries, etc.
            stylesheets/ # Vendor themes, javascript library themes, etc.

Since your web server will automatically server static files from the public/ directory, why shouldn’t all Javascript, image, and stylesheet assets be placed there? For one, nothing in the public folder will get pre-processed, compiled, or compressed automatically, so by putting assets there, you are completely bypassing the asset pipeline and losing all of its benefits. When you put assets there, you also lose the ability to easily reference them within your Rails code. You could have a view, for example, that has the following code:

1
2
3
4
5
6
7
8
9
10
# app/assets/images/logo.png
= image_tag('logo')

# Outputs something like <img src="/assets/logo-[hash].png" />

# public/images/logo.png
= image_tag('/images/logo.png')

# Outputs something like
# <img src="/images/logo.png" />

In the second scenario (public/images/logo.png), the site will only work if it is delivered from the base directory. It also can’t take advantage of the asset pipeline’s versioning of the file.

Precompilation

You may now be wondering if everything you put in the app/assets/javascripts/ folder will be automatically precompiled for your app. Fortunately, the asset pipeline provides a way to specify which files are compiled, and in which order. By default, application.css and application.js (or their sass/coffeescript equivalents), along with all non-Javascript, non-CSS assets are included. To include a CSS or Javascript file other than application.css and application.js, you have to require it in one of two ways:

  1. Add it to the precompile array in config/initializers/assets.rb (Rails 4) or your application config file (e.g. config/application.rb), or
  2. Include the file in your asset’s manifest or one of its sub-file’s manifest.

The first option looks like this:

1
2
# In config/initializers/assets.rb
Rails.application.config.assets.precompile += %w( some-other-file.js even-another.css )

This option is best for files that it makes sense to only include on certain pages, and should not be included on others. For example, if you have a portion of your site that will be used as an iframe embedded widget, you may only want widget.js and widget.css, or similar, to be used on that page. Those files would have to be added to the precompile array as shown above.

The second option is what should be used most of the time, and allows your Javascript and CSS files to be compiled into one application.js and one application.css file. The manifest is written at the top of the applicable asset.

In coffeescript, it looks like this:

1
2
3
4
5
# In application.coffee
#
#= require jquery
#= require jquery_ujs
#= require_tree .

The above manifest will include jQuery, the Rails jQuery unobtrusive scripting adapter (jquery_ujs), and all the files in the current tree (i.e. app/assets/javascript/*). Note that require_tree does not compile assets recursively through directories. If you have a folder of files that you want to include, you’ll have to add that to the manifest as well:

1
#= require_tree ./components

One more manifest directive is require_self, which is used to include the current file’s Javascript at that point in the chain. The above, with a require_self can be written in a .js file as follows:

1
2
3
4
5
6
7
// In application.js
//
//= require jquery
//= require jquery_ujs
//= require_tree .
//= require_tree ./components
//= require_self

The Sass/CSS manifests use the same basic format, but with the appropriate comment style:

1
2
3
4
5
6
/** In application.css
 *
 *= require reset
 *= require global
 *= require layout
 */

Note that when using Sass, you’ll need to use its @import rule to take advantage of variables and mixins, as each file compiled by the manifest has its own scope.

Be careful with your usage of the require_tree directive. Assets in the tree will be included in alphabetical order, which means that if a file that starts with “a” depends on a file that starts with “z”, you could run into issues where the necessary pieces are not available when the Javascript is evaluated by the browser. This issue can be avoided by using the appropriate jQuery(document).ready(), or window.onload guards, by specifying an order manually, or be prefixing the files with numbers like 01_, 02_:

1
2
3
4
5
# application.js
# Note that the `.js` isn't needed at the end of the filename.
#
#= require subfolder/library
#= require subfolder/depends-on-library

Remember that any precompiled Javascript or CSS file can contain a manifest at the top, so you can use subfolders’ files to simplify your top-level manifest.

Sass and Coffescript, and Rails Asset Helpers

I’ve mentioned Sass and Coffeescript a bit in the above sections, but I haven’t yet gone into what they are. If you’re already familiar with them, feel free to skip on down to the “Rails Asset Helpers” section.

Sass and Coffeescript

Sass and Coffeescript are languages that use preprocessors to transform their syntax into CSS and Javascript, respectively. There are several other preprocessed languages such as Typescript and Less, but Sass and Coffeescript are included by default with Rails, and are probably the most popular. I won’t go into extensive detail here on these languages, so please check out the links above for more information.

From my experience, while Sass and Coffeescript provide a lot of syntactic sugar (pretty code features), the fact that exceptions are thrown for invalid code in the preprocessing stage is enough to warrant using them. Coffeescript automatically wraps your code per file and adds the var keyword to your local variables, thus preventing a lot of scope headaches that can happen more easily in vanilla Javascript.

For example, the following Coffeescript code:

1
2
3
4
5
$ ->
  $('#element').on 'click', ->
    state = 'clicked'
    window.state = 'clicked'
    console.log 'element clicked'

is converted to the following Javascript:

1
2
3
4
5
6
7
8
9
10
11
(function() {
  $(function() {
    return $('#element').on('click', function() {
      var state;
      state = 'clicked';
      window.state = 'clicked';
      return console.log('element clicked');
    });
  });

}).call(this);

When the preprocessor processes the file, it automatically wraps the code in an anonymous function ((function() {}).call(this)) and adds the var keyword to the first usage of state. Note that if you want to use the global scope, you have to specify that by prefixing window..

There is a lot more to Coffeescript, but the automatic scoping has been extremely helpful for me personally in preventing hard-to-find bugs.

Rails Asset Helpers

Another great feature that you can only get by using Sass in your Rails project is the asset path helpers. When referencing other assets in your Sass, you can use the following syntax to get the appropriate paths:

1
2
3
.logo {
    background-image: image-url("logo.png");
}

The helpers image-path, asset-url, and asset-path can also be used. The -url helpers wrap the path with url().

Erb in Assets

The asset pipeline allows you to evaluate erb code in your CSS and Javascript assets by suffixing the filename with .erb (e.g. application.js.erb or application.scss.erb). While this can be useful for adding asset paths to your Javascript, on the whole I don’t recommend using this feature. It adds another preprocessing step to the file, thereby increasing the time it takes to precompile. It also can lead to bad habits. You might be tempted to add code that doesn’t make sense at compile time, like translating strings. Those strings would only be translated at compile time, thus negating the whole purpose of translating them. The only reason to use Erb in a Javascript file should be to use the asset_path helper as discussed in the guide.

Asset Pipeline Gotchas

The following are some gotchas and tidbits that can be helpful in developing your Rails application’s assets.

  • How do I add global data for my Javascript to use?
    • The gon gem can be used to insert globally-scoped data to the page for your Javascript to consume. This is great for things like providing the current user’s ID or site settings.
  • Where should I augment the precompile array?
    • In Rails 4, you should augment the array in config/initializers/assets.rb, and in Rails 3, you should do so in config/application.rb. Using assets.rb or application.rb sets the precompile array for all environments so that if you add an environment (perhaps staging), you will not need to augment the precompile array specifically for that new environment.
      • assets.rb: Rails.application.config.assets.precompile += %w( widget.css widget.js )
      • application.rb: config.assets.precompile += %w( widget.js widget.css )
  • When should I precompile assets separately by augmenting the precompile array?
    • This should rarely be done. Most of the time, the only reason you’d want to do this is if the asset is included alone on a special page where you don’t want the rest of the assets, such as an iframe-embedded widget. Use your application.js and application.css manifests to pull in your assets.
  • Should I allow Rails to serve static files?
    • Yes. This follows the Twelve-Factor App guidelines and prevents errors if you forgot to include a file in the precompile array or if you forgot to precompile.
    • Set config.serve_static_assets = true in Rails 3, and config.serve_static_files = true in Rails 4.
  • How can I ensure my Javascript/CSS is well-written?
    • Writing high-quality Javascript and CSS (or Coffeescript and Sass) can take a lot of time and experience, but one thing that has greatly helped me is using a linter). Some IDEs, such as Sublime Text, have plugins that integrate linters. I personally use the SublimeLinter package. If you’re going to write plain-old Javascript and CSS, you should definitely be using a linter.
  • How do I change the directory where my precompiled assets are placed?
    • You can change the config.asset.prefix setting to another directory, such as /static.
    • Change the asset prefix in development.rb if you want to track precompiled assets in git for production use.
    • Change the asset prefix globally if you want to use the /assets path namespace for your controllers.
  • How do I serve static assets with Nginx?
    • The basic nginx directive should look like this (see the Precompiling Assets section of the Rails guide for more information):
1
2
3
4
5
6
7
location ~ ^/assets/ {
  expires 1y;
  add_header Cache-Control public;

  add_header ETag "";
  break;
}
  • What is manifest.yml or manifest-[digest].json?
    • The manifest.yml (Rails 3) or manifest-[digest].json (Rails 4) files are created automatically when you precompile your assets with rake assets:precompile. They contain information about which files are to be used by your app.
  • What types of files should NOT be included in the asset pipeline?
    • You will usually want to keep large files (videos, large numbers of PDF downloads, etc.) in a separate repository or in a cloud file manager, such as AWS S3. The files can then be referenced from your app, but don’t have to bog down your git repository.
  • How can I get my Rails 3 assets to compile faster?
    • Use the turbo-sprockets-rails3 gem.
  • Can I use a CDN (content delivery network) for my assets?
    • Yes! The digest, or fingerprint, suffixed on each asset makes them work great with CDNs. Set up your CDN to serve assets from your Rails app, then set the config.action_controller.asset_host configuration to the appropriate domain for your CDN (e.g. config.action_controller.asset_host = 'mycdn.fictional-cdn.com'). I recommend using environment variables to set this: config.action_controller.asset_host = ENV['CDN_HOST'].

Wrapping it up

Using the asset pipeline properly can improve the overall quality of your application in terms of performance, resilience, and code cleanliness. By using the asset pipeline’s features, you can automatically overcome some of the biggest issues related to coding and delivering static assets.