Sass and Sprockets for Rails 3.1
UPDATE: This article is definitely out of date now. The new sass-rails gem will augment Sass to handle dependencies, load paths, and Rails helpers (like asset_path) in a Sprockets environment. I’m now including edge versions of sass-rails and compass in my Gemfile and everything is working smoothly in development. I no longer have to use Sprocket-style *= require directives, just Sass @imports.
I’m a big fan of the new asset pipeline features in Rails 3.1. I’ve spent way too much time building my own JavaScript and CSS precompilers or forcing Sprockets 1.0 to do things it wasn’t intended to do. When I started using Sprockets 2.0 with Sass and Compass, there were some gotchas I didn’t anticipate and I was surprised not to see bug reports about them. As it turns out I was thinking about it all wrong. Here’s a quick explanation in case other people have the same problem.
I’m used to using Sass with one master stylesheet and several partials, like so:
-
@import "compass";
-
@import "common"; // Defines variables and mixins
-
@import "home"; // Uses variables and mixins defined in _common.scss
-
@import "checkout"; // Like _home.scss
In Rails 3.1 and Sprockets, this doesn’t work for two reasons:
- Sprockets isn’t aware of
@importstatements in Sass, so it doesn’t invalidate the cached master stylesheet when one of them changes. You can use the*= depend_ondirective to handle this, but then you have to list your partial files twice. - If you reference images in
assets/images, the Rails precompiler rewrites those files names with an md5 hash, likeassets/bg-9c0a079bdd7701d7e729bd956823d153.png(as described in this post on Rails 3.1 in production.) Sass can’t preprocess files with ERB, so you can’t useasset_path.
Being able to use ERB is pretty huge, so I realized I had to use Sprockets require directives to get that preprocessing. But you can’t just require "compass" at the top (it’s not in the load path) or be able to share variables and mixins with a single require.
The trick is to think of Sass files like Ruby files: if you depend on functionality in another file, you have to explicitly include it each time. So my example above becomes:
-
/*
-
*= require_self
-
*= require "home"
-
*= require "checkout"
-
*= depend_on "_common"
-
*/
-
@import "compass/reset"
-
@import "compass";
-
@import "common";
-
-
body {
-
background: url();
-
}
-
@import "compass"; // again!
-
@import "common"; // also again!
I still had to add a depend_on directive for the partial so that application.css updates when that file changes, but otherwise this solution solves all my problems and I have maximum flexibility.
Let me know if I’m overlooking something simple! And this post will probably be out-of-date soon as the Sprockets guys starting adding documentation.
UPDATE: Came across another gotcha today. You can’t use asset_path inside partial Sass files included with @import, so any shared styles can’t reference image files. Arg.
PS: This is how you get Compass in the Sass load path (I forgot where I stole this from):
-
Sass::Engine::DEFAULT_OPTIONS[:load_paths].tap do |load_paths|
-
load_paths << "#{Rails.root}/app/assets/stylesheets"
-
load_paths << "#{Gem.loaded_specs['compass'].full_gem_path}/frameworks/compass/stylesheets"
-
end

