How to generate CSS spacing utility with PostCSS
What is PostCSS
PostCSS is a tool for transforming CSS with JavaScript. It's like creating your own language for CSS. It unlocks limitless possibilities with what you can do with CSS.
What we're trying to achieve
We're going to recreate the Bootstrap Spacing Utility. In a nutshell, it will generate classes for margins and padding at different directions and sizes. However, I'll be using different sizes. Here's a sample of the code that will be generated:
.mt-1 {
margin-top: 0.25rem;
}
px-2 {
padding-left: 0.5rem;
padding-right: 0.5rem;
}
Because I prefer not to introduce new syntax, the plugin would look for a comment with a special string and append the new classes.
The code
Let's create a file for our plugin. I'll just call mine plugin.js
.
const postcss = require('postcss')
module.exports = postcss.plugin('postcss-custom', function () {
return function (root) {
root.walkComments(function (comment) {
if (comment.text && comment.text.indexOf('@spacing') >= 0) {
spacing(comment)
}
})
}
})
function spacing(comment) {
const unit = 0.25
const directions = {
// placed inorder so that they can overwrite correctly
t: ['top'],
r: ['right'],
b: ['bottom'],
l: ['left'],
x: ['left', 'right'],
y: ['top', 'bottom'],
'': [],
}
function generate(type, alias, direction, props, multiplier) {
const postfix = multiplier < 0 ? 'n' + Math.abs(multiplier) : multiplier
const rule = postcss.rule({ selector: `.${alias}${direction}-${postfix}` })
if (direction === '') {
rule.append(postcss.decl({
prop: type,
value: (multiplier * unit) + 'rem'
}))
} else {
props.map((cur) => {
rule.append(postcss.decl({
prop: `${type}-${cur}`,
value: (multiplier * unit) + 'rem'
}))
})
}
comment.after(rule)
}
for (let dir in directions) {
for (let i = -2; i <= 8; i++) {
generate('margin', 'm', dir, directions[dir], i)
if (i >= 0) {
generate('padding', 'p', dir, directions[dir], i)
}
}
}
}
To add this plugin to PostCSS, simply add a new entry with the path to the JS file e.g. styles/plugins/my-plugin.js
In the plugin file we need to export a default object for our plugin. When you define the plugin with postcss.plugin()
, you need to give it a name. In this case it's postcss-custom
.
module.exports = postcss.plugin('postcss-custom', function () {})
The above function will search the document for all comments with the special text @spacing
. Then it calls spacing()
to generate the classes.
The important code to note in the comment()
function is in the generate()
function.
postcss.rule()
will create a rule with the selector given. Then we add new property to that selector with .append()
. Finally we will append that selector after the comment with .after()
.
As you can see, creating a new PostCSS is quite easy.