A look into writing future CSS with PostCSS and cssnext

Used by the likes of Twitter, Google and the BBC, I decided to take a look into the world of CSS post-processing with PostCSS to see what it’s all about.

What is PostCSS?

PostCSS is a CSS post-processor that transforms CSS using JavaScript plugins. By itself, PostCSS wont do anything to your CSS; you’ll need to add some plugins to get started. This not only makes it modular, but very powerful too.

It works by parsing CSS and transforming it into a CSS node tree, which can be manipluated with JavaScript (this is where plugins come into play). It then returns the modified tree and saves it. This is different to the way Sass (and other pre-processors) work, as you are essentially writing in a different language that gets compiled to CSS.

Pre-post-what?

To explain the difference between pre and post-processing in a simplified way, I’ll take unit conversions as an example. When writing Sass, we can use a function to convert a px value to rem:

/* input */
.selector { margin-bottom: rem(20px); }

/* output, assuming base font size is 1rem */
.selector { margin-bottom: 1.25rem; }

This is great as it saves us time calculating the conversion manually. With PostCSS, we can do better than this. Because it post-processes CSS, we don’t need to call any functions that get compiled to CSS. We can simply write in px values and it’ll get processed to rem automatically:

/* input */
.selector { margin-bottom: 20px; }

/* output, assuming base font size is 1rem */
.selector { margin-bottom: 1.25rem; }

PostCSS processes each declaration before running a calculation on each occurrence of a px value, to convert it to its value in rem. Note: Because of its modular nature, you will need to use the postcss-pxtorem plugin for this to work.

Writing future CSS with cssnext

We can take advantage of proposed additions to the CSS spec into our stylesheets by leveraging cssnext—a PostCSS pack—which contains multiple plugins for features such as custom properties and custom selectors:

/* custom properties */
:root {
  --heading-color: #ff0000;
}

/* custom selectors */
@custom-selector :--headings h1, h2, h3, h4, h5, h6;

/* usage */
:--headings { 
  color: var(--heading-color);
}

With cssnext, this will get processed to:

h1,
h2,
h3,
h4,
h5,
h6 { 
  color: #ff0000;
}

This is really neat—there are some exciting features coming to CSS. And because we’re writing future spec CSS, eventually the PostCSS build step won’t be needed when browsers implement it. You can find the full range of supported features here.

Next, I’m going to look at using PostCSS to extend what CSS can do with our own custom functions.

Taking a step futher and extending CSS with custom functions

Alternatively to—or alongside—cssnext, we can build our own custom functions with JavaScript to manipulate the parsed CSS. Going back to Sass, a function we use a lot is to generate a spacing unit—calculated from the base line-height—helping create a simple and maintainable vertical rhythm:

$line-height: 32px;

/* vertical rhythm function */
@function vr($amount) {
  @return $line-height * $amount;
}

/* input */
.selector { margin-bottom: vr(2) }

/* output */
.selector { margin-bottom: 64px; }

To do this with PostCSS, we can take it even further and create our own CSS unit instead of calling a function:

/* input */
.selector { margin-bottom: 2vr }

/* output */
.selector { margin-bottom: 64px; }

I really like this approach as it feels more natural, almost like we’re writing native CSS. The JavaScript behind this:

var vr = function (css) {
  var lineHeight = 32;

  css.eachDecl(function (decl) {
    if (decl.value.match(/vr/)) {
      decl.value = lineHeight * parseFloat(decl.value) + 'px';
    }
  });
};

This may seem a little more work, but if you’re not familiar with JavaScript, theres not actually that much happening. Here’s a commented version to dissect the function:

// Define the function to include in the PosCSS transforms list, and pass in the CSS node tree.
var vr = function (css) {

  // Set the line-height.
  var lineHeight = 32;

  // Loop through each CSS declaration using PostCSS's `eachDecl()` and pass in the declaration (`decl`) string.
  css.eachDecl(function (decl) {

    // Match if the declaration value has the 'vr' unit.
    if (decl.value.match(/vr/)) {

      // Replace the declaration value with the calculated value.
      // Use parseFloat() to remove the `vr` unit from the string and return a number.
      decl.value = lineHeight * parseFloat(decl.value) + 'px';
    }
  });
};

For those interested, I’ve made this into a more advanced plugin that requires no set up other than adding it to the PostCSS transforms: postcss-vertical-rhythm.

This is only a small example of what you can do with PostCSS. Hopefully at this stage you can see how powerful it can be, and how it’s different to a pre-processor. I’d recommend checking out the documentation and the plugin list to see what’s already available to drop into your project.

Speed

PostCSS claims to be 3-30 times faster than other pre-processors. Out of curiosity, I ran the above Sass function and PostCSS function on 10,000 selectors, each with 5 properties using the function. This makes 50,000 things to process. Here are the results:

Libsass 3.2:

libsass

PostCSS:

postcss

To my surprise, PostCSS is almost 13 seconds quicker at processing this particular function. That’s an incredible 34 times faster. Obviously this is not a real world project scenario, but it was an interesting test to see how well it performs—the last thing we want with a new tool is to decrease our build speed. It’s worth nothing that Libsass looks to be improving its speed in version 3.3, so I look forward to seeing how much it improves when it’s released.

Thoughts

PostCSS looks very promising. I like its modularity so I can plug in what I need, and create any custom things I need it to do. It’s also exciting to try out what’s coming with the future CSS syntax.

With that said, I’m not saying we should all ditch Sass. Sass is a fantastic tool, and I’d still recommend it for anyone getting started with pre-processors. If you’re feeling adventurous however, I’d definitely recommend giving PostCSS a try.

It’d be great to hear anyone’s thoughts and experience using PostCSS in a live project. Get in touch!

Want to know a little more about us?

We are a friendly bunch and love talking all things WordPress. Whether you’re using one of our open source projects, want to discuss a blog article or hire us for your next project, we would love to hear from you.

Contact us