What's up with @extend in node-sass v3.5.1 and beyond / by Alexander Hadik

I've recently bumped up against a breaking change in node-sass v3.5.1. That's right - not a major version - but still a breaking change for some code bases. The issue is in @extend and the continual process of alignment between the Sass spec and the actual compilers that we all use on a daily basis. Here's an example of the error that node-sass might be throwing:

Error: source/scss/main.scss
Error: ".class" failed to @extend "%pseudoselector".
    The selector "%pseudoselector" was not found.
    Use "@extend %pseudoselector !optional" if the extend should be able to fail.
    on line 6 of source/scss/main.scss
>>         @extend %pseudoselector;
        --^

Tl;Dr;

If you just want a quick fix - uninstall node-sass with npm remove node-sass and then install node-sass v3.4.2 with npm install node-sass@3.4.2 make sure you install the most recent version of gulp-sass. That will make sure you code is parsed just like it used to be.

Why does this work? Explanation below...

AN Explanation

You might have bumped into this without knowing you use node-sass, if you use a plugin like gulp-sass. Infact, I'd take a guess that it's gulp-sass that got you here. gulp-sass is just a light-weight wrapper around node-sass which provides Node bindings to Libsass which is, finally, the C/C++ Sass compiler that takes all your Sass and makes it CSS.

This error is being thrown because the following is, according to the Sass spec, invalid Sass:

.class{
    @extend %pseudoselector;
    color : green;
}

So is this:

%pseudoselector{
    &.nested-class{

    }
}

.class{
    @extend %pseudoselector;
    color : green;
}

The problem in the first example is that %pseudoselector doesn't exist at all. Of course the same problem would happen for a normal selector, not just a pseudoselector. The problem in the second example is that %pseudoselector doesn't have any rules defined within it. If any logical person were to look at these examples, they might expect both of them to compile into the following:

.class{
    color : green;
}

And indeed it did for quite a while, all the way up through node-sass v3.5.1. However node-sass v3.5.1 is stricter with its parsing and compiling than its previous versions. So much so that the example above became flagged as invalid Sass, which infact it is - even though it can be logically resolved.

The fix here is actually provided by the error itself. By adding !optional to your @extend like (@extend %pseudoselector !optional) the inclusion isn't executed if:

a. The extend selector doesn't exist at all
b. The extend selector "exists", but is infact empty.

So if you're getting an error similar to the one above, check your Sass to make sure it's actually valid, or allow the @extend to be ignored with the !optional attribute.

If you don't want to fuss with this, you can fix the problem by making sure your version of node-sass is v3.4.2 or below.

A Note about Gulp-Sass

This problem was surfaced to me during a seemingly simple upgrade to gulp-sass - from v2.2.0 to v2.3.0. The big catch is that part of the gulp-sass v2.3.0 release is an update to node-sass v3.5.1. As we just explained, this update to node-sass introduced this breaking change in a dependency, making it look like a breaking change in gulp-sass itself. This was remedied in gulp-sass v2.3.1, which reverted the gulp-sass dependency back down to v3.4.2. So if you didn't update to gulp-sass v2.3.0 and you now update to the most recent version, you won't see a thing. But people that updated a bit earlier saw this node-sass change raise its ugly head.