"Why replace Compass?" you ask. "Everyone likes Compass. Compass is nice! It provides helpful CSS3 mixins, and it makes life generally easier for the Sass developer."
True, but have you ever looked at the Compass source code? I dare you to figure out how Compass initializes itself, how it parses your configuration file, and how it ties into Rails. You will quickly find that it's an absolute nightmare. There's a Compass::Compiler class which wraps Sass::Plugin::Compiler. Why? I have no clue. There is really no need for Compass, except that it provides an abstraction around Sass for different frameworks. A lot of the stuff in Compass could be in Sass itself!
The other thing that's a little frustrating (and this is more about Sass than Compass) is that paths are too closely tied together. In Sass there are two arrays: the template location array, where you tell Sass where your .sass or .scss files are and where they should end up when they are compiled; and the load path, where you tell Sass where to look when resolving @import statements that refer to partials. Now usually these are the same; if I have Sass files in app/stylesheets, I probably have partials inside of there too.
It gets more complicated when you have more than one directory involved. In our case we have three locations where Sass files could be:
app/stylesheets
vendor/assets/stylesheets
vendor/compass-mixins
However, we want stylesheets in app/stylesheets
and vendor/assets/stylesheets
to end up in public/stylesheets/compiled
, in a certain directory structure such that app/stylesheets/app
and vendor/assets/stylesheets/app
get merged into public/stylesheets/compiled/app
, and so forth. So we have a symlink to guarantee this happens:
public/stylesheets/src/app
->app/stylesheets
public/stylesheets/src/vendor
->vendor/assets/stylesheets
So when we are compiling Sass files, we want this to happen:
public/stylesheets/src/**/*.scss
->public/stylesheets/compiled/**/*.css
Here is how we can achieve this.
- Remove Compass from Gemfile, and re-run
bundle install
cp -R COMPASS_GEM/frameworks/compass YOUR_RAILS_APP/vendor/compass-mixins
- Remove
config/compass.rb
- Add
config/initializers/sass.rb
with this content:
module Sass
module Plugin
class Compiler
# Override to NOT put template_location in the load_paths
def load_paths(opts = options)
opts[:load_paths] || []
end
end
end
end
module SassCompiler
class << self
DEFAULT_OPTIONS = {
:cache => true,
:line_comments => true,
:style => :expanded,
:css_location => nil,
:cache_location => Rails.root.join('tmp/sass-cache'),
:load_paths => [],
# Set default template location array since
# Sass::Plugin::Configuration#normalize_template_location! initializes
# the array with [:template_location, :css_location], but only if it
# hasn't been set yet
:template_location => []
}
def add_to_template_locations(*template_locations)
template_locations.each do |pair|
source_dir, dest_dir = pair.map {|dir| Rails.root.join(dir) }
# Write to the array directly since
# Sass::Plugin::Configuration#normalize_template_location! does some
# weird stuff with the template location array
_options[:template_location] << [source_dir, dest_dir]
end
end
def add_to_load_path(*dirs)
_options[:load_paths] |= dirs.map {|dir| Rails.root.join(dir) }
end
def updating_plugin_options
yield
Sass::Plugin.options.merge!(_options)
end
def compile
Sass::Plugin.force_update_stylesheets
end
def clear
system "rm -rfv #{Sass::Plugin.options[:cache_location]}"
Sass::Plugin.compiler.send(:css_locations).each do |dir|
system "rm -rfv #{dir}/*"
end
end
def add_hooks
Sass::Plugin.on_updating_stylesheet do |sass_file, css_file|
SassCompiler.on_updating_stylesheet(sass_file, css_file)
end
Sass::Plugin.on_compilation_error do |error, sass_file, css_file|
SassCompiler.on_compilation_error(error, sass_file, css_file)
end
end
def on_updating_stylesheet(sass_file, css_file)
sass_file = _abbr_path(sass_file)
css_file = _abbr_path(css_file)
logger.debug "Sass compiled #{sass_file} to #{css_file}"
end
def on_compilation_error(error, sass_file, css_file)
sass_file = _abbr_path(sass_file)
css_file = _abbr_path(css_file)
logger.debug "Sass ran into a #{error.class} error while compiling #{sass_file} to #{css_file}: #{error.message}"
end
def _options
@_options ||= DEFAULT_OPTIONS.dup
end
def _abbr_path(path)
path.sub(Rails.root.to_s + "/", "")
end
end
end
SassCompiler.updating_plugin_options do
SassCompiler.add_to_template_locations(['public/stylesheets/src', 'public/stylesheets/compiled'])
SassCompiler.add_to_load_path('app/stylesheets', 'vendor/assets/stylesheets', 'vendor/compass-mixins')
end
SassCompiler.add_hooks
- Add
lib/tasks/sass.rake
with this content:
namespace :sass do
task :compile => [:clear, :environment] do
SassCompiler.compile
end
task :clear => :environment do
SassCompiler.clear
end
end