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
AST & PostCSS playgrounds
PostCSS
By dnlytras
PostCSS
PostCSS Overview
- 785