Theme development with Grunt: LiveReload, lint, uglify, oh my!

The JavaScript part comes from its dependency on using Node.js. The task runner is another way of saying automation. So what we have is an automation tool built on top of Node.js.

What can it do, and why do I need it?

As a Drupal themer/developer, I'm all about being lazy. Lazy in that I don't like doing the same thing over and over again. It compiles & watches for SASS changes. It LiveReloads. It lints, minifies & combines JavaScript and/or CSS files. It copies library assets. It can minify & combine images. Plus tons more. Pretty much any repetitive task can accomplished with Grunt.

Install Node.js/npm & Grunt

Once Node.js & npm are installed, in a terminal, run (might need to use sudo):

$ npm install -g grunt-cli

Grunt's files

A Grunt project consists of two files: package.json & Gruntfile.js. package.json is used by npm to install the Grunt plugins. The Gruntfile.js (or Gruntfile.coffee if you prefer) is the config file where we setup our tasks. Both of these files should be placed at the root of your theme.

package.json

Start with a simple package.json file, and you can customize it to your specific project:

Once this initial stub is in place, you need to add plugins. The easiest way is to run the following:

$ npm install [plugin] --save-dev

where [plugin] is the name of the plugin you want installed. The --save-dev argument tells npm to not only install the plugin locally, but also adds it to the devDependencies section of the pavkage.json file. At the bare minimum, you'll need to install grunt itself as a plugin:

$ npm install grunt --save-dev

The Grunt website has a rather extensive set of plugins you can use. Here's an example of a package.json file I used recently:

First is the jshint block of code. Within this block is an "options" section that allows you to specify "global" options that will be used anytime the jshint task is invoked. In this case, the jshintrc: '.jshintrc' allows you to specify where the jshint config file is located.

Also in this code block is a "dev" section and a "build" section. The section identifier is simply a way to reference which set of files (or options) to use.

As you'll see below, you can use these set (or target) identifiers like so:

$ grunt jshint:dev

or

$ grunt jshint:build

The next part that was added is the loadNpmTasks() function. This loads the plugin into Grunt. You'll need to specify each plugin that you use in the Gruntfile.

Finally, the registerTask() function is used to create chainable tasks. This allows you to create a task that does multiple things at once. For example, with one command, you can lint your JavaScript files, combine them into a single file, then minimize the single file, all the while putting the rendered file in a location completely different from your source files.

There are two tasks that I've created. The "default" task is what I leave running all the time. Since it is the default, you can start it by simply running:

$ grunt

No arguments are required if you create a default task. First, it copies files from third party libraries that I've specified. Next, it checks my JavaScript files for any issues. Then is uses the "dev" section of uglify to combine JavaScript files into a single file. Next it uses the compass plugin to compile all the .scss files. Finally, it starts the watch plugin, which doesn't terminate (until you manually stop it with a Ctrl+C). Any time the "watch" plugin detects a change in one of my .scss files, it runs the compass plugin to recompile .scss partials into .css. It also JSHints whenever a change is saved. And finally (and certainly not least) it LiveReloads the browser.

Whew! That was a lot of things to do. But Grunt handled it marvelously. Pretty cool, eh?

I've also setup a "build" task that can be used when development is completed and you want your files optimized. Run it by also specifying the task like so: