SASS Antipattern: The Selector Bomb


SASS is a really powerful tool, which sometimes means that one has to be careful. It can generate all kinds of insane CSS that is hard for debugging and hard for the browser to parse and apply. Now and then, you have to take a step back and ask yourself if you really need to use SASS for the task at hand. I’d like to attempt to coin a new term for an anti-pattern that I, and many others, have stumbled upon.

The Selector Bomb

A gigantic selector that is hard to debug and hard for the browser to parse and apply, generated by SASS when using @extend  as a utility that spans across many selectors.

An Example

There is a lot of jargon in the above statement. Let’s break it down with a clear example with three pages. Keep in mind that three pages is not really a problem, but when you scale this up to a huge amount of pages, it really becomes a problem. This especially applies to utility classes – something like a clearfix class or a text-align class. Each page has a panel with a different background color. This is a fairly common pattern, especially if you’re concatenating all of your CSS into a single file. You want to share the common styles and yet still change the background color. You decide to use a SASS placeholder and use @extend  to bring in the common styles. Let’s have a look at the markup and SASS for each page.

First Page

  <div class="panel">
    <h2>Sass</h2>
    <p>CSS with superpowers</p>
  <div>
.page.sass {
  .panel {
    @extend %panel;
    background-color: #e1faea;
  }
}

Second Page

<div class="page stylus">
  <div class="panel css-library">
    <h2>Stylus</h2>
    <p>Expressive, dynamic, robust CSS</p>
  </div>
</div>
.page.stylus {
  .panel {
    @extend %panel;
    background-color: #def0fc;
  }
}

Third Page

<div class="page less">
  <div class="panel">
    <h2>Less</h2>
    <p>Leaner CSS</p>
  </div>
</div>
.page.less {
  .panel {
    @extend %panel;
    background-color: #ebebeb;
  }
}

And finally, the common styles are in the placeholder, which is extended in each panel.

%panel {
  padding: 1em;
  margin: 1em;
  width: 300px;
}

This will render these three components on each of the three different pages.

sass-panels

Unfortunately that will compile to this. See how it merges all of the selectors into one for the common styles? This is the selector bomb. Every time you add a page that has @extend %panel , it will add one more selector here. Do that across enough pages and you can get a huge selector that is hard to debug and hard for the browser to parse and apply.

.page.sass .panel, .page.stylus .panel, .page.less .panel {
  padding: 1em;
  margin: 1em;
  width: 300px;
}

.page.sass .panel {
  background-color: #e1faea;
}

.page.stylus .panel {
  background-color: #def0fc;
}

.page.less .panel {
  background-color: #ebebeb;
}

Solutions

There are a couple ways to get around this. Depending upon the context of what you’re doing, one of these (or even something else) may make sense. First, a mixin will alleviate the pain, since a mixin will actually copy the CSS everywhere it is included. This could cause more pain, since it makes the file size a bit larger. However, worrying about the size of a CSS file is very likely to be overthinking the problem.

@mixin panel() {
  padding: 1em;
  margin: 1em;
  width: 300px;
}

.page.sass {
  .panel {
    @include panel();
    background-color: #e1faea;
  }
}

.page.stylus {
  .panel {
    @include panel();
    background-color: #def0fc;
  }
}

.page.less {
  .panel {
    @include panel();
    background-color: #ebebeb;
  }
}

Secondly, CSS is called Cascading Stylesheets for a reason. Depending upon the context, it’s probably better to not even bother with SASS features. Sometimes SASS makes you forget about some of the reusability features that plain old HTML and CSS have in the first place. Just add a class for scope and style it globally. I know, I know, global is bad from what you’ve heard, but often it makes sense.  Here is what that might look like.

<div class="page sass">
  <div class="panel css-library">
    <h2>Sass</h2>
    <p>CSS with superpowers</p>
  </div>
</div>
<div class="page stylus">
  <div class="panel css-library">
    <h2>Stylus</h2>
    <p>Expressive, dynamic, robust CSS</p>
  </div>
</div>
<div class="page less">
  <div class="panel css-library">
    <h2>Less</h2>
    <p>Leaner CSS</p>
  </div>
</div>
.page .panel.css-library {
  padding: 1em;
  margin: 1em;
  width: 300px;
}

.page.sass {
  .panel {
    background-color: #e1faea;
  }
}

.page.stylus {
  .panel {
    background-color: #def0fc;
  }
}

.page.less {
  .panel {
    background-color: #ebebeb;
  }
}

Don’t avoid @extend and placeholders out of principle, but do be very careful, particularly if @extend is used for some kind of common utility. It will not scale out to many, many pages. Know what you’re using, and know what CSS it’s generating.


Advertisement

No Comments

Name
A name is required.
Email
An email is required.
Site
Invalid URL

No comments yet