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()

Resulting CSS:

.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

Resulting CSS:

.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:

I also created a slightly more advanced example:

#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.