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.
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 a package manager.āā) 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.ā