On writing real CSS (again)

Submitted for your approval: many of us designers and front-end developers haven’t written a line of real CSS in years. Instead, we’ve been using pre-processors to write abstractions of CSS. This has gotten us into a lot of hot water. It’s time to ditch the pre-processors and start writing real CSS (again).

(TL;DR: Pre-processors like Sass and Less are too abstracted from production CSS; this blinds us to detrimental effects of our actions during development, locks us into vendor-specific syntax and features, and ultimately puts too much distance between the code we write and the code that is delivered to our users. Post-processors like PostCSS and plugins like cssnext offer some forward-looking alternatives to this.)

Why should I ditch my pre-processor?

  • You don’t need functions, if statements, extends, and other extension-language-specific features in your CSS. Shut up, no you don’t.
  • Even if you do need those things — no you don’t. Just stop it.

But seriously, now: Sass and Less (and Stylus, and…) have done a good job of providing us with ways to extend and modularise our CSS up until now. I’ve been writing my CSS with Sass since around 2011, and it has proven extremely useful (especially in terms of being able to use variables and various mathematical operations — features which are now being brought into new CSS specs, which we likely owe Sass et al. a debt of gratitude for).

But pre-processors have also introduced some issues I find problematic:

  • They encourage use of proprietary, non-standard syntax (are they $variables or @variables, @mixins or .mixins?)
  • That syntax is not portable (in other words, it’s specific to the vendor and cannot be utilised outside of that vendor’s product)
  • In a standards-based medium such as the web, proprietary, unportable syntax and languages are anathema
  • Pre-processors abstract CSS by nature; this has the effect of defamiliarising us with the CSS that gets spit out the other end of the processor, and thus the code that actually gets delivered to browsers (and users!)
  • Pre-processors are powerful, but that power also makes it far too easy to write shitty, bloated and/or brittle code (think over-nesting, over-extending, etc.)
  • Additionally, when that power extends to logic and functions (as referenced above), our stylesheets quickly turn into code that can be incredibly hard for even veteran CSS authors to comprehend. I’ve seen some functions and logical conditions in Sass stylesheets that, even as someone with years of Sass experience under my belt, left me scratching my head for far too long. This disconnect, and time spent on comprehending these blocks of code, has never outweighed whatever that code was spitting out for me.

The deeper I get into front-end development, the more I find myself wanting to be less abstracted from my end product. There’s a reason for that:

When I lose that abstraction — when I’m forced to write plain CSS and not rely on pre-processor magic — I find myself solving problems in a far more considered and intentional way.

(When you’ve been writing CSS long enough, you learn to spot red flags a mile away. The nuance and abstraction of pre-processor syntax, for me, can make it harder to detect code smells in my CSS; although you can learn to spot code smells in Sass, I find it more challenging and less obvious.)

In fact, I’ve often felt that, if CSS just had variables built-in, I’d ditch my pre-processor altogether and just write straight CSS.

Well, as it happens:

Better CSS modules are coming

Not only do the latest CSS modules’ specs include variables (or, as the W3C calls them, custom properties), it also includes things like custom selectors, custom media queries, colour manipulations, and more that I don’t even understand half of. In short: these new modules are going provide us with a lot of useful abilities, and make a ton of pre-processors irrelevant as soon as browser support becomes broad enough. CSS custom properties, for example, will be even more useful than pre-processor variables, providing the ability to scope them to selectors, write fallbacks inline with declarations, override the custom property, and more.

“Well, that’s all well and good for the future”, I hear you say, “but what about right now?

And to this, I say: just write to the new specs today.

“What?”

Yes.

“But…?”

No. Shut up. You can do it. Right now.

“Huh?”

Let me explain.

A different approach to the pipeline

One of the biggest problems with CSS pre-processors is the “pre” part of the word. Instead of writing valid CSS, pre-processors force us to design our web things using this fancy magical mutation of CSS that only that one pre-processor can understand. Only after we’re done learning and writing our styles in this wondrous manner do we pass our incantations to the pre-processor, which then spits out something that a web browser can actually do something with — that is, real-life CSS.

You don’t have to think too long and hard to realise that this is really messed up. We’re supposed to be designing and coding for the web, not for The Web As Abstracted By Pre-Processors And Co. It’s almost as bad as, say, drawing a highly detailed, intricate picture of a website in a proprietary digital image format, and then handing that picture to someone else and saying “HERE MAKE THIS REAL KTHXBAI” and hoping what materialises ends up being hopefully maybe good enough.

What if we turned that process on its head? What if we instead authored our styles in 100% valid, future-proof CSS — making use of features that are now (or will soon be) available in actual browsers — and used a processor only to provide fallbacks for browsers that do not yet support those cutting-edge features? This would put valid CSS at both ends of our production line, and would cut out all that proprietary, “CSS-like” nonsense.

Oh, and that valid CSS we started with? Once the upcoming CSS modules gain sufficient support, it’s the only CSS we’ll need. We can ditch the generated, “fallback” CSS.

Wouldn’t that be stupid-awesome crazy-go-nuts good?

Well, that’s what writing CSS with PostCSS and cssnext is all about.

Shaking the Abstract Syntax Tree

PostCSS is a new kind of tool for working with CSS, which enables us to transform our CSS with JavaScript plugins. At its core, PostCSS is extremely agnostic about what exactly it does to CSS. All PostCSS is doing (I’m oversimplifying here) is taking your source CSS, transforming it into an abstract syntax tree that can be parsed and acted upon by JavaScript, and then outputting the result of those actions. It’s this sort of methodology that allows Autoprefixer to examine our source code and attach vendor prefixes to properties that require them, for example.

So, PostCSS, while it provides an extremely powerful foundation for transformation to our code, won’t actually give us any sort of valuable output on its own — it relies on plugins to accomplish this. This is where cssnext comes in.

cssnext is a plugin pack for PostCSS. It represents a new kind of tool, referred to as a “post-processor” or “transpiler” (which, while being a more technical term, is a more accurate descriptor). What does this mean, and how is this different from a pre-processor? Lyza Danger Gardner, in a recent panel discussion hosted by A List Apart, gives a great overview:

When I think of a post-processor, I’m thinking: you’re writing CSS to the published specs and standards, even if those specs and standards aren’t adopted or implemented in [all modern browsers]. You’re writing CSS directly, and then a set of tools is taking that CSS and translating it to the kind of CSS that browsers understand more globally.

As for the term “transpiler”, Wikipedia provides a decent description (additions in square brackets my own):

A source-to-source compiler, transcompiler, or transpiler is a type of compiler that takes the source code of a programming language as its input [future CSS] and outputs the source code into another programming language [current CSS]. A source-to-source compiler translates between programming languages that operate at approximately the same level of abstraction, while a traditional compiler translates from a higher level programming language [e.g. Sass] to a lower level programming language [CSS].

Bringing this all together, cssnext’s offering begins to sound very intriguing:

[cssnext] is a CSS transpiler that allows you to use tomorrow's CSS syntax today. It transforms CSS specs that are not yet implemented in popular browsers into more compatible CSS.

While this may sound simple, what PostCSS and cssnext are collectively offering us is a radical new approach to authoring CSS. We can use upcoming CSS modules’ features (and all their aforementioned benefits) today, keeping our source code to published CSS specifications, without having to learn any outside vendor’s syntax, and compile that code 3–30 times faster than with traditional pre-processors (according to PostCSS’s documentation).

To give you an idea of how this works in practice, I recently took on the not-too-trivial task of completely rewriting the distributed CSS library we use at my work, taking it from a Sass-based library to one built with PostCSS and cssnext. Where it wasn’t odd for our Sass-based library to take over 8 seconds to complete an initial compilation using Ruby Sass, our new library takes less than 1 second to run through PostCSS and cssnext initially, and less than ⅓ of a second on re-compilations. This a huge time-saver for developers. The new library also ditches all the needlessly complex functions, logic, and mixins we used in Sass, which helped to cut our compiled, minified CSS down by over 40% (although switching to an entirely object-oriented approach certainly helped with file size as well).

Summing up

To summarise, here’s what we gain by using PostCSS and cssnext, as compared to typical pre-processors:

  • extremely fast compilation times (in my example case, ~800% faster)
  • source code written in CSS, as defined by current and upcoming specs, which means:
  • no vendor-specific syntax (unless we write our own — be careful!), and thus:
  • source code that is immediately comprehensible to anyone with a half-decent understanding of CSS, and:
  • source code that is future-proof, portable, and easier to diagnose and debug

(I should note at this point that, because PostCSS is so agnostic about what plugins are authored for it, you could certainly get yourself into more hot water than any pre-processor could present to you. You can write whatever plugin you want for your CSS, so long as it abides by the plugin guidelines. With great power comes great responsibility.)

If you’re as excited as I am about all of this, I’d urge to get started by reading up on PostCSS and cssnext today. If you’re already familiar with tools like NPM and Gulp, you’ll be up and running with the ability to write real, better CSS in minutes (and if you’re not, well: there’s some fine googling for you to do today).

Happy coding!

Thanks to @jxnblk and @mrmrs_ for their feedback and input on this article.

A previous version of this article referred to upcoming CSS modules collectively as CSS4. See this article on why this is incorrect. Thanks @MoOx for the heads-up.