Blog

Working Efficiently with CSS in Rails

January 1, 2014    CSS SASS Rails

I've made an effort improve my CSS/SASS workflow in the past few weeks, and I want to share what I've come across!

Getting Started

What is Sass, and what's so great about it?

Sass, or Syntactically Awesome Style Sheets, is a CSS preprocessor that adds a ton of new functionality to CSS, including variables, mixins (functions), nesting, imports, inheritance, etc.

Sass provides two different formats - .sass (a whitespace dependent format that reduces the number of characters you need to type) and .scss (similar to vanilla CSS). Pick whichever you prefer!

I'm not going to go into a lot of detail about what Sass actually is - just go check out their guide.

Using Sass in your Rails Project

Since Rails 3.1, new applications have come pre-configured to use Sass. Simply add gem 'sass-rails' to your Gemfile if it's not already in there or you're upgrading from an older version of rails.

Managing Files - The Manifest

By default, Rails sets you up with a manifest file (application.css) which looks something like this:

application.css
1
2
3
4
5
6
7
8
/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 *
 *= require_self
 *= require foundation_and_overrides
 *= require font-awesome
 *= require_tree .
 */

Basically, this is a specially processed file that compiles all of our Sass into CSS and merges it together into one file. This setup works, but it is far from ideal.

The main issue with including our .scss in this way is that the files are compiled before they are merged together. Let's take a look at an example to see why this is a problem:

episodes.scss
1
2
3
.episode-list {
  @include grid-row();
}

In this example, I'm using a mixin (similar to a function) provided by Foundation (a front end framework). However, this will not work! Remember - the files are compiled before they are merged together, so our episodes.scss file has no idea that there is a grid-row() mixin!

We could get around this by adding something like @import 'foundation/components/grid' to the top of this file, but doing this every time we want to extend Foundation starts to get annoying and difficult to maintain.

Sass @include

Let's try a different method - using Sass's provided @import directive. First we'll rename our manifest file to application.scss and modify it to look something like this:

application.scss
1
2
3
@import "foundation_and_overrides"
@import "font-awesome"
@import "*.scss"

The sass-rails gem provides that "glob" import that allows you to import multiple files with one line.

With this, our files are merged together prior to compilation and we're able to effortlessly extend Foundation's SASS code. Awesome! So we're done right? Not quite... There are some gotacha's to look out for.

Gotcha #1 - @import allows files to be redundantly imported

Let's suppose we have a directory structure like this:

- stylesheets
  - application.scss
  - fundation_and_overrides.scss
  - episodes.scss

If our application.scss file looks like this:

application.scss
1
2
@import "foundation_and_overrides"
@import "*.scss"

foundation_and_overrides.scss will be imported twice.

We absolutely need to import foundation_and_overrides.scss first because our other scss files depend on the styles within it. Fortunately, this is pretty simple to fix. One approach is to move the order-dependent files into thier own directory:

application.scss
1
2
@import "ordered/foundation_and_overrides"
@import "*.scss"

Gotcha #2 - sass-rails glob import bug

Unfortunately, due to a bug in sass-rails, glob imports (@import "*.scss") do not properly recognize new files as they are added.

So in order to recompile application.scss when a new file is added, you must alter the application.scss file in some non-trivial way - such as commenting out a line.

There is an open issue for this on Github.

Modifing the application.scss every time a new file is added is pretty annoying, but it seems like it's a bit easier than maintaining a list of all the scss files that we want to include.

Hey! There is an obvious solution to these issues!

If you want, you can avoid all of this by simply not using the glob imports. You can manually just type all of your files in like so:

application.scss
1
2
3
4
5
@import "foundation_and_overrides"
@import "font-awesome"
@import "episodes"
@import "media"
// etc ...

That's an option, but I'm simply not a fan of it.

Easy Debugging with Source Maps

So writing Sass is now a pleasure, but what about debugging it? Not so much:

Debugger without source maps

Because of the merging and compilation, Chrome no longer provides a sensible file name and line number.

This is where source maps come into play. The Sass compiler can generate a source map that provides a means for Chrome to connect each line of CSS output back to the Sass file. We can do this through the sass-rails-source-maps gem.

Setup

First, you'll need to add the sass-rails-source-maps gem to your :development section in your Gemfile.

Next up, run the following to install/update the relevant gems and clear out the cache:

bundle
bundle update sass
rm -rf tmp/cache/assets
rm -rf public/assets

A recent version of Chrome supports this out of the box (I'm guessing Firefox supports this in some fashion as well):

Debugger with source maps

Excellent!

References

That's it!

If you have any comments for have found this interesting feel free to tweet me @bolandrm! Thanks for reading!