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
For the asset_path stuff, I think you can use ERB in Sass if you change the file name:
.css.scss.erb
http://www.rubyinside.com/dhh-keynote-streaming-live-from-railsconf-2011-right-here-right-now-4769.html
Dan Croak — June 12th, 2011 at 9:38 am
@Dan: If you use Sprocket’s `*= require` to pull in the file, it will use Tilt to render the template using the extensions in reverse order, e.g. file.css.scss.erb will run through ERB and Sass before rendering the final CSS file. But Sass’s `@import` statement doesn’t do that precompilation as far as I’m aware. Am I wrong?
Lenny — June 12th, 2011 at 7:22 pm
ive got all my references to assets in separate mixins in a separate partial sass, so i was desperately looking for a solution to use ERB within SASS partials. turns out, just rename your main (!! probably screen.css.sass) to screen.css.erb.sass, which causes sass to compile and import your styles first (leaving your erb tags untouched) and then ERB runs and renders asset_paths etc.
i don’t know if this behavior is intended, and therefore won’t break with the next RC, but it works in Rails 3.1 RC4
Cice — June 24th, 2011 at 1:08 am
Good article.
I’d suggest to specify the depend_on file extension
Because Sprockets will add a dependency on the first file matching the name it is usually the javascript file.
By specifying “_common.css” we are sure to get the correct one.
Crystalin — June 28th, 2011 at 3:38 pm
Maybe you got the snipped from here
http://metaskills.net/2011/05/18/use-compass-sass-framework-files-with-the-rails-3.1-asset-pipeline/
I was just about to updated to the latest 3-1-stable in the rails repo and master branch of compass to see if I can get a better asset_path usage and non-hacked up compass in my load paths from my early may experiments. I also found this ticket where @chriseppstein talked about a SASS extension that would let you do something like
background: image-url("foo.png").https://github.com/rails/rails/issues/1209
Ken Collins — July 12th, 2011 at 5:52 pm