I’ve been playing with Rails 3.1 lately, and ran across a leaky abstraction in the way the new Asset Pipeline handles SASS, variables and mixins in particular.
In my project I wanted to create a SASS file with global settings. For example:
$border_style: thin black solid;
These SASS variables I could then reference from other .scss files in my asset pipeline.
This seems like a perfect use of SASS variables in Rails 3.1, right? Set up a theme file that declares how colors should look in the app, then use those SASS variables from other SASS files.
Except it doesn’t work like that. Ryan Batt’s mentions this at the end of Railscast #268:
This error is caused by the way that Sprockets work in Rails 3.1. Variables aren’t shared between SASS files
The normal way you set up the Asset Pipeline to include your SASS files is this:
*= require_true .
*= require_tree ./shared_css
The Asset Pipeline will look at each file in
app/assets/stylesheets/ and call the SASS renderer on each file individually.
With this approach each SASS file is compiled individually, without knowing about any other file in the folder structure.
Except, we want to have a SASS file with some global settings.
The Answer: Have SASS include everything, then render, instead of using Asset Pipeline to do this
The Asset Pipeline makes N different SASS rendering calls, where N is the number of files in your
app/assets/stylesheets/ folder. The way to use a file with global settings is to have the asset pipeline render one file, but include all the files you want via the SASS include mechanism.
Railscast #268 mentions this solution, so it’s not new. Ryan Bates’s solution is to manually include the files in
But you have to do this with every SASS file if your asset pipeline. This sucks.
INCLUDE ALL THE
There’s a simple way you can solve this problem: use .erb.
That’s right: use ERB, and a clever function, to automatically include all the stylesheet assets.
application.css.scss to be
- Call the function below in your ERB (implementation of this function comes later):
<%= AssetPath.sass_require( Rails.root.join('app/assets/stylesheets/'), 'shared_css' ) %>
<%= AssetPath.sass_require( Rails.root.join('app/assets/stylesheets/'), 'site_css' ) %>
- Turn SASS caching off for development mode by adding this line to your
config.sass.cache = false
You don’t have to do anything for production, because your SASS won’t change anyway, so caching it once is OK. Turning off caching in development mode, however, will force SASS to re-render everything for every request.
"But Ryan, OMG won’t that take time?". No, not really. Also, SASS doesn’t very good dependency management, so changes in dependent files (like changing a value in my global settings SASS file) won’t be picked up, unless you also change
app/assets/stylesheets/application.css.scss.erb. Turning the cache off avoids this problem, especially since you’re changing things all the time in development mode (and don’t want to fight a cache).
Obviously, the old adage is true: those who don’t understand Make are forced to rewrite it, poorly.
The implementation of sass_require
The implementation of
AssetPath.sass_require is below. I want to put it into a gem at some point, but I want to test it in wider use before doing so. (I also want to wait until Rails 3.1 is officially released)
(see the gist)
Having SASS itself pull in all the SASS files works pretty well, allowing SASS variables to be shared between .scss files.
There is a disadvantage: because everything is shared, there is no variable scoping. Yes, all your variables, when SASS requires the file, are global.
This is something to watch out for, and one of the drawbacks. However, that was part of the point of the exercise, to be able to share SASS variables…