Using Compass for CSS in your Sinatra application

Stage left: Sinatra, the hottest new Ruby DSL for expressive, singular web application development. Stage right: Compass, the CSS metaframework that takes the ease of use of Blueprint's grids and Sass' syntax, and lets you combine them in a manner semantic & modular, just how you like it.

Until now, these actors could never meet. With the release of the 0.9 series of Sinatra, however, it has become a breeze.

The key change occurred with the restructure of Sinatra's rendering logic. When you call sass to render a Sass stylesheet, you can now pass options that go directly to the Sass engine. See this excerpt from lib/sinatra/base.rb in the codebase:

def sass(template, options={}, &block)
  require 'sass' unless defined? ::Sass
  options[:layout] = false
  render :sass, template, options
end

def render_sass(template, data, options, &block)
  engine = ::Sass::Engine.new(data, options[:sass] || {})
  engine.render
end

See that options[:sass] hash going to ::Sass::Engine.new? That's how we'll get Compass to work. All it needs are some extra load paths to be passed to Sass::Engine, which you can do when you render your Sass stylesheet. Compass also conveniently provides a configuration singleton to let you do this just once in the configure block of your application:

gem 'chriseppstein-compass', '~> 0.4'
require 'compass'

configure do
  Compass.configuration do |config|
    config.project_path = File.dirname(__FILE__)
    config.sass_dir     = File.join('views', 'stylesheets')
  end
end

get "/stylesheets/screen.css" do
  content_type 'text/css'

  # Use views/stylesheets & blueprint's stylesheet dirs in the Sass load path
  sass :"stylesheets/screen", :sass => Compass.sass_engine_options
end

This stylesheet action at the bottom of this example will parse your views/stylesheets/screen.sass file and your Compass inclusions and mixins will output exactly the CSS that you expect. All done!

Update (2009-02-15): The code above was updated for the improved Sass configuration support in Compass 0.4. See my post on the 0.4 release for an explanation of the improvements. Before this release, your stylesheet actions would have looked a lot more ungainly:

get "/stylesheets/screen.css" do
  content_type 'text/css'

  # Use views/stylesheets & blueprint's stylesheet dirs in the Sass load path
  sass :"stylesheets/screen", { :sass => { :load_paths => (
    [ File.join(File.dirname(__FILE__), 'views', 'stylesheets') ] + 
    Compass::Frameworks::ALL.map { |f| f.stylesheets_directory })
  } }
end

Of course, you won't want Sass loading up all the Compass files and re-rendering your CSS with every page request, so you will want to cache this generated CSS file. This is another handy Sinatra trick I've come across, but I'll save that for the next entry.