Becoming Self-Sufficient with Grunt

I’ve fallen in love with Grunt, the so-called JavaScript task runner. Before I go into the namesake of this post, I want to first talk about what Grunt is and why you should be using it.

Grunt Logo

Although the “JavaScript taskrunner” is, in a very mechanical sense, true, it does nothing to tell you what Grunt will do for you as a developer. Grunt makes it really easy to whip your static files into shape. If you use any sort of preprocessors, you know you ultimately have to get the files you author into something the browser can understand. SASS and LESS have to come back around to CSS, HAML needs to be HTML, CoffeeScript will end the day as JavaScript, etcetera.

CodeKit does a good job of this for Mac users, but it’s maintained by one man (so far as I know) which limits the featureset to what that one man can turn out. He’s done a great job, but he can’t compete with the entire Grunt community turning out plugins for everything under the sun. Windows users have options, but I’ve heard of nothing that seems to approach CodeKit. Grunt can do all these things and then some.

That’s generally what Grunt can do for a developer. Now, here’s how I use Grunt. I’ve created two tasks: a default task and a build task. The default task is a magical task which runs whenever you type grunt at the terminal. In this task, I’ve included plugins useful for my development workflow. My thinking is that I’ll be developing more than deploying, so development-specific workflow gets the shorter command. The build task gets run when I type grunt build. You can have any number of tasks named anything you like and run them with grunt <taskname>.

In the default task, I

  • Run jshint on my scripts
  • Concatenate the scripts (Not necessary in many development scenarios, but it’s what I needed for this particular project.)
  • Run Compass to compile my SASS with the development options (preserving comments and nesting)
  • Start a development server
  • Start watching for changes in my JS and SASS

All these tasks are performed by Grunt plugins with very minimal configuration. My build task is slightly different.

  • Run jshint as in the default task
  • Concatenate and uglify scripts
  • Run Compass in production mode (remove comments, concatenate, and minify)
  • Compress images using Yahoo’s Smush.it lossless JPEG/PNG compression tool

You’ll notice here I’m not running the dev server or the watch task. That’s because the assumption here is that development time is over, and it’s time to get down to business. These files are ready to deploy.

My setup is a simple one, but it provides me with a great deal of convenience. Grunt is a command line tool with all that entails, but, as is often the case with the command line, the power and flexibility cannot be matched by anything with a GUI.

All this sounds good, but how do you get started using it? I’m going to walk with you down the same path I took to Gruntland. It’s not even that treacherous!

First, you need to read the Grunt Getting started page. This will help you get Grunt installed and show you around a bit. Then, you need to look at their Configuring tasks page — in particular, the Files section of it. Almost every Grunt plugin operates on your files in some way. This section shows you how to tell the plugin which files to watch and where to put any output files it might have. Grunt offers a few different formats for this.

For simple configurations, the compact format works really well. If you have something a bit more complex and need to change settings per file location, you might want to look at the files array format. You can mix and match inside your Gruntfile, so just keep them in mind. It’s good to have a couple of them in your repertoire.

Next, check out this great primer by Sethen Maleno. He does an excellent job of easing you into Grunt. Now, you should have a nice working setup. I hope, at this point, you’re a little dizzy thinking of all the other things you might be able to have Grunt do, but maybe you’re not sure quite how to get there.

Before we get started, here’s my Gruntfile. Don’t worry about trying to figure out what everything does, but you may want to refer back to it as I talk about its parts to get a bird’s-eye view.

var scripts = ['js/jquery-1.9.1.js', 'js/jquery.animate-enhanced.min.js','jquery.easing.min.js', 'js/**/*.js', '!js/scripts.js'];
  module.exports = function (grunt) {
    // Project configuration.
    grunt.initConfig({
      pkg: grunt.file.readJSON('package.json'),
      jshint: {
        options: {
          force: true
        },
        all: scripts + ['!js/jquery-1.9.1.js', '!js/jquery.animate-enhanced.min.js', '!jquery.easing.min.js']
      },
      concat: {
      	dist: {
      	  src: scripts, dest: 'js/scripts.js'
      	  }
      	},
      uglify: {
        min: {
          files: {
            'js/scripts.js': ['js/scripts.js']
          }
        }
      },
      compass: {
        dev: {
          options: {
            sassDir: 'sass', cssDir: 'css'
          }
        },
        production: {
          options: {
            sassDir: 'sass', cssDir: 'css',
            environment: 'production',
            outputStyle: 'compressed',
            force: true
          }
        }
      },
      smushit: {
      	images: {
      		src: ['img/**/*.{png,jpg,jpeg}']
      		}
    		},
  		watch: {
  			options: {
  				livereload: true
				},
				scripts: {
					files: scripts, tasks: ['jshint', 'concat']
				},
				styles: {
					files: ['sass/**/*.{sass,scss}'],
					tasks: ['compass:dev']
				}
			},
			connect: {
				server: {
					options: {
						port: 8888,
						hostname: '*'
					}
				}
			}

		});

		grunt.loadNpmTasks('grunt-contrib-jshint');
		grunt.loadNpmTasks('grunt-contrib-concat');
		grunt.loadNpmTasks('grunt-contrib-uglify');
		grunt.loadNpmTasks('grunt-contrib-compass');
		grunt.loadNpmTasks('grunt-contrib-watch');
		grunt.loadNpmTasks('grunt-contrib-connect');
		grunt.loadNpmTasks('grunt-smushit');

		// Development task checks and concatenates JS, compiles SASS preserving comments and nesting, runs dev server, and starts watch
		grunt.registerTask('default', ['jshint','concat', 'compass:dev', 'connect:server', 'watch']);
		// Build task builds minified versions of static files
		grunt.registerTask('build', ['jshint', 'compass:production', 'concat', 'uglify', 'smushit']);  };

Let’s learn how to become self-sufficient with Grunt. What’s the point of having access to all these great plugins if you don’t know how to set them up? Thankfully, most of the plugins do a great job of documenting how they work. You probably have an idea of what you want Grunt to do, but you’re not sure if a plugin is available. Your first stop is the official Plugins page.

I know my site has several images that could probably be compressed better, and, although I love ImageOptim, it’s a bit of a pain to have to run it manually every time I add an image or make a change. I’ll start with a search for jpg.

That yields a few different options for me. Notice the first option in my results: contrib-imagemin. Plugins named with “contrib-” are developed by the Grunt team. That means you can usually count on these to be stable and well-documented. I use several of the official plugins, and they are incredibly useful. However, I’m a rebel. I’ve heard of Yahoo’s Smush.it service, so I’ll try the smushit plugin instead.

Clicking takes me to the npm page for the plugin. “npm” stands for node package manager. (Update: Reddit user tribalfloyd pointed out that this is not correct. According to the documentation, “it is a recursive bacronymic abbreviation for ‘npm is not an acronym.’”) You should have installed it in a previous step. If you have, you’ll start setting up this plugin by installing it with npm. The command is right here on the npm page under “Getting Started.” Every plugin I’ve tried so far has this in the documentation, but it is also pretty constant across plugins. You first install the plugin by running npm install <package-name> --save-dev replacing <package-name> with the name of this particular npm package. That name is at the top of the npm page for the package. So, our command to install the Smush.it plugin from the terminal is npm install grunt-smushit --save-dev.

Most of what this command does is easy to decipher, but the --save-dev may not be. This switch adds the package to your package.json file, the file which tells npm about the requirements for this project (among other things). If you’re collaborating or would like to share your project, this file will allow another person to quickly get the project up-and-running on a different computer by simply running npm install in the project directory.

Now that we have the plugin installed, we’ll tell Grunt about it. This is also in the “Getting Started” section of the documentation. To do so, add grunt.loadNpmTasks('grunt-smushit'); to your Gruntfile.

This is where plugins start to diverge: task configuration. As I said before, most plugins need to be told about which files they should watch, and that’s easily accomplished with the information on Grunt’s Configuring tasks page. Everything else is up in the air. Your saving grace is the plugin documentation’s “Task” section where you learn the particulars of configuring this plugin.

The Smush.it plugin offers a quick and simple configuration right at the top. This is really nice. If you don’t have any special needs, you can look at this and quickly figure out what you need to do. It’s important to note that, for most plugins (perhaps all), the first object inside the plugin’s object is the name of your task configuration.

grunt.initConfig({
	smushit: {
		mygroup: {
			// This is the name of this configuration.
			// It's completely arbitrary, and I can make it whatever I want.
			src: ['tests/img/**/*.png','tests/img/**/*.jpg'],
			dest: 'tests/img/min'
		}
	}
});

You can make it whatever you want. This allows you to have multiple task configurations for the same plugin. Back in my Gruntfile, take a look at the Compass plugin task configuration on line 31 to see what I mean. I have a dev task and a production task each with its own set of options.

Browse through the “Task” section and see which options are available for the plugin. You can also see the default value for each of the available options. In the case of Smush.it, the only one is service which you probably won’t want to use. This lets you use a different service for image processing besides Smush.it which is kinda the point of this plugin, right?

However, if you did want to set some options, you can do it in one of two ways: you can make an options object inside the plugin’s object or you can make an options object inside one or more of your configurations for the plugin. I’m going to pull out my Compass plugin config as an example… and it’s a good thing, too, because it needs to be refactored a bit.

compass: {
	dev: {
		options: {
			sassDir: 'sass',
			cssDir: 'css'
			}
		},
	production: {
		options: {
			sassDir: 'sass',
			cssDir: 'css',
			environment: 'production',
			outputStyle: 'compressed',
			force: true
			}
		}
	}

You can see the options objects under the dev and production configurations, and I have some duplication here. The SASS and CSS directories are the same in both cases. I want to pull this out and and add it to an options object directly under the plugin’s object. Here’s the new configuration:

compass: {
	options: {
		sassDir: 'sass',
		cssDir: 'css'
	},
	dev: { },
	production: {
		options: {
			environment: 'production',
			outputStyle: 'compressed',
			force: true
		}
	}
}

Now, my config is much DRYer. The dev task no longer makes any changes to the configuration at all. If I wanted, I might explicitly set the dev task’s environment option to development, but, since that’s the default value, it’s not necessary.

You can also override your plugin-level options with task configuration level options if you need to. This might be helpful if you had several tasks under a single plugin and most use a particular setting. You could configure that option directly under the plugin but change it inside the individual task or tasks that need a different setting.

Once your configuration is done, save it and run the task with grunt <plugin>:<task>. You can omit :<task> if you only have a single task configured for the plugin. If you have several, run this for each one to make sure they all work assuming the tasks won’t destroy anything.

You’re almost there! To really get the most out of your new plugin, you’ll want to add it to one or your custom tasks. Smush.it is going into my build task. I don’t really care about optimizing images for my local development, but I need them to be lean and mean on the web. Here’s that task before Smush.it:

grunt.registerTask('build', ['jshint', 'compass:production', 'concat', 'uglify']);

and after:

grunt.registerTask('build', ['jshint', 'compass:production', 'concat', 'uglify', 'smushit']);

This part is really simple. Just add the name of your task to the task’s array. If you had multiple configurations, follow the plugin name with a colon and the name of the task you want to run. You can see that in action with the Compass task in the configuration above.

The only caveat is that you need to consider the order of the tasks. In this case, it doesn’t really matter when the images are optimized relative to the other tasks. However, if you look at the two tasks preceding that, order is very important. My uglify task only works on the concatenated scripts.js file. If I were to run uglify before concat, it would minify the old scripts.js file before overwriting it with a new version concatentated from my other script files. Effectively, the minification would not occur. I avoid this by concatenating before minification.

Now, simply save and start running your new task! If you ever have need for more Grunt functionality, you should now be able to find a plugin, install it, configure it, and make it go using these techniques. As the proverb says, “If you give a man a Gruntfile, he’ll develop a web site. If you teach a man to write his own Gruntfile, that’s, like, way better.”

Post a question or comment