1198Days

1198 days since this post was last revised. Specific details are likely out of date.

Way back in August 2013 I was getting excited about Libsass and it’s Node, and in turn Grunt, wrappers (node-sass and grunt-sass respectively).

On the off chance you use Sass but haven’t heard of Libsass I’ll give you the York Notes summary; Libsass is a compiler for Sass that does not rely on Ruby to generate the CSS. It’s blazingly fast and makes style iterations far more palatable.

Since first using Libsass, I’ve used it on all my own ‘personal’ projects; however for larger scale projects there have been some blockers:

The Achilles heel of Ruby based Sass compile

If Sass has an Achilles heel, something users of alternative compilers can always point at and laugh, it’s compile time. Jo Liss did a great comparison of compile time speed with various pre-processors.

I was recently having a moan about the compile time of my Sass code base:

And people generally replied “Just use Libsass”! I explained the blockers for me, knowing that, realistically, the lack of object/list map support was the only thing I couldn’t really workaround (I’d have to change code/approach in the other respects but that was just a time investment).

And then Dale Sande sent me this tweet:

Turns out that Lu Nelson has created a set of functions called Sass list maps that are Libsass compatible and allow object maps to be manipulated in the same manner I was with Ruby Sass 3.3.

Back to Libsass

With this final piece of the puzzle, I was able to switch this large project to Libsass. A process which, once complete saw my project’s Sass compile time drop from 5.7 seconds to 137 milliseconds.

For anyone else (and my future self) thinking of porting a large codebase over to Libsass, I felt documenting the minor problems that needed solving would be useful:

Be aware that these problems and fixes are based on using grunt-sass version 0.12.1. Later versions of Libsass will likely have some/all of these issues fixed.

Combined placeholder and class selectors

Do you combine placeholder and class selectors for certain elements? For example:

%cell,
.cell {
    display: table-cell;
    vertical-align: middle;
    > * {
        vertical-align: middle;
    }
}

.thing {
  @extend %cell;
}

If you do, with Libsass, you’ll find things don’t extend well. With Libsass, that prior example compiles to this:

.thing, .cell {
  display: table-cell;
  vertical-align: middle; }
  %cell > .thing, .cell > * {
    vertical-align: middle; }

Notice the placeholder selector in the output? It should actually compile to this:

.thing,
.cell {
  display: table-cell;
  vertical-align: middle;
}
.thing > *,
.cell > * {
  vertical-align: middle;
}

Thankfully, there is a simple workaround. Simply do this in your source file:

%cell {
    display: table-cell;
    vertical-align: middle;
    > * {
        vertical-align: middle;
    }
}
.cell {
    @extend %cell;
}

And you’ll have a more sensible output:

.cell {
  display: table-cell;
  vertical-align: middle; }
  .cell > * {
    vertical-align: middle; }

Interpolation in attribute selectors

If you’re trying to do something like this with Libsass:

$colourList: green, blue, yellow;

@each $colour in $colourList {
    $i: index($colourList, $colour);
    [data-section-id=#{$i}] {
        width: 100%;
    }
}

You’ll get this output:

source string:8: error: expected a string constant or identifier in attribute selector for data-section-id

What you need to do is this:

$colourList: green, blue, yellow;

@each $colour in $colourList {
    $i: index($colourList, $colour);
    // Below line needed to fix interpolation problem in Libsass at time of writing
    $id: "'#{$i}'";
    [data-section-id=#{$id}] {
        width: 100%;
    }
}

The important part is the $id: "'#{$i}'"; line which is taking the $i counter variable from before and wrapping it in another set of quotes. I picked this workaround up from this ticket on the Libsass issues. By doing that, you will get the selectors you expect:

[data-section-id='1'] {
  width: 100%; }

[data-section-id='2'] {
  width: 100%; }

[data-section-id='3'] {
  width: 100%; }

Switching to sass-list-maps from Sass 3.3 list maps

This was a big blocker for me. For some time I have been using the Sass 3.3 list map feature. To be clear, not pre-processor source maps but object/list maps. I’ve written before about how useful Sass 3.3 list maps can be so I won’t go over that again in full here. Instead, if you consider the kind of example in that post:

A un-sequential list (in that the numbers of the keys don’t follow in order) of values in a map and a loop to iterate through and produce relevant rules:

$colourList: (
    1  : (#000000, #000011), 
    2  : (#000011, #000022), 
    4  : (#000033, #000044), 
    7  : (#000044, #000055), 
    9  : (#000066, #000077), 
);

@each $colourList, $keyNumber in $colourList {
    $background: nth($keyNumber, 1);
    $lowlight: nth($keyNumber, 2);
    header {
        .section_#{$colourList} & {
            background-color: $background;
            border-right: 2px dotted $lowlight;
        }
    }
}

With Sass 3.3 that yields:

.section_1 header {
  background-color: black;
  border-right: 2px dotted #000011;
}

.section_2 header {
  background-color: #000011;
  border-right: 2px dotted #000022;
}

.section_4 header {
  background-color: #000033;
  border-right: 2px dotted #000044;
}

.section_7 header {
  background-color: #000044;
  border-right: 2px dotted #000055;
}

.section_9 header {
  background-color: #000066;
  border-right: 2px dotted #000077;
}

With Libsass the same Sass yields this:

source string:2: error: error reading values after 1

However, thanks to Lu Nelson’s aforementioned Sass list maps, we can include his set of functions and then amend that prior code slightly to this:

$colourList: (
    1 (colors (color1 #000000, color2 #000011)), 
    2 (colors (color1 #000011, color2 #000022)), 
    4 (colors (color1 #000033, color2 #000044)), 
    7 (colors (color1 #000044, color2 #000055)), 
    9 (colors (color1 #000066, color2 #000077)) 
);

@each $colour in $colourList {
    $number: key($colour);
    $values: value($colour);
    header {
        .section_#{$number} & {
            background-color: get($values, colors, color1);
            border-right: 2px dotted get($values, colors, color2);
        }
    }
}

That then yields our expected CSS, regardless of the compiler used (Ruby or Libsass):

.section_1 header {
  background-color: #000000;
  border-right: 2px dotted #000011; }

.section_2 header {
  background-color: #000011;
  border-right: 2px dotted #000022; }

.section_4 header {
  background-color: #000033;
  border-right: 2px dotted #000044; }

.section_7 header {
  background-color: #000044;
  border-right: 2px dotted #000055; }

.section_9 header {
  background-color: #000066;
  border-right: 2px dotted #000077; }

Great work Lu Nelson. Take a bow.

Reverting to a non-globbing approach

Probably my biggest bugbear with Sass partials is the inability within the core language to do ‘Globbing’ imports. We have to explicitly import each partial like this:

@import "_partials/partial1";
@import "_partials/partial2";
@import "_partials/partial3";
@import "_partials/partial4";
@import "_partials/partial5";
@import "_partials/partial6";

On and on Ad Infinitum. Each time you break some code out to a new partial, you’ll need to explicitly write an @import statement for it which defines the order each partial will get ‘written’ the compiled CSS file.

Thanks to the Sass Globbing plugin for Ruby Sass authors can just do this:

@import "_partials/_modules/**/*";

And get all partials within a folder imported, regardless of how ever many more are added or how they are named.

However, some argue strongly against Globbing. They feel ordering is entirely necessary and it should not be possible for users to circumvent this. After all, CSS is order dependant due to specificity so there is a potential with Globbing for authors to get some styles placed in the wrong order relative to some others.

Bryan Jones (of CodeKit fame) certainly thinks so:

It introduces very subtle, very difficult-to-track bugs by affecting the cascade in unpredictable ways.

Nathan Weizenbaum, Sass maintainer, isn’t a fan either:

The reference implementation of Sass doesn’t and will not support globbing, for the reasons [Bryan Jones] outlined. It makes the order that rules are emitted very difficult to reason about and contingent on non-local factors. There does exist a Ruby plugin for it, but I personally discourage its use.

My feeling is that it is a mistake to hamper useful functionality merely because some authors will mis-use it. As mentioned previously, there’s been some back and forth on this. However, as one of the core contributors to Sass is opposed to Globbing, I don’t think we’ll see it in core any time soon. However, here’s my reasoning and argument of a typical and fairly common use case where Globbing functionality is important:

I try and write selectors with a flat and low specificity (e.g. single class names). I use name-spacing to avoid conflicts if necessary rather than add specificity that will make the style order dependent. It therefore makes no difference the order they are placed within the output CSS. Being able to do a glob import of all my ‘module’ partials means I never have to remember to write an import statement. I also don’t think this approach is particularly uncommon.

Sadly, regardless of the merits for and against, there is currently no comparable functionality in Libsass (that I currently know of) to allow partial Globbing. It means, giving my own case as an example, manually writing 80+ import statements for each partial. And remembering to write another each and every time I add another partial to the codebase.

That said, explicitly writing @import statements is a small price to pay for the gain in compile time speeds.

Want to see it your Sass will port over? If you head over to Sassmeister they have both the Ruby based compiler and also the Libsass compiler and switching between them just takes a click. You can therefore enter chunks of your code at a time and check how it compiles before jumping in with both feet.

Sass compile speed nirvana

As I stated at the top, switching a large Sass codebase to the Libsass based compiler dropped compile time from an average of 5.7 seconds with the Ruby Sass compiler to an average of 137ms with Libsass. Roughly 41X faster! Manually writing @import statements and taking the time to re-write list maps and the odd function is a small price to pay for such huge speed gains.