Ever since PostCSS came out, I've exclusively used it for Autoprefixer. I didn't really dig into the power of PostCSS
and how it could actually be a comparable solution to SASS.
This article will go into depth about my skepticisms, and how I was able to find solutions using PostCSS
.
The Push
What originally considered me to switch was my interest in Tailwind CSS. I love the ideas of utility libraries
, but found it difficult to sell to other developers. However, we've been looking for ways to optimize development time, so Tailwind
felt like a good choice. It's built with PostCSS
in mind, so I at least wanted to give it a try.
Previous Approach
My previous approach was a mix of SASS, BEM, ITCSS, & SMACSS. They solved the following issues:
-
SASS
-variables, loops, imports, nested selectors, etc
-
BEM
- reducespecificity
and allow for easier additions / modifications to code -
ITCSS
- (same asBEM
), but also allows for aninverted triangle
hierarchy forspecificity
gets more and more specific. -
SMACSS
- used mostly formodifier classes
, like.js-is-active, .has-posts, etc
.
This worked really well for me. I saw the power of utility libraries
, but figured I would let them evolve and try them out later.
The Switch
I won't bore you with the details, but I went through many iterations like:
-
PostCSS
only, noplugins
besidesTailwind
PostCSS + SASS
-
PostCSS
only, withTailwind
and other complementaryplugins
The core issue with the PostCSS + SASS
approach was the inevitable duplication. For example, these situations can't work together:
/**
* cannot set $font-size-px-base in PostCSS
* since SASS gets run first, so would have
* to duplicate code
*/
@function rem($pixels, $context: $font-size-px-base) {
@if (unitless($pixels)) {
$pixels: $pixels * 1px;
}
@if (unitless($context)) {
$context: $context * 1px;
}
@return $pixels / $context * 1rem;
}
/**
* can't use Tailwind variables
* in the SASS function `scale` since
* SASS runs before PostCSS
*/
.selector {
font-size: scale(font-size, theme('fontSize.lg'), theme('fontSize.xl'));
}
/**
* Interpolation was also a nightmare
* I couldn't figure out how to make
* something like this work
* even with PostCSS Simple Vars
*/
.selector {
$path: theme('path.theme');
background-image: url($(path)/test.png);
}
Final Solution
The final solution involved PostCSS
only, with Tailwind
and other complementary plugins
.
When I thought about it, I was really using SASS
for:
imports
mixins
functions
variables
-
nested selectors
- rarely, but nice to have, especially for pseudo selectors -
loops
- very rarely, but nice to have
PostCSS
has solutions for all of these once I realized the power of their plugin system. Furthermore, I found out some plugins, like for functions
and mixins
, allow you to inject Javascript
functions into PostCSS
. I was sold.
Imports
PostCSS Imports gives the exact same functionality as SASS Imports
, so it was an easy switch.
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'components/btn';
@import 'tailwindcss/utilities';
Functions
PostCSS Functions allows you to write functions IN JAVASCRIPT
and inject them into PostCSS
. IN JAVASCRIPT. I LOVE JAVASCRIPT
.
// functions.js
let settings = require('./settings');
let rem = (pixels, context = settings.defaults.fontSizePxBase) => {
pixels = parseFloat(pixels);
let result = pixels / context;
return `${result}rem`;
};
module.exports = {
rem,
};
Mixins
PostCSS Mixins is the same deal as PostCSS Functions
.
// mixins.js
let placeholder = (mixin, immediateSelector = true) => {
let vendors = [
'::-webkit-input-placeholder',
':-moz-placeholder',
'::-moz-placeholder',
':-ms-input-placeholder',
];
return vendors.reduce((prev, vendor) => {
let selector = immediateSelector ? `&${vendor}` : vendor;
prev[selector] = {
'@mixin-content': {},
};
return prev;
}, {});
};
module.exports = {
placeholder,
};
Other Notable Plugins
-
PostCSS Nested - offers
SASS nesting
-
PostCSS Simple Vars - offers simple
variables
to be used inselectors, etc
-
PostCSS PurgeCSS - removes unnecessary
CSS
based on file globs you pass in -
CSSNano - minify
CSS
-
Autoprefixer - remove unnecessary
prefixes
based on your browser support
Example PostCSS Config File
// postcss.config.js
let functions = require('./functions');
let mixins = require('./mixins');
module.exports = function(context) {
/**
* context comes from what you pass
* into `gulp-postcss`, omitting detailing here
* since `gulp` is an implementation detail
*/
let { options } = context;
let plugins = [
require('postcss-import')({
path: [options.paths.components, options.paths.styles, 'node_modules'],
}),
require('tailwindcss')('./tailwind.config.js'),
require('postcss-functions')({
functions,
}),
require('postcss-mixins')({
mixins,
}),
require('postcss-nested'),
require('postcss-simple-vars'),
require('autoprefixer'),
];
return {
plugins,
};
};
I hope this at least got you interested in PostCSS
and what it's powerful plugin
system can provide for your next project.
Top comments (3)
Nice write-up! Check out
postcss-preset-env
, which does the same thing as a lot of your plugins, in just one: preset-env.cssdb.org/Thanks! I'll check it out soon.
Fascinating explanation.
I also make article about PostCSS.
epsi-rns.gitlab.io/frontend/2019/1...