a tool for transforming styles with JS plugins
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
#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"
}
],
...
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
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 :
> 1%
Last 2 versions
IE 10
more @ Browserslist
: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-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.
: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;
}
}
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;
}
Basically a collections of smaller SASS like plugins.
E.g. you can just use PostCSS-mixins for, well, mixins.
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.
Exploit hot-loading in order to test multiple fonts.
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
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;
}
@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;
}
/* 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; }
body {
background-image: svg("img/arrow-up.svg", "[fill]: pink");
}
.foo::before {
font-awesome: camera;
}
Postcss-colorblind, Color Guard, Doiuse, Immutable-css, rtlcss, Currency, Reporter, Click, Theme, Tipsy, Nope ...
Creating PostCSS plugins is so easy, even I could do it.
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(' ');
});
};
});
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}', {});
});
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
};
});
more @ PostCSS API
#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);
}
});
};
});
#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;
}
AST & PostCSS playgrounds