How difficult is that for someone who’s proficient in jQuery, maybe has even written a couple plugins, but has never used JS outside the browser?
Gotta Walk Before You Crawl
When you go to the Getting Started page on the Grunt site, the first thing you are told to do is run:
npm install -g grunt-cli
This is a sign of things to come. The main thing I learned while writing this article is that in order to be comfortable with Grunt, you need to be comfortable with node.js—which means also being comfortable with:
- node.js’s package manager, npm
- the package.json file that npm uses
- node.js’s module.exports syntax
It’s not mind-bendingly difficult stuff once you get your feet wet, but still new knowledge you’re going to have to take on before you can be slinging Grunt tasks like a pro.
Best to start from the beginning.
The node package manager lets you install node.js packages in
a local directory so they can be found by node.js programs when they use
“But I’m not using node.js,” you say? Oh yes, you are. Grunt runs on node, and you’ll be using npm to install any Grunt plugins you want to use. So it’s time to learn a bit more about npm.
By including a
package.json file in your project, you can specify exactly
which modules (and which versions of those modules) to install via npm. This
file has to be true JSON, and it must include the required keys of “name” and
“version”. You can run
npm init and answer a bunch of questions to get a
package.json with more than you really need in it. Or, you can just copy
this JSON right here:
1 2 3 4 5 6 7 8 9 10 11 12
Plop that in a package.json file in the root of your project, and then run
npm install. Voila! You have installed Grunt in your project, along with
some helpful plugins.
Isn’t Grunt already installed?
"grunt" in package.json if we already installed it with the npm
command? What we installed before was
grunt-cli, (some very simple code to
grunt command available everywhere). The local Grunt code installed
via package.json and
npm install is what actually runs your project’s tasks,
defined in your project’s Gruntfile.
We include both the Gruntfile and package.json in our project to be sure we are using a version of Grunt that is compatible with our Gruntfile.
More about npm install
npm install is called without any specific package name, it will go through
your package.json file and install all of your dependencies—and unless you
tell it otherwise, your devDependencies as well.
To install new Grunt plugins, you have two options. First, you can add a
new line to your package.json file that mentions the new plugin and then run
npm install. Or (and this way is my favorite), you can run:
npm install grunt-contrib-requirejs --save-dev
This command will download the specific node package, (in our case, it’s grunt-contrib-requirejs, a node package which happens to be a Grunt plugin), and also adds that package’s information to the devDependencies section of our package.json file.
If you used
--save instead of
--save-dev, the packages
get added to the dependencies section instead of devDependencies. This would
be important if you were making your own node package. For this example,
it really doesn’t make a difference, but devDependencies is more semantically
When you run a
grunt command, Grunt looks in your
current directory for a Gruntfile that defines tasks it knows how to run.
A Gruntfile is a file named either
Gruntfile.coffee (written in CoffeeScript).
If there isn’t a Gruntfile in your current directory, Grunt will look in each directory above it. So, if you put your Gruntfile in the root of your project, you’ll be able to use the tasks defined in that Gruntfile anywhere within your project.
If there isn’t a Gruntfile anywhere above your current directory, you’ll get
A valid Gruntfile could not be found.
Here’s the very simplest Gruntfile:
1 2 3
In fact, this Gruntfile is so simple it contains no tasks. Any attempt to use it
will result in a warning:
Warning: Task "foo" not found.
But we can still see the structure of a Gruntfile. A Gruntfile is actually a
node module. Node modules signal what they want to export by assigning it to
module.exports. In this case, we’re exporting a function that receives a
grunt object when it’s called. This grunt object provides methods that allow
you to configure Grunt. It also gives you access to things like
You can find out everything you can do with the grunt object in the
Here’s a Gruntfile with a task you can actually run. The task is still useless, but whatevs…
1 2 3 4 5
We call the
registerTask method on the grunt object and pass a task name,
description (optional), and a function that defines the actual task.
Save that into your Gruntfile.js and then run
grunt pig and you’ll see your
Piggybacking on the Hocks of Giants
Now we know how to register our own tasks with Grunt. This is amazingly powerful – not only do you have the entire Grunt API to work with, you also have all of node! The sky is the limit when writing your own tasks.
/js ├── plugins │ ├── jquery.cycle.all.js │ └── jquery.timeago.js ├── jquery-2.0.3.min.js └── site.js
To use the concat plugin, the first thing we have to do is to make sure we actually have the code available. If you used the sample package.json file above, you already have it installed. If not, install it and add it to your package.json with:
npm install grunt-contrib-concat --save-dev
Now that the plugin is available, we can load that plugin by calling the
loadNpmTasks method on the grunt object. Here’s our Gruntfile now:
1 2 3
If you run
grunt -h, Grunt will show you some help text, along with a
list of available tasks. You’ll see that concat is now available. However,
if you run
grunt concat, you’ll get an error:
No "concat" targets found.
We need to add some configuration, and we do that via the grunt object’s
initConfig method. You pass it a hash, and if you want to configure
a specific task you use the task’s name as a key in that hash.
1 2 3 4 5 6 7
Figuring out how to format the parameters to concat is a little hit or miss. While it’s not the easiest to read through, I recommend checking out the examples in the concat documentation—and thankfully, you have this tutorial!
Here we’re creating a subkey called
js, which is a target of the concat
task. Some tasks (called “multi-tasks”) allow you define a range of targets.
Many plugins are multi-tasks, including concat. If you run
it’ll concat using that target’s config. If you just run
it will run all targets.
For our js target, we define a list of source files in the order we want to concat them, and a destination file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
grunt concat and you’ll find that a build.js file has been created.
Note that we’re able to use a wildcard
*.js to grab all the JS files in
the plugins directory. You can also use
some/path/**/*.js to grab all the
JS files in
some/path and its subdirectories, no matter how deep!
Since a lot of Grunt tasks deal with sets of input files and sets of output files, Grunt has some standard ways of representing source-destination file mappings. If you find yourself using Grunt a lot, you’ll want to read through the documentation at some point.
In our src definition above, we put the jQuery file first, then
the plugins and our site file so that they concat in the right order.
This assumes that none of the plugins depend on each other. If you have to,
you can name some files explicitly. Grunt concat is smart enough to not
duplicate files grabbed by wildcards. In the following example,
jquery.timeago.js would only appear once in the built file.
1 2 3 4
The concat plugin also has a few options you can set in order to add banners to the built file, strip banners from source files, set separator characters, and even process source files as if they’re Lo-Dash templates. You can check those out at your leisure.
Making it Easy
We can now type
grunt concat and build our files. This is great, but y’know
what’s even better? Less typing!
Let’s set a default task in our Gruntfile by adding this line:
Now we can just run
grunt and Grunt will run our concat task.
First, we make sure we’ve installed the plugin locally:
npm install grunt-contrib-uglify --save-dev
Then we add the tasks to our Gruntfile:
Then we add the configuration for uglify, and also make sure we use a
a separator when we concat our JS files (just to be safe).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Now we can run
grunt uglify:js or
I’ve chosen the name “js” for the uglify target. This is purely coincidence. It doesn’t have to be the same target name as the one we used for concat.
Lastly, let’s update our default task:
We can now run
grunt anywhere in our project, and Grunt will concatenate our
JS files and minify them.
Even Less Typing!
We’re now doing a lot of work by just typing a single five letter command, but what if we could type even less!? Using the watch plugin, we can run Grunt tasks automatically whenever your project’s files change.
By now this should be old hat. Install the plugin:
npm install grunt-contrib-watch --save-dev
Add its tasks to the Gruntfile:
And add some config:
1 2 3 4 5 6 7 8 9
Now we can run the
grunt watch task, and Grunt will sit there watching
our files. If any of the matched files change, Grunt will run the associated
$ grunt watch Running "watch" task Waiting...OK >> File "js/site.js" changed. Running "concat:js" (concat) task File "build.js" created. Running "uglify:js" (uglify) task File "build.min.js" created. Done, without errors. Completed in 2.313s at Sat Jul 27 2013 20:49:10 GMT-0700 (PDT) - Waiting...
The End Result
Here’s our complete Gruntfile:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
The ability to create templates with
grunt-init is cool, too. I strongly
recommend the grunt-init-jquery
template if you’re ever making a jQuery plugin. It includes some great