Build and Publish Your Own Grunt Plugin

Share this article

Grunt is a widespread and popular task runner for JavaScript. Its architecture is based on plugins that you can combine and configure to create a powerful build system for your web applications. The Grunt ecosystem is huge and offers hundreds of plugins to help you with tedious and repetitive tasks, such as linting, testing, minification, image processing, and so on. I had a blast building and publishing my Grunt plugin and I’m keen to share with you the experience I’ve gained along the way. I’ll show you how to build your own little Grunt plugin and publish it via the npm package manager. The plugin we will build in this article will serve as a remedy for so-called typographic orphans — single words on the last line of a paragraph or block element — by replacing the last space with a non-breakable space. This is a quite easy task, but while implementing it, we’ll touch all the relevant subjects, such as setup, best practices, configuration, testing, and publishing. A typographic orphan in a text paragraph If you want to get an in-depth knowledge of Grunt’s mechanics or wish to contribute to an existing plugin this article is for you. Before starting, I suggest you to take some time to have a look at the official Getting Started guide and at Etienne Margraff’s article titled How to Grunt and Gulp Your Way to Workflow Automation. The plugin that we’ll build in this article is available on GitHub. For your benefit, I added tags (called step01step04) to the repository. If you want to follow along with the code at hand just check out the respective tag. For example the command git checkout tags/step02 mirrors the state of the code after section 2.

Setting up Your Playground

Assuming you have Node.js installed on your machine, we can immediately get started to set up our plugin skeleton. Luckily, the Grunt team provides a nice tool called grunt-init to make plugin development easy. We’ll install this tool globally with npm and clone the Grunt plugin template from Git:
npm install -g grunt-init
git clone git://github.com/gruntjs/grunt-init-gruntplugin.git .grunt-init/gruntplugin
Now we’re ready to create a new directory for our plugin and run the command grunt-init:
mkdir grunt-typographic-adoption
cd grunt-typographic-adoption
grunt-init gruntplugin
We’ll be prompted with a couple of questions regarding the meta data of our plugin. When naming your Grunt plugin, remember that the grunt-contrib namespace is reserved for tasks that are maintained by the Grunt team. So, your first job is to find a meaningful name that respects that rule. Since we’re dealing with typographic orphans, I thought that a name as grunt-typographic-adoption could be appropriate. If you put your new plugin folder under version control and set a remote to GitHub before running grunt-init, you’re lucky. The scaffolding script will use the information provided by Git and GitHub to populate a lot of the points you have to tick off. Stick to the default values for Grunt’s and Node.js’ version unless some of your dependencies demand a specific one. With regard to versioning your own plugin, you should get familiar with Semantic Versioning. I suggest you to take a look at the official documentation for project scaffolding where you can read more about other available templates for grunt-init and ways to specify default prompt answers. Now, let’s have a look at the directory and file structure we have in place now:
.gitignore
.jshintrc
Gruntfile.js
LICENSE
README.md
package.json
- tasks
  | - typographic_adoption.js
- test
  | - expected
  | - custom_options
    | - default_options
  | - fixtures
    | - 123
    | - testing
  | - typographic_adoption_test.js
The .gitignore file comes in handy once you put your plugin under version control (operation that you should do and hopefully you already performed!). The Gruntfile.js specifies what needs to be done to build our plugin and luckily it comes with some pre-defined tasks, namely JavaScript linting (configured in .jshintrc) and a simple test suite (we’ll examine in detail the corresponding test folder in a minute). LICENSE and README.md are self-explanatory, pre-filled with standard content and important once you decide to publish your plugin. Finally, package.json contains all the information about our plugin including all its dependencies. Let’s install and run it:
npm install
grunt
If all went smoothly, we are rewarded with our typographic_adoption task in action and the output Done, without errors.. Give yourself a pat on the back since we have a fully functional Grunt plugin. It’s not doing anything particularly useful yet, but we’ll get there. The whole magic happens in tasks/typographic_adoption.js where we’ll implement our anti-widow code. But first of all, we’ll write some tests.

Test-Driven Development

It’s always a good idea to implement the tests first and thus specify what you want your task to accomplish. We’ll make the tests pass again, which gives us a good hint that we implemented everything correctly. Test-driven development is amazing and the users of your plugin will thank you! So what do we want to accomplish? I already told you that we want to tackle typographic orphans, that is single words on the last line of a paragraph or other block element. We’ll do this by scanning files for HTML block elements, extracting the inner text, and replacing the last space with a non-breakable space. In other words, we feed our plugin with this:
<p>
  Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
  sed diam nonumy eirmod tempor invidunt ut labore et dolore
  magna aliquyam erat, sed diam voluptua.
</p>
And we expect it to transform it into this:
<p>
  Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
  sed diam nonumy eirmod tempor invidunt ut labore et dolore
  magna aliquyam erat, sed diam&nbsp;voluptua.
</p>
Since our plugin scaffold comes with the nodeunit testing task, we can implement this kind of tests easily. The mechanism is simple:
  1. Grunt executes our typographic adoption task on all files specified in Gruntfile.js (best practice is to put them into test/fixtures).
  2. The transformed files are then stored in tmp (the .gitignore file makes sure that this folder is never going into your code repository).
  3. The nodeunit task looks for test files in test and finds typographic_adoption_test.js. This file specifies any number of tests, that is checking whether a file in tmp equals its counterpart in test/expected.
  4. nodeunit informs us on the command line if and which tests failed or if the whole test suite passed.
Usually, you build one test per configuration to make sure your task can handle all kind of scenarios and edge cases. Let’s take some time and think about possible configurations for our Grunt plugin. We basically want the user to be able to configure in which HTML elements our task is run. The default option could be every text-containing HTML block element (h1
, p, blockquote, th, and many others), while we let the user customize this with an option to set arbitrary CSS selectors. That helps to widen or narrow down the scope of our task. Now it’s time to get our hands dirty. Firstly, navigate to test/fixtures, remove the 123 file, and edit testing into a simple HTML file with some block elements that you want to test your plugin against. I decided to use a short article about Marvel’s Black Widow since typographic orphans are also sometimes called widows. Now, copy the content of test/fixtures/testing and override the two files in test/expected with it. Edit them according to what you expect as an outcome after your plugin processed the testing file. For the case with custom options I chose the scenario where the user only wants <p> elements to get de-orphanized. Lastly, edit Gruntfile.js to target only your testing file (that means remove the 123 bit from the files arrays) and give your tests a meaningful description in test/typographic_adoption_test.js. The moment of truth has arrived. Run grunt in your project’s root:
grunt
...
Warning: 2/2 assertions failed
Brilliant! All our tests fail, let’s fix this.

Implementing the Task

Before we start with implementation, we should think to the helpers we might need. Since we want to search HTML files for certain elements and alter their text portion, we need a DOM traversing engine with jQuery-like capabilities. I found cheerio very helpful and lightweight, but feel free to use whatever you are comfortable with. Let’s plug in cheerio as a dependency:
npm install cheerio --save
This installs the cheerio package into your node_modules directory and also, thanks to --save, saves it under dependencies in your package.json. The only thing left to do is open up tasks/typographic_adoption.js and load the cheerio module:
module.exports = function(grunt) {
  var cheerio = require('cheerio');
  ...
Now, let’s fix our available options. There is just one thing the users can configure at this stage: the elements they want to de-orphanize. Look for the options object inside the grunt.registerMultiTask function and change it accordingly:
var options = this.options({
  selectors: 'h1.h2.h3.h4.h5.h6.p.blockquote.th.td.dt.dd.li'.split('.')
});
The options
object gives us all the customized settings the plugin users put into their Gruntfile.js but also the possibility to set default options. Go ahead and change the custom_options target in your own Gruntfile.js to reflect whatever your tests from chapter 2 are testing. Since I just want paragraphs to get processed, it looks like this:
custom_options: {
  options: {
    selectors: ['p']
  },
  files: {
    'tmp/custom_options': ['test/fixtures/testing']
  }
}
Make sure to consult the Grunt API docs for more information. Now that we have cheerio and our options in place, we can go ahead and implement the core of the plugin. Go back to tasks/typographic_adoption.js and right under the line where you are building the options object replace the scaffolding code with this:
this.files.forEach(function(f) {
  var filepath = f.src, content, $;

  content = grunt.file.read(filepath);
  $ = cheerio.load(content, { decodeEntities: false });

  $(options.selectors.join(',')).each(function() {
    var text = $(this).html();
    text = text.replace(/ ([^ ]*)$/, ' $1');
    $(this).html(text);
  });

  grunt.file.write(f.dest, $.html());
  grunt.log.writeln('File "' + f.dest + '" created.');
});
We are looping over all the files that we have specified in Gruntfile.js. The function we call for each file loads the file’s content with the grunt.file API, feeds it into cheerio, and searches for all the HTML elements we have selected in the options. Once found, we replace the last space inside the text of each element with a non-breakable space and write that back to a temporary file. Our test suite can now compare those temporary files with our expected ones and hopefully it shows you something like this:
grunt
...
Running "nodeunit:tests" (nodeunit) task
Testing typographic_adoption_test.js..OK
>> 2 assertions passed (59ms)

Done, without errors.
Awesome! We have just implemented our own little Grunt plugin and it works like a charm! A typographic orphan in a text paragraph If you want you can further improve, extend, and polish it until you are happy with the result and feel like sharing it with other developers.

Publish Your Plugin

Publishing our plugin is easy and it takes only a few minutes. Before we push our code, we have to make sure that everything is set up correctly. Let’s take a look at the package.json file where all the information that npm uses in their registry reside. Our initial template already took care of adding gruntplugin to the keywords list, which is essential for our plugin to be found as a Grunt plugin. This is the moment to take some time and add more keywords so people can find our plugin easily. We also take care of our README.md file and provide our future users with documentation on our task’s general usage, use cases, and options. Thanks to grunt-init we already got a nice first draft to work with and can polish it from there. Once these preparations are done, we are good to publish our plugin. If you don’t have a npm account yet, you can create one on their website, or fire up npm on the command line and set up everything there. The following command will ask you for a username and password and either create a new user on npm and save your credentials at .npmrc or log you in:
npm adduser
Once you are registered and logged in, you can go ahead and upload your plugin to the npm:
npm publish
That’is it! All the information needed are automatically retrieved from thepackage.json file. Take a look at the Grunt plugin we just created here.

Conclusions

Thanks to this tutorial, you have learned how to create a Grunt plugin from scratch. Moreover, if you’ve published it, you’re now the proud owner of a Grunt plugin that is available on the Web, ready to be used by other web developers. Keep it up, keep maintaining your plugin, and stick to test-driven development. If you are in the process of building a Grunt plugin or already built one and want to share something around the process, please comment in the section below. Once again, I want to highlight that the plugin we’ve build in this article is available on GitHub.

Frequently Asked Questions (FAQs) about Building and Publishing Your Own Grunt Plugin

What is a Grunt plugin and why should I create one?

A Grunt plugin is a piece of code that extends the functionality of Grunt, a JavaScript task runner. It allows you to automate repetitive tasks such as minification, compilation, unit testing, and linting. Creating your own Grunt plugin allows you to customize these tasks to suit your specific needs, improving your workflow and productivity.

How do I start building my own Grunt plugin?

To start building your own Grunt plugin, you need to have Node.js and npm installed on your computer. Then, you can use the grunt-init command to create a new Grunt plugin project. This will generate a basic structure for your plugin, including a package.json file and a Gruntfile.js file.

How do I publish my Grunt plugin to npm?

Once you’ve built your Grunt plugin, you can publish it to npm by running the npm publish command in your project directory. Before you can do this, you need to create an npm account and log in to it using the npm login command. After publishing, your plugin will be available for others to install and use.

What is the purpose of the package.json file in a Grunt plugin?

The package.json file in a Grunt plugin contains metadata about the plugin, such as its name, version, and dependencies. This information is used by npm when installing the plugin and by Grunt when loading the plugin.

How do I add tasks to my Grunt plugin?

You can add tasks to your Grunt plugin by defining them in the Gruntfile.js file. Each task is a function that takes two arguments: the Grunt object and a task-specific options object. The Grunt object provides methods for configuring and running tasks, while the options object allows you to customize the behavior of the task.

How do I test my Grunt plugin?

You can test your Grunt plugin by writing unit tests using a testing framework such as Mocha or Jasmine. These tests should verify that your tasks are functioning correctly. You can run your tests using the grunt test command.

Can I use third-party libraries in my Grunt plugin?

Yes, you can use third-party libraries in your Grunt plugin. You can install them using npm and require them in your Gruntfile.js file. Be sure to list these libraries as dependencies in your package.json file so that they are installed along with your plugin.

How do I update my Grunt plugin?

You can update your Grunt plugin by making changes to its code and then publishing a new version to npm. Be sure to update the version number in your package.json file before publishing.

Can I share my Grunt plugin with others?

Yes, you can share your Grunt plugin with others by publishing it to npm. Others can then install your plugin using the npm install command.

What are some best practices for writing a Grunt plugin?

Some best practices for writing a Grunt plugin include writing clear and concise code, documenting your code thoroughly, writing unit tests for your tasks, and following the Grunt plugin conventions.

Stephan MaxStephan Max
View Author

Stephan is a Dublin-based developer originally from Germany. Holding a degree in computer science and being a code wrangler at heart, he works as a technical cloud adviser inside the IBM Bluemix team by day and moonlights as a front-end developer and JavaScript engineer. He loves traveling, writing, music, and building stuff that helps people.

AurelioDGruntgrunt pluginjavascriptnode.jsnodejsplugintypographic orphansweb application
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week