Untangling the Rails Asset Pipeline, Part 3

Making sense of the configuration options.

The asset pipeline feature introduced in Rails 3.1 is not so simple once you start tweaking the settings. This article takes a look at some of the trickier aspects of asset pipeline configuration, along with a practical example of testing production settings without a web server.

You’re reading the third installment of a four-part series on the Rails asset pipeline. The previous entries are Part 1: Caches and Compass and Part 2: Production.

By default, Rails scatters the configuration of the asset pipeline in a few different places. Also confusing is that sometimes the term “asset” does not refer to the asset pipeline specifically. Finally, many settings are interrelated. Here’s a quick reference to three of the trickiest:

1. config.assets.compile. config.assets.compile is true in development and test environments, and is what tells Rails to compile (and cache) the contents of app/assets/ on the fly whenever the browser makes a request for a script or stylesheet. It is false in production, meaning assets in production must be precompiled.

Furthermore, it is worth noting that Rails does more than just disable compilation in production: by default, some versions Rails (3.1 and 3.2) go one step further and do not even load asset pipeline-related gems in production. This is a nice optimization, but it means that should you ever want to enable asset compilation in production, it is not just a simple matter of setting config.assets.compile=true.

Most applications will have no reason to do so, but if for some reason you do want to enable asset compilation in production for Rails 3.x, you will need to perform two steps: first set config.assets.compile=true, then alter the Bundler.require section in application.rb:

if defined?(Bundler)
  # If you precompile assets before deploying to production, use this line
  # Bundler.require(*Rails.groups(:assets => %w(development test)))
  # If you want your assets lazily compiled in production, use this line
  Bundler.require(:default, :assets, Rails.env)
end

2. config.serve_static_files. config.serve_static_files is related to the asset pipeline, but more broad: it controls whether or not Rails serves the static files located in the public/ directory. These might be precompiled asset pipeline assets, or they may just be static files you’ve decided to put there, like public/favicon.ico.

When set to true, Rails will install a middleware that checks if each browser request matches a file in the public/ directory. If so, it responds with the matching file, and your routes and controllers will not be used. Since this middleware adds a certain amount of overhead to processing each request, it is important to set config.serve_static_files=true only if necessary.

This setting is true in development and test environments. In production it is false, because a web server like Nginx will handle serving public/ files.

3. config.assets.precompile. config.assets.precompile controls which assets are precompiled when you run the assets:precompile rake task. By default, Rails only precompiles application.js and application.css (or their coffee, erb, sass, etc. versions). These files are often called application “manifests”, because they will contain a bunch of //=require or @import statements. Rails will compile these referenced files as well.

But scripts and stylesheets not referenced by these manifests will not be compiled! Consider the case where you have factored legacy Internet Explorer-specific styles into a separate ie.css file. It is fairly common to link to this in a conditional comment:

<!--[if IE]>
<%= stylesheet_link_tag "ie" %>
<![endif]-->

This will work in development, but when you deploy to production it will fail, because ie.css will be skipped during assets:precompile. This is where config.assets.precompile comes in:

config.assets.precompile += ['ie.css']

Regular expressions are also possible (as explained in Getting Compass to work with Rails):

# Precompile *all* assets, except those that start with underscore
config.assets.precompile << /(^[^_\/]|\/[^_])[^\/]*$/

Testing in production without a web server

As explained in the previous article of this series, normally an asset pipeline-enabled application will not work at all in production without a web server. But what if we want to test an application before the web server is set up? Or maybe we want to run our code in production mode locally without a web server.

The solution is to tell Rails to play the role of the web server, which is to say: serve static files from the public/assets directory.

Precompile assets as you would normally. Remember, asset pipeline-related code and gems are disabled in production, so we have to compile ahead of time.

bundle exec rake assets:precompile

Tell Rails to serve static files. In production.rb:

# Serve public/* without a web server
config.serve_static_files = true

Now start your application in production mode:

rails server -e production

Your application is now up and running sans web server; all CSS and JS will be served by a Rails middleware using the precompiled files in the public/assets directory.

Continue reading part 4 of this series: Untangling the Rails Asset Pipeline, part 4: Troubleshooting.