More missed chances: What minifiers also leave behind


The journey continues

It has been a while, hasn't it, Internet?

I missed you too.

I'm still probing what optimizations the modern CSS minifiers are capable of. Previously, I mainly looked at colors and whitespace elimination. Let's dive right in to the second part, no?

I wrote a bit of a program to help me mark my students' work. I figure I can use it for this as well. The main idea of the software is that it will bin the submissions into either the majority/most common group or into one of the many dissent groups. The idea is that I will get to see what were the mostly commonly made mistakes. For the purpose of this article, the minifiers will take on the role of homework submissions.

I inserted line breaks in the minifier output by hand, wherever it would help legibility.

I'm counting on you!

Counter names can be a bit long, no?

-- Original --
p {counter-reset: kittensarefluffy}
-- Results --
Majority:
cleancss crass cssnano csso ----
p{counter-reset:kittensarefluffy}

None of the minifiers reduced this counter name to something shorter. I'm not an expert, but I think this is because the counters could be spread over many CSS files (or maybe even modified from JavaScript, though I haven't seen how from my very limited research)? I dunno. Regardless, the counters were left alone.

Well, except...

-- Original --
p { counter-reset: black }
-- Results --
Majority:
cleancss crass cssnano csso ----
p{counter-reset:#000}

Yup. Every single minifier mangled this one. What if I had named a different counter #000 already? Am I even allowed to name counters like this? Oh noes! I don't think it is unreasonable that someone could name a counter after a color, at least. Not likely, but not impossible.

At any rate, I'm not commenting on the correctness, just comparing the minifiers against each other and against my expectations.

Are you sizing me up, boy?

Next up, sizes! We have all those units, might as well use them, right? Sizes are a popular thing that can go into CSS, so let's start with some absolute units.

Millimetres to Centimetres

We can write always things in millimetres, but centimetres might be shorter. It really depends on the circumstances. In this case, it should be worth it to do a rewrite.

-- Original --
p{font-size:100mm}
-- Results --
Majority:
cleancss cssnano csso ----
p{font-size:100mm}
Dissent group 1:
crass ----
p{font-size:10cm}

Yup. You can change the unit to get rid of the terminal zeroes. Only crass performs this one. Color me surprised.

Centimetres to Millimetres

Going the other way, maybe?

-- Original --
div{width:99.9cm}
-- Results --
Majority:
cleancss cssnano csso ----
div{width:99.9cm}
Dissent group 1:
crass ----
div{width:999mm}

Crass can strip out the decimal point by changing the unit. Very nice.

Q and...

We aren't done here, not yet. This one made me scratch my head for a tiny bit.

This trouble comes from another example of unit reduction in action:

-- Original --
p{font-size:2mm}
-- Results --
Majority:
cleancss cssnano csso ----
p{font-size:2mm}
Dissent group 1:
crass ----
p{font-size:8q}

Yep. Once I got over the shock, I learned that a q is an actual unit. It means 0.25mm. The things you learn...

I had some issues with the q unit on Chrome, but that might have just been my horrid CSS giving up the ghost under the strain. Either crass is breaking things, Chrome is (or at least was) buggy with q, or the other three are missing an optimization. My Firefox did not have any issues with it.

At least you could do this much...

Some simple optimizations of values can be done without changing of the unit.

-- Original --
div{width: 0.510mm}
-- Results --
Majority:
cleancss crass cssnano csso ----
div{width:.51mm}

See? It isn't that the numbers are totally untouchable! You can always take away the trailing and leading zeroes. Everyone gladly performs this one.

What goes round...

How about the situation of being "almost there"? We could just say "close enough" and call it a day.

-- Original --
p{font-size:0.999999999cm}
-- Results --
Majority:
cleancss cssnano csso ----
p{font-size:.999999999cm}
Dissent group 1:
crass ----
p{font-size:1cm}

Crass notices that the number can be made smaller through rounding. I'm not going to comment on correctness of this one, though it made be feel a touch uneasy. I'm guessing that at nine nines that is pretty much a one anyway and it would not even change a single pixel on the screen.

Subatomic-level CSS

How about things that are so small that you can barely see them?

-- Original --
p{font-size:0.000000001cm}
-- Results --
Majority:
cleancss csso ----
p{font-size:.000000001cm}
Dissent group 1:
cssnano ----
p{font-size:1e-9cm}
Dissent group 2:
crass ----
p{font-size:0q}

Cssnano surprises with a change to the scientific notation. Otherwise this is pretty much as expected, given how the previous examples went.

Getting structural

Next up, some of the more fun optimizations. We are not just transforming values and trimming whitespace anymore. We are actually reasoning about the content of the CSS on a much broader level.

I was here first!

First of all: If we set something up twice within one selection, the second time takes precedence.

-- Original --
p{font-size:10px;font-size:12px}
-- Results --
Majority:
cleancss csso ----
p{font-size:12px}
Dissent group 1:
cssnano ----
p{font-size:10px;font-size:12px}
Dissent group 2:
crass ----
p{font-size:9pt}

Cssnano does not do this merge. Crass does and also changes the unit.

Selectors ate my neighbours!

We can also merge neighbouring selectors.

-- Original --
p{font-size:10px}
p{font-size:12px}
-- Results --
Majority:
cleancss csso ----
p{font-size:12px}
Dissent group 1:
cssnano ----
p{font-size:10px;font-size:12px}
Dissent group 2:
crass ----
p{font-size:9pt}

Cssnano does at least merge neighbours. Everything else is as in the example before.

What's in the selector?

We can also merge things just based on the content alone.

-- Original --
p { font-size: 10px }
figure { font-size: 10px }
-- Results --
Majority:
cleancss crass cssnano csso ----
figure,p{font-size:10px}

Yup. No question here. All is well. Everyone made this merge.

We have something in common!

And sometimes you should be able to just pick the common bits of each selector and if it is to your advantage.

-- Original --
p{color:tan;font-size:10px}
b{color:tan;font-size:20px}
-- Results --
Majority:
csso ----
b,p{color:tan;font-size:10px}
b{font-size:20px}
Dissent group 1:
cssnano ----
p{font-size:10px}
b,p{color:tan}
b{font-size:20px}
Dissent group 2:
crass ----
p{color:tan;font-size:10px}
b{color:tan;font-size:20px}
Dissent group 3:
cleancss ----
b,p{color:tan}
p{font-size:10px}
b{font-size:20px}

Four minifiers, four answers. Crass did not touch anything here. Cleancss and cssnano actually arrive at pretty much the same answer, even though they list it in a different order. Csso does a really clever thing. That tan color does not need to be separated out to be by itself.

Breaking up and splitting our stuff

How about going the other way? We could take the factored out values and stuff them into each individual selector.

-- Original --
.areallyreallyreallylongclass,.differentreallyreallylongone{color:tan}
.areallyreallyreallylongclass{size:10px}
.differentreallyreallylongone{size:15px}
-- Results --
Majority:
cleancss crass cssnano csso ----
.areallyreallyreallylongclass,.differentreallyreallylongone{color:tan}
.areallyreallyreallylongclass{size:10px}
.differentreallyreallylongone{size:15px}

No one touches anything. One would think that throwing that color:tan into each of the two selectors would have been a benefit. Guess not.

I tried to get the minifiers to trip up while doing those, but at least they did not make things worse. It seems that the changes don't occur unconditionally, but rather only when it is beneficial to do so.

Closing thoughts

Whew!

As always, corrections welcome. I am still a complete bumbling fool. :)

CSS minifiers are really fun to work with, mainly for the variety of the optimizations performed.

Next up, next week or so: Media queries, more stuff about values and some more structural things too.

The creation of this dissent sorter program is ironic considering that I'm not teaching anymore. I created a teaching program and the only use it will see is this.

I love automation.

Old habits die hard.

Academia kinda sucks.

I'm for hire now. Le Sigh. Email's in the footer and the CV is here.

Did that bit about the q unit amaze you? Why don't you amaze me in return and give a hug to the button below? There might be candy inside[*]!

Donate

[*] There isn't candy inside, but you do get a chance to be sweet.


Past: The lesser known CPUs: Lattice Mico8