Is PostCSS a Game Changer?

Short answer – yes. It allows you to write future CSS syntax today. That's about as future proof as it gets. And it doesn't stop there! You can solve real business problems by fine-tuning CSS into exactly what you need and nothing you don't need.

As the PostCSS name implies, it takes CSS in and spits modified CSS out. Although the name accurately describes the original project goals, it's a bit misleading, because some of the plugins written for it have diverted from CSS syntax. More on that later.

At the end of the day, PostCSS is similar to preprocessors like Sass and Less in that it transpiles code from one form into another. The main difference is that PostCSS, by itself, doesn't extend or transform CSS in any way. Instead, it relies on its many plugins to handle any transformations.

PostCSS has a gulp plugin, a grunt plugin and Connect/Express middleware; courtesy, yours truly.

How does PostCSS work?

You can think of PostCSS like an enabler. It enables developers to create plugins that would transform CSS, but it doesn't do any of the dirty work itself. It parses CSS into AST (an abstract syntax tree), sends this AST through any number of plugins (chosen by you), and then spits out the result. Plugin developers are given an API that makes navigating and transforming CSS pretty painless. There's even a boilerplate to get you started.

It's all about the plugins.

PostCSS is gaining some serious traction. As such, it has an ever-growing, pleasantly plump plethora of plugins. CodePen even started supporting some of them recently.

Ultimately, what PostCSS means to you comes down to the plugins that align with your philosophies. You could spend hours reading up on all the plugins out there and even more hours trying to figure out how to configure them the way you want, how they work together and how that affects your day-to-day development.

If you feel something is missing, you might decide to write a plugin yourself. Maybe it's something you've always wanted to do, but couldn't before now. That's why I created postcss-nested-props and postcss-font-pack.

Plugins are only supposed to do one thing, so you shouldn't expect any one plugin to solve all your needs. Together, however, plugins can change your world. I'll try to cover the most significant ones and leave the rest to you.

CSSnext plugin for PostCSS

Probably the most popular plugin right now, and rightly so, is cssnext, which allows you to use tomorrow's CSS syntax, today.

You can literally write future-proof CSS and forget old preprocessor specific syntax.

img

Cssnext is a plugin pack or a collection of plugins that, together, enable this future-proof syntax. You could just as well install each of the plugins yourself, but it's definitely more simple to install cssnext and be done with it.

Here's a pro tip – stash your global variables in a vars.json file and reference it like this:

require('cssnext')({  
  features: {
    customProperties: {
      variables: require('./vars.json')
    }
  }
})

This prevents you from opening up your build script every time you want to play with your variables. Since variables are nothing more than data, it makes more sense to store them in a data file format, rather than CSS or JavaScript. This way, all your variables are in a single, easy-to-scan file.

I find it handy to have a styles/config folder where I can put things like this:

styles  
├── config
│   ├── fonts.json
│   ├── postcss-processors.js
│   └── vars.json
...

It's important to separate configuration from actual code. You might decide to use these configuration files for more than one purpose. In my case, I use the postcss-processors.js file for my gulp task, but I also use the exact same file for my middleware. I couldn't do this if my configuration were embedded in one or the other.

// postcss-processors.js

export default createPostcssProcessors(options) {  
  options = options || {};
  return [
    require('postcss-mixins'),
    require('postcss-nested-props'),
    require('postcss-nested'),
    require('cssnext')({ /* options */ }),
    // ...
  ];
}

Autoprefixer

Autoprefixer adds vendor prefixes so you don't have to fret about it.

Just write normal CSS according to the latest W3C specs and Autoprefixer will produce the code for old browsers.

a {  
    display: flex;
}

compiles to:

a {  
    display: -webkit-box;
    display: -webkit-flex;
    display: -ms-flexbox;
    display: flex
}

Autoprefixer is probably the most important plugin included in cssnext. If you want to stay in the past and you don't want to install cssnext, you can just install Autoprefixer by itself, but that would be lonely, wouldn't it? However you decide to install it, install it now! If you don't, you will die in the same ignominious way you would have if you didn't forward that email to at least 10 people within 24 hours. Tick… tick… tick…

Where were we? Oh, right – Autoprefixer! Actually, we're done with Autoprefixer. You can read the docs for the details.

PreCSS

For all you Sass lovers out there, PreCSS is the plugin pack for you. It allows you to write Sass-like syntax in your CSS files. Technically, this is a diversion from or an extension to CSS syntax, so there is some discussion about introducing a .pcss file extension. This could complicate things in your editor with respect to code coloring, so be warned. Again, you don't have to use the whole pack if you don't want. Personally, I don't use this pack; however, I do use the following:

Postcss-nested-props

Unwraps nested properties like Sass. This is great at keeping things tight and not repeating yourself.

.foo {
  font: 1rem/1.2 Roboto, Arial, sans-serif {
    stretch: condensed;
  }
  margin: {
    left: 5px;
    right: 10px;
  }
}

compiles to:

.foo {
  font: 1rem/1.2 Roboto, Arial, sans-serif;
  font-stretch: condensed;
  margin-left: 5px;
  margin-right: 10px;
}

This is a huge win for me, because I really hate repeating myself. Also, I've noticed some developers aren't entirely great at remembering the clockwise rotation of the margin shorthand property. This gives you the best of both worlds. You don't have to repeat yourself and it's very readable at the same time. This goes for other shorthand properties too.

Postcss-nested

Unwraps nested rules like Sass:

.block {
  &__element {
    &--modifier {
      color: tomato;
    }
  }
}

compiles to:

.block__element--modifier {
  color: tomato;
}

In my example, I'm building one selector with the & symbol. Without this symbol, you could nest classes and element selectors, but I wouldn't recommend it, because it creates a CSS specificity hell.

This is really convenient for the BEM naming convention, for which I also use postcss-bem-linter.

PostCSS Mixins

Not unlike Sass mixins, you can write simple templates to prevent repeating yourself with postcss-mixins. You can write mixins directly in your CSS.

@define-mixin icon $network, $color: blue {
    .icon.is-$(network) {
        color: $color;
        @mixin-content;
    }
    .icon.is-$(network):hover {
        color: white;
        background: $color;
    }
}

@mixin icon twitter {
    background: url(twt.png);
}
@mixin icon youtube, red {
    background: url(youtube.png);
}

Or in JavaScript:

// triangle.js
import { assign } from 'lodash';

export default function triangle(mixin, width, height, color, direction) {  
  let halfWidth = `calc(${width} / 2)`;
  let halfHeight = `calc(${height} / 2)`;
  return {
    'box-sizing': 'border-box',
    width: width,
    height: height,
    border: assign(
      {
        style: 'solid',
        color: 'transparent'
      },
      {
        up: {
          'bottom-color': color,
          width: `0 ${halfWidth} ${height}`
        },
        right: {
          'left-color': color,
          width: `${halfHeight} 0 ${halfHeight} ${width}`
        },
        down: {
          'top-color': color,
          width: `${height} ${halfWidth} 0`
        },
        left: {
          'right-color': color,
          width: `${halfHeight} ${width} ${halfHeight} 0`
        }
      }[direction]
    )
  };
}

Writing them in JavaScript has the added benefit of allowing you to write tests to go along with them. This gives me warm fuzzies, verifying that my mixin does what I claim it does. Given certain input, I expect a certain output. Here's a Jasmine test for the triangle mixin above.

// triangle.spec.js
import { isEqual } from 'lodash';  
import triangle from './triangle';

describe('triangle mixin', function() {

    it('generates a triangle pointed up', () => {
        expect(
            triangle({}, '5px', '10px', 'red', 'up')
        ).toDeepEqual({
            'box-sizing': 'border-box',
            width: '5px',
            height: '10px',
            border: {
                style: 'solid',
                color: 'transparent',
                'bottom-color': 'red',
                width: '0 calc(5px / 2) 10px'
            }
        });
    });

    // more specs...
});

I have my mixins in their own mixins folder, where each mixin has its own JavaScript file and a spec to go with it.

styles  
├── mixins
│   ├── triangle.js
│   ├── triangle.spec.js
│   ...
...

You can do the same thing with the mixinsFiles option; courtesy, yours truly.

require('postcss-mixins')({  
    mixinsFiles: path.join(__dirname, 'mixins', '!(*.spec.js)')
})

This keeps things properly separated, scalable and easy to maintain. If all the mixins were stuffed into one file, it becomes harder to find what you're looking for.

Apparently, I've been a bit overzealous with my use of mixins, so feel free to keep it simple.

PostCSS Sprites

If you don't have a solution for sprites in your CSS then you aren't ready for prime time. This is because browsers are limited to up to 8 simultaneous persistent connections per server/proxy. This means 8 images at a time, max. Limiting the number of requests you send for images is a well-known way to improve site performance and we do this by combining a bunch of images together into a single sprite sheet. Postcss-sprites will generate sprites from your CSS and update the image references for you. That's pretty awesome!

Look – spriting (the act of creating sprites) is the main reason I hung onto Compass for so long. I've been waiting for an alternative to Compass for years and here it is, folks. No more Ruby dependencies!

Fonts

Some fonts are expensive and relatively heavy to download. Can you imagine downloading all of Roboto's 18 font files if you only need to use 1 or 2 of them?

Postcss-font-pack ensures you stay within your boundaries and only use the font styles that were installed on your site. If you didn't do this, you could inadvertently show fallback fonts like Arial instead of the pretty font you thought you were showing.

All you need to remember is the keyword you specified in the configuration options, like roboto:

.foo {
  font-family: roboto;
}

compiles to:

.foo {
  font-family: Roboto, Arial, sans-serif;
}

This isn't just for custom-installed fonts. You can also configure Arial to automatically add the sans-serif fallback so you don't have to type it out all the time.

Postcss-font-pack enforces these rules by throwing an error if you try to use a font that's not specified in your configuration options.

Pro tip – use requireSize: true to ensure nobody tries to specify a font-size without also specifying the font-family. I like to stash my supported fonts in styles/config/fonts.json and reference it like this:

require('postcss-font-pack')({  
  requireSize: true,
  packs: require('./fonts.json')
})

See the docs for more details.

Conclusion

The plugins are the biggest selling point here. If you don't like where PostCSS is now, write a plugin until you do like it. At least you don't have to wait for some feature you find important to be added to a single code base. You can write a plugin now and start using it in a flash!

Your philosophy doesn't have to align 100% with the project owner, because you have the utmost control over which plugins you decide to install. It's like a choose your own adventure game! CSS can be everything you want it to be and nothing you don't want it to be.

Hey – I'm a long-time Sass enthusiast. Sass has been good to me for a few years now, so it'd take a lot to make me switch away from it, but that's exactly what PostCSS did. I'm completely sold! Are you?

img