Applying Preact minification tricks to CSS
📖 tl;dr: Making code shorter and simpler is a valuable skill to have. Most of that knowledge can be transferred to other areas and languages. This has very real world benefits where the dominating factor of performance is the network connection.
One thing we're known for over at Preact is making code a lot smaller than it originally was. In fact the tiny size is one of the main strengths of Preact. A few days ago a friend of mine Jan Willem Henckel shared a link to CSSBattle, where users can compete against each other in trying to replicate an image with just CSS and HTML. The kicker? You have to use as few characters as possible to get the most points! Currently he holds the top spot with a mere 56 characters for rendering the following page:
It's a greenish rectangle inside a brown canvas. Not the prettiest for sure, but enough to get us going. Intrigued and always up for a code challenge, I gave it a go. Although I mainly write JavaScript or TypeScript these days I was curious to see how much of the knowledge gained from working on Preact can be applied to other areas. The mindset for minifying and optimizing code in general should be very similar in theory, but we're dealing with a different language. Some of the optimization tricks from JS obviously won't transfer to that.
The challenge starts with the following code snippet for the image above:
<div></div>
<style>
div {
width: 100px;
height: 100px;
background: #dd6b4d;
}
</style>
As we can see it's just a single div
and a style
element that holds the CSS. All in all this snippet comes in at 105 characters which is close to twice as much as our target. Here we notice that we have a few repeating strings. The word div
is used three times and style
occurs twice. Unfortunately, the challenge is measured in characters and not in bytes. If would be the latter we could leverage the famous gzip algorithm to compress the final output like we do with Preact.
Minification tricks that work everywhere
The first obvious optimization we can do is to get rid of unnecessary whitespace and reduce it to one line:
<div></div><style>div{width:100px;height:100px;background:#dd6b4d;}</style>
In the real world this is typically handled automatically by build-tools as the last step in the asset pipeline. That's a good thing, because the above snippet is a lot harder to read than the multiline version. With just this we're down to 75 characters, but we can do a lot more. Since pretty much the dawn of time HTML has supported the style
-attribute on elements that can be used to apply CSS inline. This helps us to remove both the opening and closing <style>
tag all together. Even better: It also allows us to remove the selector portion of the CSS 🎉
<div style="width:100px;height:100px;background:#dd6b4d"></div>
This lands us at 63 characters, but if we compare our result it looks nothing like the image we're trying to render:
We still need to change the colors and reposition the rectangle. So how can we do that? We don't want to add a second element as they are quite costly in terms of characters. That's when I remembered the single div challenge from a few years back which challenged web developers and designers around the world to make the most out of a single <div>
. That was when the industry collectively discovered that we can use (abuse?) the box-shadow
-property to create multiple shapes at once.
To do that we need to remove the height
and width
declaration to make our div
spread over the whole canvas. Remember that a div
is a block-element! In exchange I tried to position the inner rectangle with a few em
values and applied the correct color codes.
<div style="box-shadow:-24em 0 0 12em#b5e0ba,0 0 0 19em#5d3a3a"></div>
We have gotten a little bigger with this version at 70 characters, but our result now looks exactly like our target image.
Coloring strategies
Looking at Jan's score we can see that it's possible to reduce our snippet by 14 more characters. Maybe we can save something by finding more optimal color codes. As most of you know there are various ways to simplify the hexadecimal string. CSS allows you to turn #ffaacc
into #fac
allowing us to save 3 characters. Sometimes the color code is even shorter than the hex string as it is the case with gray
versus #c0c0c0
.
Comparing the expected color values with the CSS spec unfortunately doesn't leave us with any reductions. The color codes are unique and can't be compressed. But we're now defeated yet, there are still various avenues we can explore 🍀.
One of those is a feature that was introduced with HTML5, which allows you to remove the quotes around an attribute. Our value needs whitespace in it, but it turns out that we can substitute the space character with a plus and save the two quoting letters.
<div style=box-shadow:-24em+0+0+12em#b5e0ba,0+0+0+19em#5d3a3a></div>
At this point I was wondering whether there are any other length units that are bigger than em
. Jan gave me a hint about this before starting this challenge so it should work out. Looking at all the available units in CSS it seems like in
for inches
fits the bill.
<div style=box-shadow:-4in+0+0+2in#b5e0ba,0+0+0+4in#5d3a3a></div>
After converting the unit to inches we reached 65 characters which is better but nowhere close to 56. What else can we do? Remember when we established that we need a block-level element to span over the whole canvas? Maybe there is another one that has a shorter name. Reading through both MDN and the HTML-Spec I came across this list of all elements that are considered to be block-level. We had the right right nose for it here, because luckily for us the <p>
-tag is also part of the list!
Armed with our new findings we can swap out our div
for a simple p
element.
<p style=box-shadow:-4in+0+0+2in#b5e0ba,0+0+0+4in#5d3a3a></p>
Quirksmode is back from the dead
Only 5 more characters to go! So let's do something that you should never do outside of code golfing challenges: Reduce the code by making it invalid HTML but let it still parse correctly. Let me repeat that: Don't do this in any project! Those of us who've been around a bit longer remember the times when cross-browser compatibility was not so great and to be frank quite lacking. During those dark times of the web it was quite common for web pages to serve invalid HTML 🤷
This is troubling for the browser because now it only has two options on how to proceed:
- Bail out of rendering (bad, white page)
- Try to render it anyway and guess what the developer meant
The first option is a no go, because users never assume that the page itself is at fault. When something works in one browser and not the other the usual conclusion is that the browser is broken. And how should they know that the page is actually causing issues? That burden lies on us developers to make sure we serve valid HTML.
To combat this browser have implemented a "quirksmode" in which the browser tries to guess what the correct result should be. Depending on the browser that is used this can lead to quite unpredictable results, but in our case, and only because it's a code-golfing challenge, we can get away with it as long as the rendered result stays the same.
What is this unholyness you ask? Well, we can remove the closing </p>
-tag 😬:
<p style=box-shadow:-4in+0+0+2in#b5e0ba,0+0+0+4in#5d3a3a>
We're now at 57 characters which is just one more than Jan's high score.
I tried finding a way to remove the last character but after trying for longer than I should, I haven't found a way to do so. Nonetheless I'm happy with the result. We effectively reduced the character count by half which is solid!
Conclusion
Even though CSS and HTML is quite different from JavaScript we could re-use a lot of common knowledge for minification. The most obvious one being stripping all possible whitespace characters. On top of that we leveraged more and more domain specific strategies by examining the spec closely. Although the result is not pretty visually we learned a lot of neat tricks in the process that can be very useful in real world applications 🚀🙌