For our next demo, we're not going to build something original, but start with adapting an existing plugin that is already available for PostCSS. The plugin we will use is postcss-fontpath by Seane King (available from https://github.com/seaneking/postcss-fontpath); we're going to incorporate an autocomplete facility that automatically adds the relevant font stack, based on the name provided, and using the lists available at http://www.cssfontstack.com/.
"Why do this", I hear you ask? To prove a point—it isn't always necessary to re-invent the wheel; sometimes it is preferable to simply adapt something that exists, which doesn't quite fit our requirements. In this instance, the code we're adding will make it more useful; it will need some further development to allow for error-checking, but nonetheless still serves a purpose.
Okay, that aside, let's get stuck in and start developing our plugin:
postcss-custom-fonts.At the prompt, enter npm init to start the process of creating a package.json file—use the details shown in this screenshot, at the appropriate prompt:

underscore.js, as a second dependency (it's used for the extend method):npm install postcss --save npm install underscore --save
Keep the session open—we will need it towards the end of this exercise.
index.js—copy this to the plugin folder.
gulpfile.js and package.json from the T42 – Building a custom font plugin folder (and not the plugin one!), then save them to the root of our project area.style.css in the src folder in our project area:@font-face {
font-family: 'robotoregular';
font-path: '/fonts/Roboto-Regular-webfont';
font-weight: normal;
font-style: normal;
}
h1 { font-family: robotoregular, fontstack("Extra Stack"); }gulp then press Enter—PostCSS will go away and compile the source style sheet. If all is well, we should see the compiled results of our style sheet in the dest folder of our project area:
At this stage, we now have a working plugin—even though this is not an original creation, it still highlights a number of key concepts around construction of PostCSS plugins. Let's take a moment to explore the functionality of our plugin in more detail.
At first glance, the code for our plugin may look complex, but in reality it is relatively straightforward to follow—let's go through it in sections, beginning with defining instances of the postcss object and a fontstacks_config object we will use in the plugin:
var postcss = require('postcss');
var _ = require('underscore');
// Font stacks from http://www.cssfontstack.com/
var fontstacks_config = {
'Arial': 'Arial, "Helvetica Neue", Helvetica, sans-serif',
'Times New Roman': 'TimesNewRoman, "Times New Roman", Times,
Baskerville, Georgia, serif'
}Next up, we add a simple helper function—this is used to convert font names into title case; the names listed in fontstacks_config are case sensitive, and will fail if they don't match:
// Credit for this function: http://stackoverflow.com/a/196991
function toTitleCase(str) {
return str.replace(/\w\S*/g, function(txt){
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
});
}This is the start of the plugin—the first two lines are the obligatory initialization to make the plugin available for use, followed by defining an options object. We then use _.extend to extend the predefined values in our chosen font stack with those added to the configuration object when running the plugin:
module.exports = postcss.plugin('customfonts', function (options) {
return function (css) {
options = options || {};
fontstacks_config = _.extend(fontstacks_config, options.fontstacks);We then walk through each rule and node, working out if they first contain a font declaration, then if they contain a font name that matches one in the predefined font stacks. If there is a match, then the font name is converted to the appropriate font stack and inserted with any additional fonts specified, but which don't match our font stacks:
css.walkRules(function (rule) {
rule.walkDecls(function (decl, i) {
var value = decl.value;
if (value.indexOf( 'fontstack(' ) !== -1) {
var fontstack_requested = value.match(/\(([^)]+)\)/)[1].replace(/["']/g, "");
fontstack_requested = toTitleCase(fontstack_requested);
var fontstack = fontstacks_config[fontstack_requested];
var first_font = value.substr(0, value.indexOf('fontstack('));
var new_value = first_font + fontstack;
decl.value = first_font + fontstack;
}
});
});In the second half of the plugin, we perform a simpler task—we work our way through each rule and declaration, looking for any instances of @font-face in the code. We then define a fontpath variable that removes any quotes from the supplied values, and a format array to manage the different font formats available for use:
css.walkAtRules('font-face', function(rule) {
rule.walkDecls('font-path', function(decl) {
var fontPath = decl.value.replace(/'/g, ''),
src = '',
formats = [
{ type: 'woff', ext: '.woff' },
{ type: 'truetype', ext: '.ttf' },
{ type: 'svg', ext: '.svg' }
];We then build up the relevant statement for each font type, before assembling the custom font declaration and inserting it back into the appropriate point in our style sheet:
formats.forEach(function(format, index, array) {
if (index === array.length - 1){
src += 'url("' + fontPath + format.ext + '")
format(\'' + format.type + '\')';
} else {
src += 'url("' + fontPath + format.ext + '")
format(\'' + format.type + '\'),\n ';
}
});
decl.cloneBefore({ prop: 'src', value: src });
decl.remove();
});
});
}
});Our plugin has exposed some key concepts in PostCSS plugin design—the main ones are the use of .WalkDecls and .WalkRu
les (or .WalkAtRules). I would strongly recommend familiarizing yourself with the API documentation at https://github.com/postcss/postcss/blob/master/docs/api.md, which outlines all of the commands available within the API, and gives a brief description of their purpose.
Despite creating what should be a useful plugin, it isn't one that I would recommend releasing into the wild. At this point you may think I have completely lost the plot, but as I always say, "there's method in the madness"—there are good reasons for not publishing this plugin, so let's take a moment to explore why it might not be a sensible move to release this plugin in its current format.
Over the last few pages, we've created what should be a useful plugin to manipulate custom fonts—it automatically builds up the right font stack based on pre-defined settings, and will fill in the appropriate @font-face code for us. At this point we should have a plugin that can be released into the wild, for anyone to use…surely?
Well yes, and no—even though this plugin serves a purpose, it is not one that I would recommend making available…at least not yet! There are a few reasons why, which also help to illustrate the benefits of using the boilerplate code we covered earlier in this chapter:
test.js file or configuration associated with it—one of the requirements for releasing plugins is that each be tested, using a test.js file. Ideally we might use a service such as Travis CI to help with this, but this really only works if you use a Unix-based environment for development.css.WalkRules (line 16), and css.WalkAtRules (line 28). These two commands parse each node within the container, and call the callback function for each rule node and at-rule node. The difference here is that css.WalkRules works on every rule; css.WalkAtRules will only work on @-rules (such as @font-face). They are not interchangeable, which makes it very inefficient at compilation.package.json file for us—all we need to do is add a suitable task runner such as Gulp or Grunt.One might ask why we would even consider this route, if it is likely to throw up issues during development—the simple answer is that it helps us understand something of how plugins should be built. If we're building a plugin for personal use only, then there is no need for some of the files or processes that we have to use when releasing plugins for general use.