Exploring the Stylus Cache Function

Looking through the Stylus docs, I found a function I hadn't encountered before: cache.

Cache might be useful for your own mixins. By default, mixins output all resulting CSS each time they are used:

mixin()  
  background red

.foo
  mixin()

.bar
  mixin()

Compiles to:

.foo {
  background: red;
}
.bar {
  background: red;
}

But is this optimal? If we enter this CSS into CSSO, we get the following output:

.bar,.foo{background:red}

CSSO grouped the selectors into a single declaration because they match, addressing one concern with mixins: that they create a lot of duplicate CSS.

You may have noticed this mixin defies a piece of widely acknowledged preprocessor wisdom. It doesn't accept any arguments, and always has the same output. This might be better implemented as an extend, resulting in the same grouped output as CSSO.

Let's adjust the example so that it accepts a colour argument:

mixin($colour)  
  background $colour

.foo
  mixin red

.bar
  mixin red

Whereas our original example could be implemented as an extend, this one can't. The background colour is dynamic and passed into the mixin each time it is called.

Let's find out how we can use the cache mixin to create the grouped output generated by an extend and CSSO:

mixin($colour)

  +cache('mixin', $colour)
    background $colour

.foo
  mixin red

.bar
  mixin red

Output:

.foo,
.bar {
  background: red;
}

What happened here?

The call to the cache function passes a key that identifies the colour passed. Cache accepts multiple arguments that together form a key. Whenever this key matches a previous cache entry, Stylus extends rather than duplicates it.

We prefixed the key with the mixin name to avoid conflicts with other cache entries in the document.

What happens if we use the mixin again with a different colour?

.foo
  mixin red

.bar
  mixin red

.bat
  mixin blue

Output:

.foo,
.bar {
  background: red;
}
.bat {
  background: blue;
}

Stylus grouped .foo and .bar because their cache keys ('mixin', red) match. .bat is separate because its cache key did not match.

I made a CodePen containing this example so you can see it in action:

See the Pen Stylus Cache Function - Basic Example by Ash (@Ash) on CodePen.

I also created a slightly more advanced example:

See the Pen Stylus Cache Function - Advanced Example by Ash (@Ash) on CodePen.

A word of caution

The topic of mixins vs extends has been widely covered by others before me.

As mentioned above, extending is generally considered a better option than a mixin if the mixin in question always has the same output.

When we extend, we duplicate the selector rather than the shared CSS. This often results in smaller uncompressed CSS.

In reality, the decision is a lot muddier. Belly conducted tests that indicate although extension results in smaller uncompressed CSS, gzip can actually make mixin based CSS smaller.

CSSO has many rules regarding intersecting properties and matching declarations. It does not simply merge them. Take this example:

.fooooooooooooo {
  background: red;
  font-size: 1rem;
}

.baaaaaaaaaaaaar {
  background: red;
  font-size: 2rem;
}

CSSO does not merge the intersecting background property, because that would actually be larger.

CSSO output:

.baaaaaaaaaaaaar,.fooooooooooooo{background:red;font-size:1rem}.baaaaaaaaaaaaar{font-size:2rem}

Versus:

.fooooooooooooo{background:red;font-size:1rem}.baaaaaaaaaaaaar{background:red;font-size:2rem}

CSSO's optimisations are documented, and make for an interesting read.

You should be careful when deciding if it's best to use a mixin or an extend, and whether it's advantageous to cache parts of a mixin.

Regardless, I think cache is a pretty nifty feature of Stylus. Just be thoughtful when implementing it.

Documentation for the Stylus cache function.