PostCSS

a tool for transforming styles with JS plugins

What's PostCSS

Write css however you like

 

PostCSS converts the css file to AST (think of it like JSON)

 

Plugins of your choosing parse it and make changes

 

Output is stringified and exported

 

AST Tree

#monospaceLabs {
    font-size:large;
}
...
      "type": "rule",
      "nodes": [
        {
          "raws": {
            "before": "\n    ",
            "between": ":"
          },
          "type": "decl",
          "source": {
            "start": {
              "line": 2,
              "column": 5
            },
            "input": {
              "css": "#monospaceLabs {\n    font-size:large;\n}",
              "id": "<input css 51>"
            },
            "end": {
              "line": 2,
              "column": 20
            }
          },
          "prop": "font-size",
          "value": "large"
        }
      ],
      "source": {
        "start": {
          "line": 1,
          "column": 1
        },
        "input": {
          "css": "#monospaceLabs {\n    font-size:large;\n}",
          "id": "<input css 51>"
        },
        "end": {
          "line": 3,
          "column": 1
        }
      },
      "selector": "#monospaceLabs"
    }
  ],
...

Yes, but why

Modular Approach vs Monolithic.

Selected plugins vs thousands lines code (LESS: 105 files, 9 800 LOC​)

Use relevant technologies like Javascript.

How useful is LESS syntax, now that you use SASS?

It's fast

& hip

> @ test /Users/dnlytras/Documents/Projects/Personal/benchmarks/benchmark
> gulp "preprocessors"

[00:38:01] Using gulpfile ~/Documents/Projects/Personal/benchmarks/benchmark/gulpfile.js
[00:38:01] Starting 'bootstrap'...
[00:38:01] Finished 'bootstrap' after 619 μs
[00:38:01] Starting 'preprocessors'...
[00:38:02] Running suite Bootstrap [/Users/dnlytras/Documents/Projects/Personal/benchmarks/benchmark/preprocessors.js]...
[00:38:08]    libsass x 9.00 ops/sec ±6.50% (27 runs sampled)
[00:38:14]    Rework x 14.64 ops/sec ±4.28% (73 runs sampled)
[00:38:20]    PostCSS x 22.83 ops/sec ±6.40% (59 runs sampled)
[00:38:27]    Stylecow x 3.28 ops/sec ±4.89% (20 runs sampled)
[00:38:33]    Stylus x 5.34 ops/sec ±20.31% (33 runs sampled)
[00:38:40]    Less x 7.60 ops/sec ±8.30% (44 runs sampled)
[00:38:50]    Ruby Sass x 0.76 ops/sec ±9.69% (8 runs sampled)
[00:38:50] Fastest test is PostCSS at 1.56x faster than Rework

PostCSS:   44 ms
Rework:    68 ms   (1.6 times slower)
libsass:   111 ms  (2.5 times slower)
Less:      132 ms  (3.0 times slower)
Stylus:    187 ms  (4.3 times slower)
Stylecow:  305 ms  (7.0 times slower)
Ruby Sass: 1308 ms (29.9 times slower)

[00:38:50] Finished 'preprocessors' after 49 s

Couple plugins

Autoprefixer

a {
    display: flex;
}


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

Manages autoprefixes in CSS, adding only necessary ones consulting caniuse.com database.

Possible configuration options :

  • last 2 versions
  • > 5%
  • >= 5% in GR
  • IE 8
  • iOS 7
> 1%
Last 2 versions
IE 10

more @ Browserslist

PostCSS CSSNext

:root {
  --main-bg-color: black;
}
 
element {
  background-color: var(--main-bg-color);
}

CSS has specification for variables, with extremely low browser support

CSSNext, allows you to use latest specifications

  • Custom Media Queries
  • Custom Selectors
  • Nesting
  • :matches & :not
  • Various colour functions
  • more, obviously
@custom-selector :--button button, .button;
@custom-selector :--enter :hover, :focus;
@custom-selector :--headers h1,h2,h3,h4,h5,h6;

:--button {
  
}
:--button:--enter {

}

.blur {
    filter: blur(4px);
}

Think of it like Babel, except it's not.

PostCSS CSSNext

:root {
  --danger-theme: {
    color: white;
    background-color: red;
  };
}

.danger {
  @apply --danger-theme;
}
@custom-media --small-viewport (max-width: 30em);
@custom-media --only-medium-screen (width >= 500px) and (width <= 1200px);

@media (--small-viewport) {
    color: color(red alpha(-10%));
}

@media (--only-medium-screen) {
    div:not(:first-of-type, .call-to-action, .social-bar ) {
        all: initial;
        font-variant-caps: small-caps;
        color: hwb(90, 0%, 0%, 0.5);
  }
}
a {
  & span {
    color: black;
  }
  
  @media (min-width: 30em) {
    color: yellow;
  }
}

PreCSS

SASS-like syntax.

Easier for casuals to transition.

 

Includes:

$blue: #056ef0;
$column: 200px;

.menu {
	width: calc(4 * $column);
}

.menu_link {
	background: $blue;
	width: $column;
}

@define-mixin icon $name {
	padding-left: 16px;
	background-url: url(/icons/$(name).png);

	&::after {
		content: "whatever";
	}
}

.search {
	@mixin icon search;
}
  • Variables
  • Conditionals
  • Import
  • Loops
  • Mixins
  • Nesting
  • more

Basically a collections of smaller SASS like plugins.

E.g. you can just use PostCSS-mixins for, well, mixins.

Font-Magician

Author's favourite!

Text

Declare the font family, and move on. Font magician will fetch the font from Google fonts.

 

Otherwise, pass a relative path for local copies.

 

Furthermore choose to use visitors local copy of a font.

Font-Magician + REACT = <3

Exploit hot-loading in order to test multiple fonts.

Rucksack

Collection of very handy plugins.

html {
  font-size: responsive 12px 21px;
  font-range: 420px 1280px;
}

// Output

html {
  font-size: calc(12px + 9 * ( (100vw - 420px) / 860));
}

@media screen and (max-width: 420px) {
  html {
    font-size: 12px;
  }
}

@media screen and (min-width: 1280px) {
  html {
    font-size: 21px;
  }
}
// shorthand positioning
.foo {
  position: absolute 0;
}

.bar {
  position: relative 20% auto;
}

// quantity queries
li:at-most(4) {
  color: blue;
}

li:at-least(5) {
  color: red;
}

* Responsive type is extremely useful, even standalone

LostGrid

Powerful grid system with support for :

div {
  lost-column: 1/3;
}

//output

div {
  width: calc(99.9% * 1/3 - (30px - 30px * 1/3));
}
div:nth-child(1n) {
  float: left;
  margin-right: 30px;
  clear: none;
}
div:last-child {
  margin-right: 0;
}
div:nth-child(3n) {
  margin-right: 0;
  float: right;
}
div:nth-child(3n + 1) {
  clear: both;
}
  • Vertical Grids
  • Waffle Grids
  • Flexbox Grids
  • Masonry Layout
  • think thats all
@lost gutter 60px;
@lost flexbox flex;
@lost cycle none;
@lost clearing left;
@lost rounder 100;
@lost --beta-direction rtl;

.foo {
  ...
}
section {
  height: 100%;
}

div {
  lost-waffle: 1/3 float-right;
}

PostCSS-svg

/* Input */
.comment { background: url(images/sprite/ico-comment.png) no-repeat 0 0; }
.bubble { background: url(images/sprite/ico-bubble.png) no-repeat 0 0; }

/* ---------------- */

/* Output */
.comment { background-image: url(images/sprite.png); background-position: 0 0; }
.bubble { background-image: url(images/sprite.png); background-position: 0 -50px; }

PostCSS-sprites

body {
  background-image: svg("img/arrow-up.svg", "[fill]: pink");
}
.foo::before {
  font-awesome: camera;
}

PostCSS-font-awesome

Even more

Postcss-colorblind, Color Guard, Doiuse, Immutable-css, rtlcss, Currency, Reporter, Click, Theme, Tipsy, Nope ...

PostCSS-autocorrect

Creating PostCSS plugins is so easy, even I could do it.

PostCSS-autocorrect

  • Corrects properties typos

  • Corrects values typos

  • Accepts user's list of common mistakes

  • Toggles default correction list off

  • Issues warnings in console

'use strict';
var postcss = require('postcss');
var data = require('./lib/data');

module.exports = postcss.plugin('postcss-autocorrect', function (opts) {

    opts = opts || {};
    let mistakes = [];
    let corrections = [];

    let providedList = opts.providedList || [];

    // If useDefaultList is true or undefined (by default true)
    let shouldInitiateDefaults = !(opts.useDefaultList === false);

    // Get the default values
    if (shouldInitiateDefaults) {
        mistakes = data.mistakes;
        corrections = data.corrections;
    }

    // if user has provided input,include them
    if (!!providedList && providedList.length > 0) {
        providedList.forEach(function (obj) {

            let key = Object.keys(obj)[0];

            obj[key].forEach(function (value) {
                mistakes.push(value);
                corrections.push(key);

            });
        });
    }
 
..continued
    function checkParameters(check, line) {
        let isMistake = mistakes.indexOf(check);

        if (isMistake !== -1) {
            let fix = corrections[isMistake];
            console.warn('Error in line ' + line + '.');
            console.warn('Got "' + check + '" instead of "' + fix + '".');
            return fix;
        }

        return check;
    }

    return function (css) {

        css.walkDecls(function (decl) {

            // check properties
            decl.prop = checkParameters(decl.prop, decl.source.start.line);

            // check values
            const values = decl.value.split(' ');
            const line = decl.source.start.line;

            const corrected = values.map(v => {
                return checkParameters(v, line);
            });

            decl.value = corrected.join(' ');

        });

    };

});

PostCSS-autocorrect (Test)

function run(input, output, opts) {
    return postcss([plugin(opts)]).process(input).then(result => {
        expect(result.css).toEqual(output);
        expect(result.warnings().length).toBe(0);
    });
}

it('prop mistake', () => {
    return run('a{heigth:10px}', 'a{height:10px}', {});
});

it('prop & value mistake', () => {
    return run('a{colour:blakc}', 'a{color:black}', {});
});

it('with import', () => {
    return run('a{dislay:flx;}', 'a{display:flex;}', {
        useDefaultList: true,
        providedList: [
            {
                flex: ['flx']
            }
        ]
    });
});

it('should not correct', () => {
    return run('a{colour:blakc;}', 'a{colour:blakc;}', {
        useDefaultList: false
    });
});

it('many values', () => {
    return run('a{border:10px solid blakc}', 'a{border:10px solid black}', {});
});

Writing a plugin

Writing a plugin

var postcss = require('postcss');

module.exports = postcss.plugin('PLUGIN_NAME', function (opts) {
    opts = opts || {};

    // Work with options here

    return function (root, result) {

        // Transform CSS AST here

    };
});

Writing a plugin

more @ PostCSS API

  • WalkAtRules
    • @media print
    • @doSomething option1 option2
  • WalkRules
  • WalkComments
  • WalkDecls
  • append
  • clone //after or before
  • insert // after or before
  • move // after or before
  • remove // all, child, values, with
  • more ..
#monospaceLabs{
  background-color : red;
  @add before circle blue 1px 15px 0,-2px,0,0
}

This really doesn't make any sense.
Let's build a mini plugin

export default postcss.plugin('edw-o-man-vazei-ena-kyklo', (options = {}) => {
    return root => {
        root.walkRules(rules => {
          
       	       const atrule = rules.nodes.filter(node => {
                    return (node.type === 'atrule' && node.name === 'add');
    	       });
                        
               if (atrule.length === 1){
                
                    const params = atrule[0].params.split(' ');
                    
                    const position = params[0];
                    const radius = (params[1] === 'circle' ? '100%' : '0');
                    const color = params[2];
                    const side = params[3];
                    const dimensions = params[4];
                    const place = params[5].split(',')
                    
                    rules.removeChild(atrule[0]);
                    
                    const decl1 = postcss.decl({ prop: 'content', value: '" "' });
                    const decl2 = postcss.decl({ prop: 'border-radius', value: radius });
                    const decl3 = postcss.decl({ prop: 'width', value: dimensions });
                    const decl4 = postcss.decl({ prop: 'height', value: dimensions});
                    const decl5 = postcss.decl({ prop: 'border', value: side + ' solid ' + color });
    	            const decl6 = postcss.decl({ prop: 'display', value: 'block'});
                    const decl7 = postcss.decl({ prop: 'position', value: 'absolute'})
                    const decl8 = postcss.decl({ prop: 'top', value: place[0]})
                    const decl9 = postcss.decl({ prop: 'left', value: place[1]})
                    const decl10 = postcss.decl({ prop: 'bottom', value: place[2]})
                    const decl11 = postcss.decl({ prop: 'right', value: place[3]})
    
                    const new_rule = postcss.rule({ selector: rules.selector + ':' + position });
            	    new_rule.append(decl1, decl2, decl3,decl4,decl5,decl6, decl7, decl8, decl9, decl10, decl11);
                    
                    root.append(new_rule);
             
              }   
        });
    };
});

Writing a plugin 

#monospaceLabs{
  background-color : red;
}

#monospaceLabs:before{
  content : " ";
  border-radius : 100%;
  width : 15px;
  height : 15px;
  border : 1px solid blue;
  display : block;
  position : absolute;
  top : 0;
  left : -2px;
  bottom : 0;
  right : 0;
}

Homework

PostCSS

By dnlytras

PostCSS

PostCSS Overview

  • 785