Managing Styles with Sass on Heroku

I’ve generally found stylesheets to be the messiest part of any website. And I’m not surprised:

  • Cross browser support means kludgy code
  • Functionality usually takes the drivers seat to well thought out styles
  • It’s someone else’s job

But organizing styles doesn’t have to be a mess and here’s one strategy that may make your life easier.

But First…

Since REST became the de facto way to design Rails apps, I’ve focused on organizing stylesheets by the same patterns. It just seemed natural that if you had a folder /views/users/new.html.erb you should have a similiar folder /stylesheets/users/new.css. Back in 2008, I wrote a two part post on how to do this nicely with Rails. Three years later, I’m not the only one who thinks that this is a decent idea.

But CSS has grown old and weary and is in desperate need of an update. Sass is clearly a more expressive way to define styles.

So, it seemed like a great time to refresh the way I manage styles in my Rails apps.

Why Start from Scratch?

Beware of Dog

Simply put, this was a pain in the ass. Why was it harder than it should have been?

  • I’m masochistic: I wanted it to work a certain way. I didn’t want to wrangle with a bloated framework or sacrifice on simplicity.
  • Heroku: Heroku has a (mostly) read-only filesystem. Waaa, waaa!. Since Sass needs to be compiled, this poses a bit of a problem. There are a few workarounds that involve serving CSS from /tmp, but I didn’t want a hack.
  • Organization: I resisted Sass for a long time. I think by just organizing your selectors you can get a lot of the same benefits. My new setup had to have the same clarity.
  • Packages: Everyone should know by now that the HTTP overhead of requesting multiple CSS files degrades client performance. But you can’t just pile them all into one file because you need to separate styles for print, screen, IE, or mobile. This problem had a solution, but not with Git based deployments on Heroku and the Amazon S3 workaround sounded like trouble.
  • Javascript: Javascript and CSS are very very different. I wanted my solution to organizing styles to inspire the way I manage Javascript, not muddle it up.

Seriously, it shouldn’t be this god damn complicated.

FTW!

  • Sass: I was commited to porting to Sass, specifically because you don’t have to rewrite all your CSS to get started. Scss plays nice. It’s part of Haml, so throw it in your Gemfile:
gem 'haml', '3.0.18'
  • Migrate your CSS to Scss: Using the command line tool sass-convert I migrated all my CSS to Scss. I know it needs a lot of refactoring to take advantage of Sass, but I’ll do that later.
$> sass-convert source.css destination.scss
  • Organize: I wanted stylesheet packages to be derived by how styles were organized - not by a configuration file. So while it made sense to have the Sass files live next to my erb (remember, I have a one-to-one mapping of styles to my templates), Chris Powers noted that it was cleaner to store them in /app/stylesheets/:package/. For me, :package was just “desktop.” Later, I can easily create packages called “mobile,” “print,” or “ie” if I need to. See?

Template and Styles, Happily Ever After

  • Use Rack to do all the heavy lifting: Compiling Scss isn’t hard. Concatinating all the stylesheets isn’t hard either. Ryan Bates’ 222nd Railscast helped me write a Rack application that does the work:
class AssetsApp < ActionController::Metal
  include ActionController::Rendering

  def stylesheets
    @output = ''

    Dir.glob("#{Rails.root}/app/stylesheets/#{params[:package]}/**/*.css*") do |filename|
      sass_options = { :syntax => :scss }
      sass_options[:style] = :compressed unless Rails.env.development?

      @output += Sass::Engine.new(File.open(filename, 'r').read, sass_options).render
    end

    response.headers['Cache-Control'] = "public, max-age=#{1.year.seconds.to_i}" unless Rails.env.development?
    response.content_type = 'text/css'

    render :text => @output
  end
end
  • Add the route and reference it: With Rack doing all the hard work, I just point to it:
Sqoot::Application.routes.draw do |map|
  match "/stylesheets/:package.css" => AssetsApp.action(:stylesheets), :as => 'stylesheets'
end
<%= stylesheet_link_tag stylesheets_path(:package => :desktop) %>

All together now!

  • Instead of 15-some stylesheets being downloaded at ~50 KB, now I send the client one 8 KB file.
  • No writing to disk! So Heroku’s happy.
  • Varnish leverages HTTP to cache the compiled CSS.
  • I can sleep again because all my styles are now served in one tight bundle.
  • You get a nice free gist of the code to try for yourself.

UPDATE: I presented on the topics of this post at Chicago Ruby and Refresh. The video and slides are now available online. (May 1st, 2011)

Thanks for reading! I'm Avand.

I’ve been building and designing products for a decade. Today, I’m the Director of Product at Mystery Science working alongside a small team of educators and scientests to revolutionize how kids learn science. I was born Boston, grew up in Salt Lake City, spent several years in Chicago, and now call San Francisco my home. When I’m not at my computer, I like to ride bikes and make photographs personally and professionally.

Read my other posts or get in touch: