Monday, September 23, 2013

How to create a Grunt Plugin

Grunt is an amazingly powerful tool when building a webapp. Using some of the many existing grunt plugins found either on the gruntjs github account or others like grunt-connect-proxy you can automate time consuming tasks like running JSHint and UglifyJS simply by typing "grunt build".  Grunt can also improve your workflow by adding watches to your sass and coffee files to auto fire compass and coffee tasks combined with live reload to save you from hitting hitting command-r all day long.  A list of available grunt plugins can be found on the grunt site, or at the npm registry with the keyword "gruntplugin".

But sometimes you need something custom. As luck would have it, grunt plugins are easy to create and they run in NodeJS which means they're written in JavaSciprt! w00t!


How to load a grunt plugin

The first question may be where do I write this plugin?  There are a few options on how to load a grunt plugin which will determine where you write it.  

The simplest way is from a folder. This may be a good option if your plugin is specific to your project since you can just make this folder under your project. If you go with this option, you'll simply put your code in this sub folder.

Another option is to create a repo for your plugin.  This is a good option if you want to share this plugin across multiple projects.  If you go with this option, create a separate project for your plugin using git init and push your work to your remote repo.

A third option is to register it on the npm registry with the keyword "gruntplugin" for the world to share.  To do so, you'll have to make your project publicly accessible by publishing it to a site like github or bitbucket (Poor bitbucket never gets any love). If you go with this option, create a separate project for your plugin using git init and push your work to your public repo (github for example). Later you'll have to register this project on the npm registry by using npm publish


Project Structure

So now that we know where we're putting this thing, what does the plugin structure look like?  The best way to create your structure is to use the grunt plugin grunt-init-gruntplugin.  This is built on top of grunt-init which is a scaffolding tool for grunt projects. Using this is highly recommended because not only will it save you time, but you'll be adhering to best practices out of the gate. 

Install grunt-init and grunt-init-gruntplugin
$ sudo npm install -g grunt-init 
$ git clone https://github.com/gruntjs/grunt-init-gruntplugin.git ~/.grunt-init/

That was easy.  Now make your plugin folder and scaffold it out like this
$ mkdir superplugin && cd $_ 
$ grunt-init superplugin

You'll be prompted with a lot of questions. Answer them and all will be taken care of for you.  Keep in mind that the "grunt-contrib" namespace is reserved for plugins maintained by the Grunt team themselves, so be nice and avoid using that in your plugin name, especially if you plan on publishing your plugin to the npm registry.

All done? Now take a look at your structure.  You'll notice:
  1. package.js - This contains your plugin information. This file is required so that you and others may run "npm install your-plugin"
  2. Gruntfile.js - This contains grunt options for your project.  This includes sample default and custom options for you to fill in, as well as a place holder for nodeunit in case you want to write unit tests for your plugin (and of course you do, right?)
  3. README.md - This is a default readme that follows the structure of other grunt plugins including information on grunt and grunt plugins so people aren't lost if they stumble on your git repo
  4. LICENSE-MIT - Open Source FTW
  5. test - This folder contains a scaffolded out unit test for you to fill in
  6. tasks - This folder contains a scaffolded out plugin for you to fill in. Finally the good stuff! This is where your plugin code actually goes.  The stubbed out file will already include code to export your module, register itself as a grunt task so it may be included in another project's Gruntfile.js as a task, samples of how to read your plugin's options (see #2 above), and samples using the grunt api.

Go ahead and fill you in task code with something.  I'll wait for you to come back.


Including your new plugin

Now that your plugin solves a problem that has been plaguing the world, you'll have to include it in your webapp project's package.json file in order to actually use the thing later.

Going back to the three ways of loading a grunt plugin from before, if you just created your plugin as a sub folder of your project just type
$ npm install your-plugin-folder-name

If you created a separate repo for your plugin, then you can reference that repo's URL in your webapp's package.json file under devDependencies
"devDependencies: { "grunt-superplugin": "git://myrepobox/grunt-superplugin.git" }

If you chose to publish your plugin to the npm registry using "npm publish" then you can run the following command
$ npm install grunt-superplugin --save-dev

This will modify for your package.json file for you as follows:
"devDependencies: {  "grunt-superplugin": "~0.1.0"}

Using your new plugin

Is this all making sense?  Now that you've installed your plugin, you'll want to actually use it.  To do so, you'll add it to your webapp's Gruntfile.js.  If you were writing a task that you want run at build time for example, you could add it to your build task list as follows:
grunt.registerTask('build', ['superplugin']
and then add the options for your plugin to your grunt initConfig section as follows (for example):
grunt.initConfig({  
  superplugin: { 
    files: { src: ['file1', file2'] } 
  } 
});

Now when you run "grunt build" on your webapp, you should see the output from your superplugin in the console.  More importantly, the problem that has been plaguing the world is now fixed.  Good job everybody!



1 comment:

  1. Thanks for the post but man blogpost could really use some syntax highlighting and blockquote styling. It wasn't pleasant trying understand what is what...

    ReplyDelete