How to Build Your Own CSS Preprocessor With PostCSS

Use a CSS preprocessor for a while and life soon becomes imaginable without it. Many developers have adopted LESS, Stylus or — the current favorite — Sass. Ideally, you should also be post-processing your CSS using Autoprefixer to add vendor prefixes when required without resorting to mixins or a frameworks such as Compass.

Autoprefixer is a plugin for PostCSS; a tool which translates CSS into an object model which can be used to manipulate styles. There are many more plugins available which implement preprocessor-like features such as variables, mixins and nesting.

Ben Frain’s recent article, Breaking up with Sass: it’s not you, it’s me, raises an interesting proposition. Do you really need Sass, LESS or Stylus given you can craft the CSS preprocessor you want using PostCSS?

--ADVERTISEMENT--

A basic Sass-like parser can be created within your build process. For this example, I’ll use a Gulp.js task file but the same concepts apply to Grunt or any other build system. First, we install PostCSS and the plugins we need using npm, e.g.

npm install gulp-postcss postcss-mixins postcss-simple-vars postcss-nested autoprefixer-core --save-dev

then create an array of CSS processing functions in gulpfile.js, e.g.

var
	gulp = require('gulp'),
	postcss = require('gulp-postcss'),
	processors = [
		require('postcss-mixins'),
		require('postcss-simple-vars'),
		require('postcss-nested'),
		require('autoprefixer-core')({ browsers: ['last 2 versions', '> 2%'] })
	];

and write a CSS processing task, e.g.

// compile CSS
gulp.task('css', function() {
  return gulp.src('source/css/styles.css')
    .pipe(postcss(processors))
    .pipe(gulp.dest('dest/styles/'));
});

You can also create your own processing functions using the PostCSS API. For example, we could apply a sans-serif fallback for all font-family declarations:

processors = [
		require('postcss-mixins'),
		require('postcss-simple-vars'),
		require('postcss-nested'),
		function(css) {
			// sans-serif fallback
			css.eachDecl('font-family', function(decl) {
				decl.value = decl.value + ', sans-serif';
			});
		},
		require('autoprefixer-core')({ browsers: ['last 2 versions', '> 2%'] })
	];

If the file /source/css/styles.css contained this code:

$colorfore: #333;
$colorback: #fff;

@define-mixin reset {
	padding: 0;
	margin: 0;
}

main {
	font-family: Arial;
	@mixin reset;
	color: $colorfore;
	background-color: $colorback;
	
	article {
		color: $colorback;
		background-color: $colorfore;
	}
}

running gulp css would create this CSS in /dest/css/styles.css:

main {
	font-family: Arial, sans-serif;
	padding: 0;
	margin: 0;
	color: #333;
	background-color: #fff;
}

main article {
	color: #fff;
	background-color: #333;
}

The Advantages

PostCSS frees you from the limitations and choices imposed by preprocessor authors. The approach offers several benefits:

  • Modularity
    You need only add the plugins and functions you require for your project.
  • Lightweight
    Preprocessors are increasingly large and sophisticated applications. You probably won’t want or use every feature but they’re still present. PostCSS reduces the bulk.
  • Immediate implementation
    Have you ever waited for something to become available in Sass, LibSass, LESS, Stylus or another preprocessor? You can now develop your own features using…
  • JavaScript functions
    Your CSS preprocessor uses JavaScript — a true programming language (despite what some people say!)

    Most preprocessor language constructs are basic. You’ll often see functions and mixins which are more complex and difficult to comprehend than the raw CSS they create. With PostCSS, facilities which will never be implemented in Sass/LESS/Stylus are available. You can open files, read from databases, make HTTP requests or create complex calculations with ease.

  • Enforce development policies
    Presume you wanted your team to avoid @extend declarations. It’s no longer possible for anyone to use @extend unless they add an extend plugin to the build process. That would be immediately obvious.
  • It’s fast — very fast
    The authors estimate PostCSS is 4 – 40x faster than an equivalent preprocessor. I suspect the gains are even higher if you only require a few plugins. The whole solution is built in JavaScript and there’s no need to jump into another library or language interpreter.

The Disadvantages

All good then? Unfortunately, PostCSS is not a perfect solution:

  • Increased complexity
    Your build process will become more difficult to manage.

    Adding Sass is typically a line or two of code but PostCSS requires more planning — especially since plugins must be called in a specific order. For example, @import in-lining should be resolved prior to parsing variables. But what if you have variables within your @import declarations? In some situations it may be necessary to call a plugin more than once.

  • A different syntax
    I initially attempted converting a small Sass project to PostCSS. Don’t even try! While I eventually succeeded, PostCSS plugins often use a slightly different syntax, e.g. @define-mixin rather than @mixin. This could lead to confusion and numerous code updates. Part of the reason…
  • PostCSS requires valid CSS
    Most preprocessors parse plain text files so any syntax is theoretically possible. PostCSS creates an object model so it requires syntactically-correct CSS from the start. Even a // single line comment can cause it to fail. You could pre-process your CSS files before passing to PostCSS but then you’re back to using a preprocessor!

Should You Abandon Your Preprocessor?

A custom PostCSS processor could be an attractive option if you’re in a one-person team embarking on a small, relatively simple self-contained project.

I also recommend PostCSS for any genuine post-processing tasks such as vendor prefixing, packing media queries into a single declaration, reducing calc() equations, applying fallbacks for older browsers, supporting CSS4 selectors, minification etc. There’s little benefit doing that work yourself.

However, Sass has achieved critical mass. No preprocessor syntax is perfect but it’ll be understood by the majority of developers in your team. Anything subtly different is unlikely to offer significant benefit or appeal to everyone.

That said, PostCSS and the similar Rework framework have enormous potential. If a modular CSS plugin system could replicate — and even mix — the syntax and functionality we want from Sass, LESS and Stylus, we would have a single preprocessor to beat all others. You can bet someone, somewhere is working on that project now…

Have you successfully used PostCSS as a preprocessor for your project? Has it enticed you away from Sass? Are you going to move on from LESS? Will you give up on Stylus?

Replies

  1. Although it looks like a modular approach to post-processing CSS can be useful, I'm still not seeing a good reason to add complexity and bloat to the development workflow. CSS is just not that difficult to warrant or justify making stylesheet creation complicated with pre- or post-processing.

    I can see why people think SASS (for example) can greatly speed up their CSS writing but it just seems to be an illusion for people who are, frankly, doing it wrong. Yes, nesting styles in SASS cuts down on repetitive typing, but if you want such specificity in your selectors to benefit from SASS nesting then you're doing it wrong. Yes, using mixins and functions to autopopulate vendor prefixes is nifty, but if you have more than 1 or 2 different box shadows or gradients then you're conceiving your CSS wrong and arguably your design work may need tightening up too. Yes, using a variable to define core colours so you only need to change a single instance is handy, but is a simple Find and Replace so abhorrent?

    Same can be said for the post-processing described in this article. Yes, I'd find it more useful to create my vendor prefixed stuff after the event, and as a developer I don't think the requirement of JavaScript processing and valid CSS is actually a negative since you'd know these anyway (or are people so wrapped up in SASS and jQuery these days they actually don't know pure CSS and JavaScript?), but as I said with pre-processing if you're creating CSS that uses a lot of rules with vendor prefixed content then arguably you're doing it wrong - how many different box shadows do you need? How many gradients? How many different transitions? And why don't you just define a class that has your box shadow defined once and slap that class onto any HTML element that needs it?

    Good article as always, but I'm afraid just don't see it.

  2. ceeb says:

    I was initially skeptical about pre-processors. I agree they can be mis-used. You should only use a pre-processor if you understand CSS. It's easy to use them badly too, e.g. creating convoluted functions for basic styles. But that's an fault of implementation - not the technology.

    The main benefits? You can keep related styles in multiple files. Nesting makes code less verbose and easier to read. Variables are handy. Useful functions can assign colors. In other words, CSS is easier to organise.

    I disagree with your point about post-processing. You don't need vendor prefixes for things like box-shadow - it's well supported. But what about modern features such as flexbox, animation or filters? I don't worry about it - I just let Autoprefixer add them as necessary according to data from caniuse.com. I no longer care whether Apple does or doesn't support a specific feature - the hard work is done for me. Features such as media query packing, calc() reduction, and fallbacks are a bonus.

    And what about minification? That's a post-processing feature. Doing that manually is painful.

    Use a pre or post-processor for a while and life becomes more difficult without it. Try using one - you may like it.

  3. I can see why you're skeptical. I'm just looking into trying postCSS to see what can be done with it and I can already see how it will look bloated. That said, when I first used Sass I had the same initial feelings about complexity and bloat but as soon as I had learnt how to do things better the issue was much less of a concern, especially due to the benefits it's given me. At the moment I can only see one benefit that can;t easily be done with Sass and that is the ability to write my own processors with js.

    Hopefully with a bit of time and experimentation it'll be viable to use but for the moment it's still going to be Sass over postCSS on any medium to large project.

  4. ceeb says:

    Of course, there's no reason why you can't use both Sass and PostCSS. You possibly are already. That way, you can still use the normal SCSS syntax but drop into JavaScript when you need advanced functionality.

  5. ceeb says:

    It's got to be easier than defining a convoluted Sass function! They always confuse me.

4 more replies