Rails 3.1 asset pipeline and SCSS modules with inter-dependencies

Dear core team,

I have a scenario that I think is important for developing apps with
DRY, modular CSS, and I can't figure out how to make it work with the
asset pipeline. I hope this provides useful feedback on what I think
is a shortcoming in the pipeline design.

The simplified scenario: I have a common.scss, with SCSS shared by
multiple applications. However, I'd like to override some variables in
common.scss in each application. For example, I have a $color variable
that sets the general hue of the application, and all the colors used
are computed based on that variable. I put all those variables in a
_vars.scss partial that is @imported by common.scss. An application
also has its' own SCSS, in special.scss. The application-specific SCSS
also uses the variables in _vars.scss, to keep things DRY.

In Rails 3.0, I had a generator that copied the following structure to
/public/stylesheets:
    /common/_vars.scss
    /common/common.scss -- @imports _vars.scss
    /special.scss -- @imports _vars.scss

For Rails 3.1, I would like to keep common.scss in /app/assets in an
engine gem, and generate _vars.scss inside the application. However,
I'm not sure what's the best place to put _vars.scss, and how to get
it @imported in common.scss. I don't think _vars.scss belongs in
/app/assets, because I don't want the //= require_tree to pick it up,
but I really don't know where it should go, or what API I should use
to compute its path.

DHH said, in his keynote, that he's been using the pipeline mostly for
JavaScript. SCSS is different, because a compiled .scss doesn't have
all the information in the original file -- the .css output doesn't
have mixin and variable information. Maybe we need a helper for
@importing?

Thank you for your help in advance,
Victor

You should definitely keep all your sass stylesheets in app/assets/stylesheets.

Name your partials beginning with an underscore and use “.css.scss” as the extension. Import files by omitting leading underscores and extensions.

IMO, Sprockets needs to add partial support, such files should return a 404 if they are accessed directly by url. But this is not a big deal.

Don’t use require_tree. Instead use the glob importing that sass-rails provides in those places where it makes sense. The Sass @import directive allows sass files to share cache more efficiently and makes the variables, mixins, etc from imported files accessible to later imports and the current file.

Sass-rails provides full glob support relative to the current file. So you can @import "**/*" if you want the same behavior of require_tree, but you can also just do @import "*/*" if you only want one level of imports or you can do @import "library/*" if you only want to import from one directory.

Lastly, Sass users are accustomed to any non-partial getting written out to a css file, but in rails it seems you have to explicitly enumerate files to be precompiled by adding them to config.assets.precompile explicitly. I think rails should adopt the Sass convention of automatically precompiling all stylesheets that are not partials.

Chris

Thank you very much, for your detailed response, Chris! I didn't know
@import can take globs, I can definitely use that to clean some things
up!

Just to make sure I understand, are you suggesting that my plugin's
generator should copy common.scss into app/assets/stylesheets, and
then use @import with a glob somewhere to pull everything in?

I was hoping I can have common.scss into a plugin's
app/assets/stylesheets directory, which could be updated independently
of the application, just like jquery-rails updates its JavaScript
files. However, if I do that, @importing with a glob won't work,
because the plugin's app/assets/stylesheets would be somewhere in my
gems directory.

I'm sorry for being slow in understanding your answer, and I'm
grateful for any further insight you can share!
Victor

Thank you very much, for your detailed response, Chris! I didn’t know

@import can take globs, I can definitely use that to clean some things

up!

It’s a feature that’s only available via sass-rails or if they install my sass-globbing plugin (https://github.com/chriseppstein/sass-globbing).

Just to make sure I understand, are you suggesting that my plugin’s

generator should copy common.scss into app/assets/stylesheets, and

then use @import with a glob somewhere to pull everything in?

I was suggesting that you shouldn’t put them in public anymore. Best to think of public like tmp.

I was hoping I can have common.scss into a plugin’s

app/assets/stylesheets directory, which could be updated independently

of the application, just like jquery-rails updates its JavaScript

files. However, if I do that, @importing with a glob won’t work,

because the plugin’s app/assets/stylesheets would be somewhere in my

gems directory.

You can add your libraries to the sass load path and import them without globbing. This is basically what compass does. You can add to the sass load paths in a railtie:

config.sass.load_paths << “/path/to/my/gems/stylesheets”

I’m sorry for being slow in understanding your answer, and I’m

grateful for any further insight you can share!

No problem. Hit me up in IRC if you need more clarification.