Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Extend > Reducing CSS Size". Not always the case. #177

Closed
maniqui opened this issue May 21, 2014 · 9 comments
Closed

"Extend > Reducing CSS Size". Not always the case. #177

maniqui opened this issue May 21, 2014 · 9 comments

Comments

@maniqui
Copy link

maniqui commented May 21, 2014

Using the :extend(.selector all) pseudo-selector not always provides a shorter output (ie. smaller filesize), compared to the output generated when using the same selector (the one being :extend()ed) as a mixin.

Of course, this could be expected as using :extend(.selector all) is similar but not exactly the same to using a mixing.
Why?
Because when using :extend(.selector all), it even extends the selector when it's used in more complex selectors (for example, as a child, like in .parent .selector).
Using a mixin doesn't cover those cases.

A quick example:

// Input, using  :extend(.a all):
.a {
  prop: value;

  &:hover,
  &:focus {
    prop: value;
  } 
}

.parent .a { // Here is a "complex" selector which is also extended.
  prop: value;

  &:hover,
  &:focus {
    prop: value;
  }
}

.some-selector {
  &:extend(.a all); // We extend .a, everywhere it's used as a selector.
}
// Output:
.a,
.some-selector {
  prop: value;
}
.a:hover,
.a:focus,
.some-selector:hover,
.some-selector:focus {
  prop: value;
}
.parent .a,
.parent .some-selector {
  prop: value;
}
.parent .a:hover,
.parent .a:focus,
.parent .some-selector:hover,
.parent .some-selector:focus {
  prop: value;
}

Output length: 289 characters

Now, a similar use case, but using .a as a mixin.

// Input
.a {
  prop: value;

  &:hover,
  &:focus {
    prop: value;
  }

}

.parent .a { // This one will not be "mixed in".
  prop: value;

  &:hover,
  &:focus {
    prop: value;
  }
}

.some-selector {
  .a;
}
// Output
.a {
  prop: value;
}
.a:hover,
.a:focus {
  prop: value;
}
.parent .a {
  prop: value;
}
.parent .a:hover,
.parent .a:focus {
  prop: value;
}
.some-selector {
  prop: value;
}
.some-selector:hover,
.some-selector:focus {
  prop: value;
}

Output length: 242 characters

As you can see, the version using :extend is some bytes larger (and, let me state it again, it include some extra rules not present in the mixin output).

In case you are looking for some real case examples, I suggest you to try extending and mixing in Bootstrap, using the classes .btn and .panel, for example.
Extending the .btn class will get you a far more larger output than using it as a mixin. Of course, when extending, there are more selectors "covered" than when mixin in.
While doing the same for the .panel class leads to a shorter output when using :extend(.panel all) than when using it as a .mixin.

TL;DR: using :extend() usually leads to shorter CSS output, but that's not always the case.

@seven-phases-max
Copy link
Member

Well, the example is not valid since the compared results are not equivalent CSS styles. Besides, the docs never say:

extend always produce shorter result

The statement there is:

Mixins copy all of the properties into a selector, which can lead to unnecessary duplication.

that's it.

And the valid example of the extend producing larger output than a mixin-based one would be:

// using extend:

.navbar-inverse .navbar-brand {
    color: #999;
}

.my-navbar-inverse .navbar-brand {
    &:extend(.navbar-inverse .navbar-brand);
    border-color: #fff;
}

vs.:

// using mixin:

.navbar-inverse .navbar-brand {
    color: #999;
}

.my-navbar-inverse .navbar-brand {
    .navbar-inverse .navbar-brand();
    border-color: #fff;
}

These snippets are equivalent and the mixin variant produces more compact output (and to be honest, this is supposed to be quite self-evident stuff).


Extending the .btn class will get you a far more larger output than using it as a mixin.

It's just the same story as with your example. Expanding Bootstrap .btn or .panel classes into another class as mixins does not create equally behaving CSS as with re-using them by extend(... all) so it does not make any sense to compare these sizes.


Either way, what is your suggestion? I can't see any issue.
(This info could be a good blog post/article instead (assuming a correct example is used)).

@maniqui
Copy link
Author

maniqui commented May 21, 2014

@seven-phases-max: thanks for the feedback. You are correct about my examples not being valid. Sorry, my bad.
While writing the post, I couldn't come up with an example like yours, where the mixin version leads to shorter output than the extend version while at the same time generating equally behaving CSS.
But then, while looking at your example, I remembered that the first time I noticed that using :extend could backfire (that is, leading to a larger output than expected, when the goal was to achieve shorter one) was when I used :extend to extend classes from a grid system.

Just to provide an example (pretty similar to the one you provided)

// using extend:

.col-1 {
    width: 10%;
}

.my-col-1 {
    &:extend(.col-1);
    color: black;
}

.my-other-col-1 {
    &:extend(.col-1);
    color: red;
}

.some-other-col-1 {
    &:extend(.col-1);
    color: blue;
}

.yet-another-col-1 {
    &:extend(.col-1);
    color: green;
}

vs

// using mixin:

.col-1 {
    width: 10%;
}

.my-col-1 {
    .col-1;
     color:black;
}

.my-other-col-1 {
    .col-1;
    color: red;
}

.some-other-col-1 {
    .col-1;
    color: blue;
}

.yet-another-col-1 {
    .col-1;
    color: green;
}

In the above example, as in yours, the output of the mixin version (224 chars) is shorter than the one using extends (238 chars). Just a mere 5.88% shorter.

But that percentage start to grow quickly if the selector strings are larger.
In the following example, the mixin version output is 18.8% shorter than extend version output (which is the same as saying that the extend version output is 23.2% larger than the mixin version output). Add some more rules and some larger selectors and the difference keeps growing geometrically.

// using extend:
.col-1 {
    width: 10%;
}

.some-parent .my-col-1 {
    &:extend(.col-1);
    color: black;
}

.some-parent  .my-other-col-1 {
    &:extend(.col-1);
    color: red;
}

.some-parent  .some-other-col-1 {
    &:extend(.col-1);
    color: blue;
}

.some-parent  .yet-another-col-1 {
    &:extend(.col-1);
    color: green;
}

vs

// using mixin

.col-1 {
    width: 10%;
}

.some-parent  .my-col-1 {
    .col-1;
    color:black;
}

.some-parent .my-other-col-1 {
    .col-1;
    color: red;
}

.some-parent  .some-other-col-1 {
    .col-1;
    color: blue;
}

.some-parent .yet-another-col-1 {
    .col-1;
    color: green;
}

Bottom line: my suggestion? Not sure. As you said, a good blog post could help to clear out that using extend doesn't lead necessarily to shorter output, and that there are situations where using mixins could generate shorter output than using extends.

@matthew-dean
Copy link
Member

@maniqui There is already a community agreed-upon solution for this. It just has yet to be implemented. It's in less/less.js#1155 but I've summarized it in a new issue because I think it's been forgotten: less/less.js#2101.

So, to make :extend behave like a mixin, and import/extend all nested selectors, but only apply an exact match to the selector, here's how it would look like to you

// Input, using  :extend(.a deep):
.a {
  prop: value;

  &:hover,
  &:focus {
    prop: value;
  } 
}

.parent .a { 
  prop: value;

  &:hover,
  &:focus {
    prop: value;
  }
}

.some-selector {
  &:extend(.a deep); 
}
// Output:
.a,
.some-selector {
  prop: value;
}
.a:hover,
.a:focus,
.some-selector:hover,
.some-selector:focus {
  prop: value;
}
// .parent .a { } is ignored, as it's not an exact match

Your scenario and comments are similar to ones made as the spec was being developed, but unfortunately, as I said, it hasn't landed in the parser yet. I'd put your two cents into less/less.js#2101 if you want to see this happen. I think the "deep" keyword would definitely be a common use case.

@seven-phases-max
Copy link
Member

So I'm tyrantely closing this (sort of "merging" to #138). Maybe... someday... we have a "Book" section in the docs where we share some articles for "best practices / design patterns" stuff.

@matthew-dean
Copy link
Member

Maybe... someday... we have a "Book" section in the docs where we share some articles for "best practices / design patterns" stuff.

That seems more a blog / tutorial site. I would keep that separate.

@seven-phases-max
Copy link
Member

That seems more a blog / tutorial site. I would keep that separate.

The exact www location is not important. There's no difference between http://lesscss.org/book and http://lessbook.org

@matthew-dean
Copy link
Member

Sure there is: Pagerank of article pages, public perception, user psychology, etc. :-)

I think if it's an official subset of lesscss.org (maintained by less-docs crew), I'd keep it on same domain for sure. If it's a spin-off site run by different people, then another domain makes sense. I would suggest /blog (or blog.lesscss.org) or /tutorials rather than /book, which sounds like a static (unchanging) eBook.

@seven-phases-max
Copy link
Member

Sure there's not - in context of this topic (or more specifically the reason I put for closing it).

Pagerank of article pages, public perception, user psychology

This all is for less/less-meta#2 ;)

P.S. Sorry if I sound harsh a bit, I don't intend to of course I'm more about being mildly sarcastic (so if there's any pun it's just my language skills).

@matthew-dean
Copy link
Member

in context of this topic

Haha right that. :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants