So you want to make a PostCSS plugin
The following is a guest post by Marcus Tisäter. I think there are a lot of us out there that find PostCSS kind of fascinating. Especially from the perspective of potentially being able to write our own plugins, in JavaScript, that transform CSS in any way we want. But where to begin? How do you do a good job? Marcus explains.
You may have heard people say that making a PostCSS plugin is easy. But how do you get started? Don’t worry, that’s exactly what we’re going to go through in this article. Then beyond that, we’ll show you what to do once you’re done with your plugin and what it looks like to be a responsible PostCSS plugin developer.
Best practices, here we come.
How do I start?
All it takes is a few lines of JavaScript to get something magical happening in PostCSS. The best way to start is with the official PostCSS-plugin-boilerplate. This starter kit includes a initial setup script, a couple npm dependencies, and boilerplate code for your PostCSS plugin; it also provides you with a file to write tests in AVA (a test runner) and some documentation to get you going.
The snippet down below is the most essential part to understand from the plugin. It’s a wrapping function in a method that you are hooking up into the plugin API.
// You have to require the shell itself, PostCSS.
var postcss = require(‘postcss’);
module.exports = postcss.plugin(‘pluginname’, function (opts) {
opts = opts || {};
// Work with options here
return function (css, result) {
// Transform the CSS AST
};
});
First, we need to understand what PostCSS gives us out of the box. PostCSS by itself does nothing; it’s just a surrounding wrapper for your plugin. The PostCSS core contains a parser that generates a CSS AST (Abstract Syntax Tree), which is a representation of a node tree that parses strings of CSS. When we change something inside the CSS Abstract Syntax Tree, PostCSS will still represent it as a root node but stringify the syntax tree back into a CSS string.
Let’s write a really simple PostCSS plugin. We can call it PostCSS-backwards. PostCSS-backwards will let you reverse CSS declaration values backwards. color: dlog
would reverse to color: gold
and vice versa.
var postcss = require(‘postcss’);
module.exports = postcss.plugin(‘postcss-backwards’, function (opts) {
opts = opts || {};
return function (css, result) {
// Runs through all of the nodes (declorations) in the file
css.walkDecls(declaration => {
declaration.value = declaration.value.split(”).reverse().join(”);
});
};
});
Of course, this is just a silly example. It’s not useful and nobody would ever use this. More importantly, this would be bad for the PostCSS plugin ecosystem. We’ll talk more about that later.
Before we jump into the actual PostCSS API, I would like you to take look at one of my absolute favorite PostCSS plugins, PostCSS Focus. PostCSS Focus adds a :focus
selector to every :hover
in your stylesheet. This is great for improving accessibility, as :focus
styles are often forgotten and should almost always be paired with :hover
styles.
Have a look at the code marked with a few comments of how this plugin is made.
var postcss = require(‘postcss’);
module.exports = postcss.plugin(‘postcss-focus’, function () {
return function (css) {
// Callback for each rule node.
css.walkRules(function (rule) {
// Match the individual rule selector
if ( rule.selector.indexOf(‘:hover’) !== -1 ) {
// Array to contain the rule’s individual selector.
var focuses = [];
rule.selectors.forEach(function (selector) {
// Passes all declaration values within the match of hover replacing those values with the returned result of focus.
if ( selector.indexOf(‘:hover’) !== -1 ) {
focuses.push(selector.replace(/:hover/g, ‘:focus’));
}
});
// Checks if array contain values
if ( focuses.length ) {
// Concat the original rules with the new duplicated :focus rules
// Groups of selectors are automatically split with commas.
rule.selectors = rule.selectors.concat(focuses);
}
}
});
};
});
Get to know the PostCSS API
The PostCSS API is the fastest and most accurate CSS parser that I have ever used. The documentation behind it is very well written and covers everything you need to know in order for you to master it. The API provides various amounts of functions, classes, common methods, and modules to use. There are methods that you might already be familiar with like, prepend
, append
, clone
, cloneAfter
or replaceWith
. You can compare it to the DOM(Document Object Model) API, being able to manipulate CSS. It’s the same ideology.
If you can’t get what you want done with the PostCSS API directly, you can extend your plugin with so-called helper tools. Tools like selector, value and dimension parsers or function and property resolvers. The Function resolver is one of the more popular ones. It’s a tool made for exposing JavaScript functions.
Other extendable tools to use:
- Value parser – Transforms CSS declaration values and at-rule parameters into a tree of nodes.
- Property resolver – Helper method to resolve a rule’s property value
- Selector parser – Selector parser with built in methods for working with selector strings.
- Dimension parser for number, length and percentage. – Parse a CSS dimension into a JavaScript object.
What would make me a responsible PostCSS plugin developer?
Do one thing, and do it well.
PostCSS plugins should be created to do one particular thing; it can be as simple as adding a :focus
selector to every :hover
in your stylesheet, or converting a unit size like pixels into ems. The important thing is to avoid writing a multi-tool plugin that does lots of things. PostCSS users like to pick their unique set of plugins to do the things they need to do.
If you want to build a multi-tool plugin, you can bundle a set of individual PostCSS plugins into a pack. You might have heard of plugins that are made like that, like cssnext for future CSS syntax or cssnano for modern CSS minification. That’s often appreciated from PostCSS users. It saves them from having to handpick their own sets.
Keep it Simple, Stupid
The old good U.S. Navy design principle KISS (Keep It Simple, Stupid) is a perfect principle to follow when making a PostCSS plugin. Take PostCSS-focus as an example, it’s a very simple idea but still a very usable PostCSS plugin to correct accesbility. The Unix philosophy suits this perfectly in terms of code as well:
Build code that is short, simple, clear, and modular.
I have a cool idea for an PostCSS plugin
There are more than 200 registered PostCSS plugins, so before hacking away on your idea for a plugin, look it up in the PostCSS plugin register. There is a strong possibility that it has already been created by someone else, and you don’t necessarily need to rebuild it. Instead, helping one another out is what makes a good plugin ecosystem. Every PostCSS plugin author I know appreciates support, ideas, and pull requests.
PostCSS recently opened up a [suggestion box](https://github.com/postcss/postcss-plugin-suggestion-box) for users to submit ideas for PostCSS plugins.
Thinking outside the box
The amazing thing about PostCSS is that you can build something that’s very simple but yet efficient, unique, and futuristic. Amazing and popular tools like Autoprefixer, uncss, CSS Modules, and Stylelint are all powered by PostCSS. They are great examples of thinking outside the box.
What’s a bad idea for a PostCSS plugin?
I advise you to not write a “syntax sugar” plugin.
For example, short hand prefixes for non-standard declaration properties or values, like a plugin that transforms font: 10px 700
into font-size: 10px; font-weight: 700
. It looks like standard CSS, but it isn’t. Imagine handing over your CSS codebase that uses this over to another developer. It would create confusion and frustration, be harder to maintain, and have an unnecessarily bad learning curve.
I think that’s why some developers are still very skeptical about PostCSS, it let’s us create these possibilities. Still, these syntax sugar plugins can get quite popular. We have seen these mixin libraries go viral. A more appropriate way to create these is to require declaring the property in another way. _font
, perhaps, or something else that doesn’t overwrite native CSS in an incompatible way and is unlikely to ever become part of native CSS.
Create something new with the possibilities of PostCSS and keep the spirit of CSS.
– Andrey Sitnik, creator of PostCSS
JavaScript is scary?!
A lot of developers (like myself) are still not very comfortable working with JavaScript. We don’t think we have enough experience to dig into making a PostCSS plugin. PostCSS has actually turned that around for me. It is unbelievable to see how much you can accomplish with just a few lines of code and how easy it is to build a working PostCSS plugin.
PostCSS plugins that use very little code
Yet, still create something unique and useful. Have a look:
- postcss-will-change – Insert 3D hack before
will-change
property. - postcss-at-root – Place rules directly at the root node.
- postcss-calc – Reduce
calc()
references whenever it’s possible. - postcss-z-index – Reduce
z-index
values. - postcss-alias – Create custom aliases for CSS properties with an
@alias
rule. - postcss-easings – Replace easing name from easings.net to
cubic-bezier()
. - postcss-verthorz – Add vertical and horizontal spacing shorthand.
Let’s talk best practices
Your PostCSS plugin should only iterate the CSS AST (Abstract Syntax Tree) only once and keep a cache of rules, rather than multiple iteration steps. Using a string representation of a node is quite expensive, so only do that when you need to, and above all ensure that PostCSS plugins are developed using TDD (Test Driven Development).
There is one method from the PostCSS API that you should be cautious about. The node.raws
method. node.raws
contains information to generate byte-to-byte node strings, this can be bad because it stores whitespace and code formatting data from the stylesheet. As every parser can save different data, it will become very expensive.
- Use asynchronous methods whenever possible
- Set
node.source
for new nodes (generates an accurate source map) - Use
node.error
on CSS relevant errors (creates source position) - Use
result.warn
for warnings (don’t outputconsole.log()
s)
Don’t worry if this doesn’t make any sense to you. When the time comes, check out the official plugin guideline page for further detail.
Sourcemapping
PostCSS has great sourcemaps support and integration. It can auto-detect the format you expect, read, and output both inline and external maps. As a plugin developer you have to be careful not to break the sourcemapping functionality.
Every node property sets node.source
(contains a origin position in parsed input) by default. PostCSS stringify will calculate the new position of that node and use node.source
to create a mapping between the new and old position. The common mistake is that PostCSS plugin authors forget to simply add source
on the node creation, which will result in the plugin not being mapped. The best and only recommendation for plugin authors is to use the PostCSS API clone()
, cloneBefore()
or cloneAfter()
, as it will copy origin node source or set the source manually.
What do I do when I’m done?
Everyone is free to write a PostCSS plugin and publish it on GitHub and npm. In the end, it all comes down to taking responsibility for something that you have released.
With great power comes great responsibility.
But before we rush into things, there are a couple of mandatory rules for a PostCSS plugin author to follow. You can find them all here. Let’s walk you through some of the key rules.
TDD (Test Driven Development)
When writing code, it’s also important to write tests. I think developers should know what the purpose of a test is and also be able to write one. There are so many reasons why you should write test in terms. The quality of your final output is often much better, as you’ll get plenty of “aha-moments” when building and running tests. From personal experience, my code drastically improves as I learn how to refactor from the test failures.
I recommend writing tests in AVA because it has a very simple syntax and is also the most commonly used within PostCSS plugins. This is helpful for getting the most contributors for your plugin. AVA also has promise
support, which is great for async plugins and other benefits, such as speed, no implicited globals, ES2015 support, and that it runs all the tests concurrently. If you don’t prefer working with AVA, you can look into Chai or Mocha.
Let’s write a simple test for our PostCSS-backwards plugin in AVA.
// Function helper to make our tests cleaner
// This runs our plugin
function run(t, input, output, opts = {}){
return postcss([ plugin(opts) ]).process(input)
.then( result => {
t.same(result.css, output);
t.same(result.warnings().length, 0);
});
}
// This test passed.
test(‘reverse color value from dlog to gold’, t => {
return run(t, ‘a{ color: dlog }’, ‘a{ color: gold }’);
});
CI Service
Using a CI service like Travis for testing code in different node environments is recommended, as you get to test if your code meets the requirements right from node. I also like Travis as it’s configurable and you can run all tests automatically when you commit to your repo and get pass/fail notifications. Give it a go!
Naming your plugin
Your PostCSS plugin name should prefix with “postcss-“, indicating that it in fact is a PostCSS plugin and also clarifying the purpose of what it does just by reading the name. It’s also preferable to have the name lowercased.
- Preferred: postcss-my-cool-plugin
- Not preferred: PostCSS-My-Cool_Plugin
Documentation
You don’t necessarily have to write a lot of documentation, but it is important to describe what your PostCSS plugin does by showing input and output code examples in the README. This makes it more clear for the user to understand what the purpose of the plugin is.
Keep a changelog
It’s important to keep a changelog file that is updated with each and every release you have published for your PostCSS plugin. That is very beneficial both for you (having a history to look back on) and for the users of your plugin (a reference for updates and changes). I would recommend a npm publisher like npmpub and to read more on keepachangelog.com/ for tips and tricks on maintaining a good changelog file.
Last words of wisdom
I think the best way to learn how to make PostCSS plugin is to throw yourself out there, take the time to deep dive into getting to know the PostCSS API and playing around in the AST Explorer.
Remember that you are not alone on this journey; you have the PostCSS community to assist you. The beautiful thing about PostCSS is its community. It sounds cliché, but I want to emphasize the fact that you often get to talk with the experts behind the PostCSS core and receive tons of help.
If you have any questions, feel free to leave them in the comments section or tweet me and I’ll try my best to give you an answer. Be sure to check out the documentation at the official website postcss.org.
I would like to thank Ben Briggs, Andrey Sitnik, Dan Gamble, Chris Coyier and Malin Gernandt for making this article possible through proofreading and ideas.
So you want to make a PostCSS plugin is a post from CSS-Tricks