Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?

When you write

.a .b .c .d {
  color: blue;
}

What you're saying is

Any element with class d that's nested beneath elements with classes a, b, and c in that order should have blue text.

Then when you write

.e .f .g .h {
  @extend .d;
}

what you're saying is

Any element with class h that's nested beneath elements with classes e, f, and g in that order should be styled as though it also has class d.

The conjunction of these two statements implies

And element with class h that's nested beneath elements with classes e, f, and g in that order and beneath elements with classes a, b, and c in that order should have blue text.

It's important to note that the two nesting requirements here don't have any particular order. Both of the following spans should have blue text:

<div class="a">
  <div class="b">
    <div class="c">
      <div class="e">
        <div class="f">
          <div class="g">
            <span class="h">Order 1</span>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

<div class="e">
  <div class="f">
    <div class="g">
      <div class="a">
        <div class="b">
          <div class="c">
            <span class="h">Order 1</span>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

The way to represent this in CSS is

.a .b .c .d, .a .b .c .e .f .g .h, .e .f .g .a .b .c .h {
  x: y; }

Note that fully satisfying those semantics would produce exponential output, so Sass elides all but the two most likely permutations for space reasons.

michaek commented Aug 20, 2014

Thanks for expressing this so clearly. I agree that this makes sense as a safeguard for possible styling, but I'm concerned that it's excessively defensive. There are conflicting use cases:

  • I'm a novice CSS user, and I want my styles to "just work"
  • I'm an expert CSS user, and I want to closely control the generated styles

I realize for the second case there's already an established best practice to extend only placeholder selectors, but I'm trying to determine if there's a way to chip away at the "third rail" qualities of extend, making it safer to use in both cases.

It's much easier to explain extending A from B gives you a new selector with A replaced by the selector B than extending A from B tries a handful of strategies to make sure everyplace you were using A you can now use B. The second is pretty cool, but it's hard to reason around what is actually happening.

(I say "a handful of strategies" because there's also the behavior of https://gist.github.com/anonymous/aacad2e475e212450f26 .)

We're doing a lot to try to protect the user from the effects of their intentions, while it may be preferable to allow simpler logic to do the wrong thing predictably, empowering users to better protect themselves.

Owner
nex3 commented Aug 20, 2014

I would argue that the user's intention when writing @extend is that it follows the semantics listed above, not that it perform some specific mechanical replacement. Users shouldn't have to think about what the underlying implementation of these semantics are. The behavior you're suggesting is mechanically-oriented rather than being focused on how the actual elements get styled, which is bad.

michaek commented Aug 20, 2014

I agree that users shouldn't have to think about it - but in practice the mechanics of Sass have caused a lot of users to find themselves surprised by the need to think about it pretty hard, because the CSS that's been generated to protect them has caused more problems than it's solved. That said, the solution to that problem already exists: to not use Sass's features. (You've probably already read a number of articles like this: http://csswizardry.com/2014/01/extending-silent-classes-in-sass/)

But I see that we have a difference of philosophy about what Sass should (ideally) provide. As a stylesheet author, I'm ok with using a small subset of Sass's features to achieve the right balance of power in authoring and performance in the browser. As a contributor to libsass, it'll be harder for me to put resources behind the non-naive implementation of extend, though I agree in principle that Sass implementations should be consistent.

Thanks for filling me in on the reasons Sass does what it does where extend is concerned!

but in practice the mechanics of Sass have caused a lot of users to find themselves surprised by the need to think about it pretty hard

I think we've done a bad job at actually explaining what extend does. Most examples are very simple and they build a mental model that is based on a flawed understanding. Discovering that they misunderstood is a painful process, obviously. I don't think the solution is to not use @extend. I think the solution is that we explain selector inheritance more clearly so that people stop developing the wrong expectation in the first place.

michaek commented Aug 21, 2014

Even with an adequate understanding of how @extend works, increasing the number of selectors to cover the bases of what a user could possible mean makes it easier to bump into the <IE9 selector limit. The discovery is not so much that they misunderstood (misunderstanding being common enough that it's hopefully not painful!), but that the promise made by the feature - that the user shouldn't have to care about the CSS generated so long as Sass meets their semantic needs - is actually dangerous except in simple cases.

I haven't found it hard to understand what @extend does - my struggle is to understand why. @nex3 has given a good explanation of the reasoning in this case, but I haven't yet been able to find a CSS author who would ask for that behavior, let alone expect it. "The semantics I want should just work at any cost, and I should never have to think about the generated CSS" doesn't seem to be a widely-held sentiment among the developers I know. :)

michaek commented Oct 8, 2014

Hi, @chriseppstein and @nex3. I know I started this conversation before really getting into the @extend implementation in libsass, but even after completing the implementation, I'd like to discuss the possibility of simplifying the behavior of @extend. One of my team members described @extend like this: the tool isn't dangerous because it's sharp, it's dangerous because it's unpredictable.

A possibility that I've considered is adding an option to prevent @extend operating on anything other than placeholder selectors, enshrining that "conventional wisdom" as a compiler feature. Obviously, not everyone wants to extend only placeholders, so the option's default would retain the current @extend behavior.

A less sensible option is allowing a "naive_weave" function that eschews the philosophy of "the selector I'm extending from should work everywhere the selector I'm extending to does" in favor of string substitution. It's obviously not as powerful, but I think the power of @extend often works against CSS authors - not just because they don't understand @extend but because even with a good understanding it's very hard to reason around. (My terrible illustration of the concept: DealerDotCom/sass@fd4048e)

I don't disagree with the ideas and goals behind the current implementation and behavior of @extend. I think it's a really impressive solution to the defined problem. But I'd also like to imagine providing options that might give authors who prefer it an @extend with a smaller scope - a sharper tool that's easier to predict.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment