Front-end developers are often told to do certain things:
- Work in as small chunks of CSS and JavaScript as makes sense to you, then concatenate them together for the production website.
- Compress your CSS and minify your JavaScript to make their file sizes as small as possible for your production website.
- Optimize your images to reduce their file size without affecting quality.
- Use Sass for CSS authoring because of all the useful abstraction it allows.
That’s not a comprehensive list of course, but those are the kind of things we need to do. You might call them tasks.
I bet you’ve heard of Grunt. Well, Grunt is a task runner. Grunt can do all of those things for you. Once you’ve got it set up, which isn’t particularly difficult, those things can happen automatically without you having to think about them again.
But let’s face it: Grunt is one of those fancy newfangled things that all the cool kids seem to be using but at first glance feels strange and intimidating. I hear you. This article is for you.
Let’s nip some misconceptions in the bud right away
Perhaps you’ve heard of Grunt, but haven’t done anything with it. I’m sure that applies to many of you. Maybe one of the following hang-ups applies to you.
I don’t need the things Grunt does
You probably do, actually. Check out that list up top. Those things aren’t nice-to-haves. They are pretty vital parts of website development these days. If you already do all of them, that’s awesome. Perhaps you use a variety of different tools to accomplish them. Grunt can help bring them under one roof, so to speak. If you don’t already do all of them, you probably should and Grunt can help. Then, once you are doing those, you can keep using Grunt to do more for you, which will basically make you better at doing your job.
Grunt runs on Node.js — I don’t know Node
You don’t have to know Node. Just like you don’t have to know Ruby to use Sass. Or PHP to use WordPress. Or C++ to use Microsoft Word.
I have other ways to do the things Grunt could do for me
Are they all organized in one place, configured to run automatically when needed, and shared among every single person working on that project? Unlikely, I’d venture.
Grunt is a command-line tool — I’m just a designer
I’m a designer too. I prefer native apps with graphical interfaces when I can get them. But I don’t think that’s going to happen with Grunt1.
The extent to which you need to use the command line is:
- Navigate to your project’s directory.
- Type
grunt
and press Return.
After set-up, that is, which again isn’t particularly difficult.
OK. Let’s get Grunt installed
Node is indeed a prerequisite for Grunt. If you don’t have Node installed, don’t worry, it’s very easy. You literally download an installer and run it. Click the big Install button on the Node website.
You install Grunt on a per-project basis. Go to your project’s folder. It needs a file there named package.json at the root level. You can just create one and put it there.
The contents of that file should be this:
{
"name": "example-project",
"version": "0.1.0",
"devDependencies": {
"grunt": "~0.4.1"
}
}
Feel free to change the name of the project and the version, but the devDependencies
thing needs to be in there just like that.
This is how Node does dependencies. Node has a package manager called npm for managing Node dependencies (like a “gem” for Ruby if you’re familiar with that). You could even think of it a bit like a plug-in for WordPress.
Once that package.json file is in place, go to the terminal and navigate to your folder. Terminal rubes like me do it like this:
Then run the command:
npm install
After you’ve run that command, a new folder called node_modules will show up in your project.
The other files you see there, README.md and LICENSE are there because I’m going to put this project on GitHub and that’s just standard fare there.
The last installation step is to install the Grunt CLI (command line interface). That’s what makes the grunt
command in the terminal work. Without it, typing grunt
will net you a “Command Not Found”-style error. It is a separate installation for efficiency reasons. Otherwise, if you had ten projects you’d have ten copies of Grunt CLI.
This is a one-liner again. Just run this command in the terminal:
npm install -g grunt-cli
You should close and reopen the terminal as well. That’s a generic good practice to make sure things are working right. Kinda like restarting your computer after you install a new application was in the olden days.
Let’s make Grunt concatenate some files
Perhaps in our project there are three separate JavaScript files:
- jquery.js – The library we are using.
- carousel.js – A jQuery plug-in we are using.
- global.js – Our authored JavaScript file where we configure and call the plug-in.
In production, we would concatenate all those files together for performance reasons (one request is better than three). We need to tell Grunt to do this for us.
But wait. Grunt actually doesn’t do anything all by itself. Remember Grunt is a task runner. The tasks themselves we will need to add. We actually haven’t set up Grunt to do anything yet, so let’s do that.
The official Grunt plug-in for concatenating files is grunt-contrib-concat. You can read about it on GitHub if you want, but all you have to do to use it on your project is to run this command from the terminal (it will henceforth go without saying that you need to run the given commands from your project’s root folder):
npm install grunt-contrib-concat --save-dev
A neat thing about doing it this way: your package.json file will automatically be updated to include this new dependency. Open it up and check it out. You’ll see a new line:
"grunt-contrib-concat": "~0.3.0"
Now we’re ready to use it. To use it we need to start configuring Grunt and telling it what to do.
You tell Grunt what to do via a configuration file named Gruntfile.js2
Just like our package.json file, our Gruntfile.js has a very special format that must be just right. I wouldn’t worry about what every word of this means. Just check out the format:
module.exports = function(grunt) {
// 1. All configuration goes here
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
concat: {
// 2. Configuration for concatinating files goes here.
}
});
// 3. Where we tell Grunt we plan to use this plug-in.
grunt.loadNpmTasks('grunt-contrib-concat');
// 4. Where we tell Grunt what to do when we type "grunt" into the terminal.
grunt.registerTask('default', ['concat']);
};
Now we need to create that configuration. The documentation can be overwhelming. Let’s focus just on the very simple usage example.
Remember, we have three JavaScript files we’re trying to concatenate. We’ll list file paths to them under src
in an array of file paths (as quoted strings) and then we’ll list a destination file as dest
. The destination file doesn’t have to exist yet. It will be created when this task runs and squishes all the files together.
Both our jquery.js and carousel.js files are libraries. We most likely won’t be touching them. So, for organization, we’ll keep them in a /js/libs/ folder. Our global.js file is where we write our own code, so that will be right in the /js/ folder. Now let’s tell Grunt to find all those files and squish them together into a single file named production.js, named that way to indicate it is for use on our real live website.
concat: {
dist: {
src: [
'js/libs/*.js', // All JS in the libs folder
'js/global.js' // This specific file
],
dest: 'js/build/production.js',
}
}
Note: throughout this article there will be little chunks of configuration code like above. The intention is to focus in on the important bits, but it can be confusing at first to see how a particular chunk fits into the larger file. If you ever get confused and need more context, refer to the complete file.
With that concat
configuration in place, head over to the terminal, run the command:
grunt
and watch it happen! production.js will be created and will be a perfect concatenation of our three files. This was a big aha! moment for me. Feel the power course through your veins. Let’s do more things!
Let’s make Grunt minify that JavaScript
We have so much prep work done now, adding new tasks for Grunt to run is relatively easy. We just need to:
- Find a Grunt plug-in to do what we want
- Learn the configuration style of that plug-in
- Write that configuration to work with our project
The official plug-in for minifying code is grunt-contrib-uglify. Just like we did last time, we just run an NPM command to install it:
npm install grunt-contrib-uglify --save-dev
Then we alter our Gruntfile.js to load the plug-in:
grunt.loadNpmTasks('grunt-contrib-uglify');
Then we configure it:
uglify: {
build: {
src: 'js/build/production.js',
dest: 'js/build/production.min.js'
}
}
Let’s update that default
task to also run minification:
grunt.registerTask('default', ['concat', 'uglify']);
Super-similar to the concatenation set-up, right?
Run grunt
at the terminal and you’ll get some deliciously minified JavaScript:
That production.min.js file is what we would load up for use in our index.html file.
Let’s make Grunt optimize our images
We’ve got this down pat now. Let’s just go through the motions. The official image minification plug-in for Grunt is grunt-contrib-imagemin. Install it:
npm install grunt-contrib-imagemin --save-dev
Register it in the Gruntfile.js:
grunt.loadNpmTasks('grunt-contrib-imagemin');
Configure it:
imagemin: {
dynamic: {
files: [{
expand: true,
cwd: 'images/',
src: ['**/*.{png,jpg,gif}'],
dest: 'images/build/'
}]
}
}
Make sure it runs:
grunt.registerTask('default', ['concat', 'uglify', 'imagemin']);
Run grunt
and watch that gorgeous squishification happen: