Is Libsass, the lightning fast Sass compiler ready for prime time?
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:
- No Sass 3.3 list map support (not source-maps, maps as in list/data objects, so I can do this kind of thing)
- No support for suffix selectors (another Sass 3.3 feature that can be useful for BEM style selectors). This isn’t something I actually use but it you do, ensure you factor it in. I don’t have a workaround in this post for that. If you use that (I don’t) and want to use Libsass you can stop reading now.
- Using the @extend directive in Libsass is still not perfect
- No capability to do Sass Globbing (something I think should be core but the Sass core maintainer, Nathan Weizenbaum is vehemently opposed to)
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:
Estimate I'm losing 16 minutes productivity a day to Sass compiles ((5s*200)/60). Obviously Sass boosts productivity but compile time = 🙁
— Ben Frain (@benfrain) April 2, 2014
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:
@benfrain @Canfie1d @lunelson patched the Maps issue for us.https://t.co/8g9C1Wipaa
— Current status: still testing negative (@anotheruiguy) April 2, 2014
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:
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.
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.
Hi Ben
Thanks for an elaborate post highlighting common issues migrating to lib-sass.
As a keeper of front-end standards, I can convince myself in favour of speed and make these changes but for the sake of my team, I do not encourage them since I know that everyone will not take the time to figure our these patches.
Sass being a layer over CSS during compile-time is a problem for some folks not familiar with front-end and adding these minor changes in the development flow mean many more people pulling their hair out.
That said, did post something on Designer News on my specific issue on how this plays with Live-reload. I would appreciate if you could share some insight on this.
https://news.layervault.com/comments/55715/
Thanks