Proper SCSS Asset Structure in Rails
Asked Answered
Z

6

89

So, I have an app/assets/stylesheets/ directory structure that looks something like this:

   |-dialogs
   |-mixins
   |---buttons
   |---gradients
   |---vendor_support
   |---widgets
   |-pages
   |-structure
   |-ui_elements

In each directory, there are multiple sass partials (usually *.css.scss, but one or two *.css.scss.erb).

I might be assuming a lot, but rails SHOULD automatically compile all the files in those directories because of *= require_tree . in application.css, right?

I recently have tried restructuring these files by removing all color variables and placing them in a file in the root app/assets/stylesheets folder (_colors.css.scss). I then created a file in the root app/assets/stylesheets folder called master.css.scss which looks like this:

// Color Palette 
@import "colors";

// Mixins
@import "mixins/buttons/standard_button";
@import "mixins/gradients/table_header_fade";
@import "mixins/vendor_support/rounded_corners";
@import "mixins/vendor_support/rounded_corners_top";
@import "mixins/vendor_support/box_shadow";
@import "mixins/vendor_support/opacity";

I don't really understand how rails handles the order of asset compilation, but it's obviously not in my favor. It appears none of the files realize they have any variables or mixins being imported, and so it throws errors and I can't compile.

Undefined variable: "$dialog_divider_color".
  (in /home/blah/app/assets/stylesheets/dialogs/dialog.css.scss.erb)

Undefined mixin 'rounded_corners'.
  (in /home/blah/app/assets/stylesheets/widgets.css.scss)

The variable $dialog_divider_color is clearly defined in _colors.css.scss, and _master.css.scss is importing colors and all my mixins. But apparently rails didn't get that memo.

Is there some way I can fix these errors, or will I need to resort to putting all my variable definitions back into each individual file, as well as all the mixin imports?

Unfortunately, this guy doesn't seem to think it's possible, but I'm hoping he's wrong. Any thoughts are greatly appreciated.

Ziegler answered 4/2, 2012 at 6:36 Comment(0)
A
128

The problem with CSS is, you do not want to automatically add all files. The order of which your sheets are loaded and processed by the browser is essential. So you will always end up explicitly importing all your css.

As an example, lets say you have a normalize.css sheet, to get a default look instead of all the horrible different browser implementations. This should be the first file the browser loads. If you just randomly include this sheet somewhere in your css imports, it will then not only override the browser default styles, but also any styles defined in all css files that were loaded before it. This goes the same for variables and mixins.

After seeing a presentation by Roy Tomeij at Euruko2012 I decided for the following approach if you have a lot of CSS to manage.

I generally use this approach:

  1. Rename all existing .css files to .scss
  2. Remove all contents from application.scss

Start adding @import directives to application.scss.

If you are using twitters bootstrap and a few css sheets of your own, you have to import bootstrap first, because it has a sheet to reset styles. So you add @import "bootstrap/bootstrap.scss"; to your application.scss.

The bootstrap.scss file looks like:

// CSS Reset
@import "reset.scss";

// Core
@import "variables.scss";
@import "mixins.scss";

// Grid system and page structure
@import "scaffolding.scss";

// Styled patterns and elements
@import "type.scss";
@import "forms.scss";
@import "tables.scss";
@import "patterns.scss";

And your application.scss file look like:

@import "bootstrap/bootstrap.scss";

Because of the order of the imports, you can now use the variables, loaded with @import "variables.scss"; in any other .scss file imported after it. So they can be used in type.scss in the bootstrap folder but also in my_model.css.scss.

After this create a folder named partials or modules. This will be the place of most of the other files. You can just add the import to the application.scss file so it will look like:

@import "bootstrap/bootstrap.scss";
@import "partials/*";

Now if you make a bit of css to style an article on your homepage. Just create partials/_article.scss and it will be added to the compiled application.css. Because of the import order you can also use any bootstrap mixins and variables in your own scss files.

The only drawback of this method I found so far is, sometimes you have to force a recompile of the partial/*.scss files because rails wont always do it for you.

Acrylyl answered 12/2, 2012 at 8:38 Comment(11)
Thanks, this is the approach I ended up going with. I hate this solution because you have to manually add every file you're including each time you create one, but it does seem that when CSS is involved, order does matter. Normally I'd say that's just bad CSS writing, but if you're using things that are vendor provided (i.e. bootstrap as you mention above), you tend to want to overwrite things, so I grudgingly agree this is the right approach.Ziegler
Thanks! A very nice approach and a good explanation! One project worth to mention: This FireBug plugin, FireSass, shows the line number of my_model.css.sass instead of the line of the compiled application.cssKimmi
With this approach, if I want to change my default link colour to red that applies to all my partials, how would you do that? Where would you put it?Donnydonnybrook
you put it in a variable and include it as the first file. like $darkBlueShade : #434f7f; and in all partials where you use it you refer to it as .myLink{color: $darkBlueShade;}Acrylyl
Note that this approach is also recommended in The Asset Pipeline Rails Guide. Search for "If you want to use multiple Sass files".Zhdanov
Does anyone have a way to see which specific scss file is linked to an error in development. When I use this method, I just see error with application.scss. ThanksTrough
Also, what's the proper way to handle vendor assets with this methodTrough
I think vendor assets are more or less covered by the bootstrap example. I suppose you could put them in a separate tree if desired. like /vendorAcrylyl
@BenjaminUdinktenCate This approach is very interesting. So this means that I have to rename the default application.css too, that Rails generates to, application.scss (or css.scss ?) and remove all comments even *= require_tree . and *= require_self ?Bioclimatology
@Lykos yes you remove everything from the application.css and rename it to application.scss. Because require_tree includes just everything and you want to usually control the orderAcrylyl
Just a NOTE. I had problems to auto-compile because i was making imports for each scss file in each directory like this: import "pages/home.scss". Then i changed to a single import like import "pages/*" and everything started to be recompiled every time i changed any scss file inside of pages directory. Hope this helps someone with same trouble as i had.Orose
R
8

Create the following folder structure:

+ assets
|
--+ base
| |
| --+ mixins (with subfolders as noted in your question)
|
--+ styles
  |
  --+ ...

In folder base create a file "globals.css.scss". In this file, declare all your imports:

@import 'base/colors';
@import 'base/mixins/...';
@import 'base/mixins/...';

In you application.css.scss, you should then have:

*= require_self
*= depends_on ./base/globals.css.scss
*= require_tree ./styles

And as the last step (this is important), declare @import 'base/globals' in every style file where you want to use variables or mixins. You might consider this overhead, but I actually like the idea that you have to declare the dependencies of your styles in every file. Of course, it is important that you only import mixins and variables in the globals.css.scss as they do not add style definitions. Otherwise the style definitions would be included multiple times in your precompiled file ...

Reface answered 8/2, 2012 at 9:55 Comment(2)
I tried this out but could never get it to work properly (still was getting missing variable errors). Maybe I didn't do something correctly, but I think in the end the approach of manually listing each file is the right way to go, since with CSS order does matter.Ziegler
You could still use this approach: Instead of "require_tree ..." just add a line for each individual file. This will make SASS import the files in the correct order. Regarding the solution you are now using, please have a look at this post that I just found: #7046995 Make sure to include the depends_on directivie in your application.css file.Reface
C
8

to use variables and such across files, you need to use the @import directive. files are imported in order specified.

then, use application.css to require the file that declares the imports. this is the way to achieve the control you want.

finally, in your layout.erb file, you can specify which "master" css file to use

example will be more helpful:

let's say you have two modules in your app that need different sets of css: "application" and "admin"

the files

|-app/
|-- assets/
|--- stylesheets/
|     // the "master" files that will be called by the layout
|---- application.css
|---- application_admin.css
|
|     // the files that contain styles
|---- config.scss
|---- styles.scss
|---- admin_styles.scss
|
|     // the files that define the imports
|---- app_imports.scss
|---- admin_imports.scss
|
|
|-- views/
|--- layouts/
|---- admin.html.haml
|---- application.html.haml

here's what the files look like inside:

-------- THE STYLES

-- config.scss
// declare variables and mixins
$font-size: 20px;

--  app_imports.scss
// using imports lets you use variables from `config` in `styles`
@import 'config'
@import 'styles'

-- admin_imports.scss
// for admin module, we import an additional stylesheet
@import 'config'
@import 'styles'
@import 'admin_styles'

-- application.css
// in the master application file, we require the imports
*= require app_imports
*= require some_other_stylesheet_like_a_plugin
*= require_self

-- application_admin.css
// in the master admin file, we require the admin imports
*= require admin_imports
*= require some_other_stylesheet_like_a_plugin
*= require_self


-------- THE LAYOUTS

-- application.html.haml
// in the application layout, we call the master css file
= stylesheet_link_tag "application", media: "all"

--  admin.html.haml
// in the admin layout, we call the admin master css file
= stylesheet_link_tag "application_admin", media: "all"
Chengteh answered 11/11, 2013 at 1:39 Comment(0)
H
4

According to this question, you can ONLY use application.css.sass in order to define import and share variables between your templates.

=> It seems to be only a matter of name.

An other way can be to include everything and disable this pipeline.

Hawes answered 8/2, 2012 at 8:13 Comment(1)
This is essentially the same thing Benjamin Udink ten Cate offered in the second part of your response, but awarding him the bounty as his solution is more explanatory.Ziegler
C
1

I had a very similar problem. What helped me was to put in the underscore to the @import statement when importing the partial. So

@import "_base";

instead of

@import "base";

It might be a strange bug...

Christianize answered 13/3, 2013 at 8:20 Comment(0)
A
0

My solution was to have an application.css.scss with all the imports:

@import "./inputs";
@import "./buttons";
@import "./rails";
@import "./base";
@import "./checked-border";
@import "./tailwind-extended";
@import "./helpers";
@import "./custom";
@import "./overrides";

And then add:

@import "./constants";

to the files that use the constants

Aspasia answered 12/6, 2021 at 6:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.