Sass Switcheroo

Sass Switcheroo is a work around to the fact that you cannot dynamically control your imports from within a sass file.

This Sass add-on gives you the ability to define import rules. You can:

  • Rewrite import statements
  • Specify a file should be imported before or after another sass file.

What is this useful for?

  • Making a project work across different versions of libraries
  • Managing Sass version differences

Installation

Add this line to your application's Gemfile:

gem 'sass-switcheroo'

And then execute:

$ bundle

Or install it yourself as:

$ gem install sass-switcheroo

Usage

The easiest way to make switcheroo work is in a compass configuration file:

importer = Sass::Switcheroo.rules(sass_path) do
  # custom rules go here. These are just examples.
  replace("**/setup*", %r{(.*)/setup} => 'sass3.4/\1/setup')
  before("**/variables*", %r{.*} => 'sass3.4/variable_defaults')
  after("**/variables*", %r{.*} => 'sass3.4/variable_adapaters')
end
add_import_path importer

Declaring import rules

Sass::Switcheroo.rules - This method takes two arguments. A root directory within which to find sass imports and and an optional value for glob options which defaults to File::FNM_EXTGLOB | File::FNM_PATHNAME. Docs on ruby globs and the glob options can be found here.

Import rewriting (The replace directive)

The first argument is a ruby glob. Import statements that match this glob will rewritten. Note that the import statements are made relative to the import root for the purpose of rewriting, even if they are specified relative to the sass file.

The second argument is a hash of transformations where the keys are iteratively substituted by the values. The keys can be strings or regular expressions. If the regular expression has captures, those can be accessed with the escapes \1 through \9 (This uses the ruby String#sub method) so it's not a global replace. Note that, because of ruby's syntax the hash does not require curly braces when the third argument is omitted.

This directive accepts the :glob_options option, which will override the glob option value that was specified globally for the rules.

Replacements can interact with each other. That is, once a replacement is matched, the resulting import will be checked against the remaining replacements.

replace Examples:

# Given these rules:

importer = Sass::Switcheroo.rules(sass_path) do
  replace("**/test-a*", "/test-a" => "/test-b")
  replace("**/theme-1/**/*", "/theme-1" => "/theme-2")
  replace("**/*-config", %r{^(.*)/([^/]+)-config} => 'config/\1/\2')
end

The following import rewrites would occur:

  • @import "my-app/test-a-sidebar" => my-app/test-b-sidebar
  • @import "my-app/theme-1/colors" => my-app/theme-2/colors
  • @import "typography/font-config" => config/typography/font
  • @import "tests/theme-1/test-a-config" => tests/theme-1/test-b-config => tests/theme-2/test-b-config => config/tests/theme-1/test-b

Co-imports (The before and after directives)

Once rewriting is performed the actual sass file that is being imported is matched for co-imports. Co-imports are import statements that are added either to the top or the bottom of the imported file's contents. Multiple co-imports can end up being injected and files imported as a co-import can themselves have their own co-imports.

Like replace, the first argument of before and after is a glob pattern. Unlike replace the glob pattern is matched against the actual sass file (relative to root directory of the imported file). This means that the filename will include extension and for partials, the leading underscore.

The second argument to before and after can either be a string or a hash. When the second argument is a string, this is the import that should be injected when importing files matching the glob. If the second argument is a hash, the keys much be regular expressions that can capture values from the matched file to construct imports. The captures from the regular expression are referenced in the values of the hash using the \1..\9 references. If several key/value pairs are in the hash, they will all be checked if they match the file. If none of the regular expression keys match, then no import will be created. Note that, because of ruby's syntax the hash does not require curly braces when the third argument is omitted.

Like replace, these directives allow the :glob_options option to be passed if you need to override the glob options specified by the rules block.

If you want to conditionally peform a co-import if the co-import file exists, pass :if_exists => true as an option to the before or after directives.

Caveat: Because co-imports mangle the source of the sass file and inject @import directives, the column numbers of the first line (in an scss file) as reported by sourcemaps and in error messages will be incorrect. In the indented syntax, the line numbers of sourcemaps and errors will be incorrectly reported.

before and after Examples:

# Given these definitions:
importer = Sass::Switcheroo.rules(sass_path) do
  before("config/setup", "config/sass-34-defaults")
  after("config/setup", "config/sass-34-config-adapter")
  after("app/**/*", %r{/_?([^/]+)\.s[ac]ss$} => 'config/app/\1', :if_exists => true)
end

The following co-imports occur:

  • @import "config/setup" is equivalent to @import "config/sass-34-defaults"; @import "config/setup"; @import "config/sass-34-config-adapter"
  • @import "app/foo" is equivalent to @import "@import "app/foo"; @import "config/app/foo" if the import of config/app/foo exists.

Miscellaneous Notes

  • Since \ is an escape in a double quoted string("..."), it is usually better to use a single quoted string ('...') which treats \ as a literal backslash.
  • Since / is a regular expression delimiter it is easier to define regular expressions involving / using ruby's %r{...} syntax instead so that / doesn't have any particular meaning and doesn't need to be escaped.
  • It is possible to create co-import loops so be careful to avoid that unless you have import-once enabled.

Contributing

  1. Fork it ( http://github.com//sass-switcheroo/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request