PostCSS

Wee has departed from Less, a traditional pre-processor, to PostCSS. This article will clarify what it is, why and how it is being used in Wee 4, and why it is worth knowing about.

At it's core, PostCSS is simply a tool for transforming CSS with JavaScript. If you are familiar with Less or Sass, you are familiar with the idea of pre-processors and how they define custom syntax to traditional CSS in order to allow for a more powerful set of tools and features for the developer that results in de-duplicated and elegant stylesheets. Using Sass or Less requires a build process for your front-end development that will transform the Sass/Less that you write into raw CSS. PostCSS is a peer in this respect. However, PostCSS is not just a pre-processor. Instead, it is a platform that provides the opportunity to custom tailor the way you compose and distribute CSS.

The diagram above shows the anatomy of PostCSS. At it's core, PostCSS is composed of a parser and a stringifier. The parser takes some text (CSS) and parses it out into an Abstract Syntax Tree (AST). Inversely, the stringifier takes the AST and turns it back into a string of text. What makes PostCSS special and what largely sets it apart from traditional pre-processors is the idea of plugins. PostCSS provides an API for registering plugins that allow for manipulating and working with the AST that was created by the parser. These plugins can do anything. There are plugins for writing Sass style variables, for auto-generating vendor prefixes to CSS declarations, for linting stylesheets, for minifying output, and the list goes on and on. With this ability to arrange plugins together gives near unlimited potential to what can be done for your composition of stylesheets. This is the potential we saw and why we took the leap to use PostCSS exclusively in Wee 4.

We have hopefully explained why we chose to use PostCSS, now let's look at how. Here are the plugins that are currently being used in the order they are being used in Wee:

PostCSS Variables #

This plugin enables the use of Sass-like variables. What makes this plugin different is that it provides a way of defining, managing, and referencing variables as a JavaScript object. All of Wee's base variables are defined and can be overwritten this way. Here is an example variables file from a Wee 4 project source/styles/variables.js:

const register = require('postcss-variables/lib/register');
let v = require('wee-core/styles/variables');
let { colors } = require('wee-core/styles/variables');

// Override the primary color variable used in base styling
colors.primary = '#111';

// Set a new variable on variables object directly
v.newVar = 'hello world';

// Register variables object
module.exports = register(v);

These variables are retrieved in stylesheets like this:

.selector {
    $otherColor: 'blue'; // Variables can be set in stylesheet directly

    color: $colors.primary;
    &::after {
      color: $otherColor;
      content: $newVar;
    }
}

PostCSS JS Mixins #

Traditionally, there are four types within the AST that PostCSS generates and that plugins manipulate: rules, at-rules, declarations, and comments. The use of mixins is important to our workflow in Less, and so we decided to add a fifth node type: mixins. When this plugin is installed, you can call mixins like you would a JavaScript function:

.selector {
    someMixin(#000, 10px); // Arguments are comma separated
    someMixin(font-size: 10px); // Mixins can be written to support key value arguments
}

Mixins are defined and registered in pure JavaScript. There is a library of base mixins that can be listed out with Wee CLI: wee mixins, or inspected individually: wee mixins mixinName. Here is an example mixin file from a Wee 4 project source/styles/mixins.js:

const decl = require('postcss-js-mixins/lib/declaration');
const { isEmpty } = require('postcss-js-mixins/lib/helpers');

module.exports = (vars = {}) => {
    return {
        /**
         * Creates a line-height declaration with a default of 1em
         */
        lineHeight(value) => {
            if (isEmpty(value)) {
                value = '1em';
            }

            return decl('line-height', value);
        };
    };
};

Using the above mixin would look like this:

Before

.selector {
    lineHeight();
}

After

.selector {
    line-height: 1em;
}

You will notice there are a couple helper methods being used here. We are using a method that helps determine if value is passed into the mixin or not. If value is not provided, then we set a default value to 1em. There are many useful helper methods like isEmpty. The last thing we do is execute decl. This method generates an object that represents a CSS declaration. Every mixin can return one of three things: a declaration, a rule, or an array of declarations/rules. These directly correlate to CSS rules and declarations.

PostCSS Nested Selectors #

This plugin allows for referencing any parent selector. Here is an example:

.level-1 {
    .level-2 {
        .level-3 {
            & { } // .level-1 .level-2 .level-3
            ^& { } // .level-1 .level-2
            ^^& { } // .level-1
        }
    }
}

The ampersand & references the immediate parent selector, and carets ^ walk up one additional level per instance.

PostCSS Nested #

This plugin unwraps nested rules in the same fashion as Sass and Less. Here is an example:

Before

.phone {
    &_title {
        width: 500px;
        @media (max-width: 500px) {
            width: auto;
        }
        body.is_dark & {
            color: white;
        }
    }
    img {
        display: block;
    }
}

After

.phone_title {
    width: 500px;
}
@media (max-width: 500px) {
    .phone_title {
        width: auto;
    }
}
body.is_dark .phone_title {
    color: white;
}
.phone img {
    display: block;
}

PostCSS Variable Media #

This plugin allows for defining custom at-rules to represent media queries. It is crucial to the way that components are written in Wee 4 as it allows us to reference the same media queries across multiple component stylesheets. These references are then merged together in the final output CSS. In Wee 4, media queries are registered in the main configuration file wee.json. These are the default registered breakpoints:

"breakpoints": {
    "mobileLandscape": 480,
    "tablet": 768,
    "desktop": 1024,
    "desktop2": 1280,
    "desktop3": 1440
}

The key of the object is the name of the breakpoint, and the number is assumed to be a min-width media query value in pixels. As an example of how these will be used, let's assume we have two different components. The styling below is assumed to be in two different component index.pcss files:

Before

// Component 1
@tablet {
    .component1 {
        background: #fff;
    }
}

// Component 2
@tablet {
    .component2 {
        font-size: 1.4rem; 
    }
}

After

@media (min-width: 768px) {
    .component1 {
        background: #fff;
    }
    .component2 {
        font-size: 1.4rem;
    }
}

In this way, we can separate and modularize our styling for an application (see guide for Components) while also being efficient with the number of media queries we are generating in our final CSS output.

Autoprefixer #

This is the defacto PostCSS plugin. If you don't already use it, you should start. In short, it adds vendor prefixes for CSS declarations automatically so you don't have to. Here is an example:

Before

a {
    display: flex;
}

After

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

CSS Nano #

This plugin minifies our final CSS output, hence it comes at the end of our plugin chain.

Conclusion #

This is just the starting point for our PostCSS plugins. The road map includes things like adding custom functions and a wee variables command that will print out all the registered variables and their values.

One sneaky problem with using a culmination of different PostCSS plugins, some custom, to define how you write stylesheets is that syntaxes are not always supported by text editors. Below are a few options:

  • PHPStorm/WebStorm - Jetbrains has a PostCSS plugin that can be installed. This supports most of our syntax, but variables can become precarious. To clean up annoying underlining of variables, the best thing is to switch the highlighting level setting to None. The easiest way to do this is by clicking the Hector icon on the bottom right status bar. You can also choose Code from the main menu and select Configure Current File Analysis. After you disable highlighting, restart the editor.
  • Sublime Text - Sublime's PostCSS package supports our syntax.
  • Atom - The PostCSS package covers all but our mixin syntax. I have forked and modified this package. It can be found here.