Grunt is a way to automate many of the tasks you’re doing when you develop. Are you still minifying, compiling, concatenating, compressing, prefixing all your code manually? Nonsense poopy pants! In this tutorial I will show you how Grunt will do all this for you, and then some!

Why use a task runner? In one word: automation. The less work you have to do when performing repetitive tasks like minification, compilation, unit testing, linting, etc, the easier your job becomes.

- Grunt.js

The basic idea

The most basic Grunt setup consists of two required files: Gruntfile.js and package.json. After you’ve set up the basic structure, you customize Grunt to fit your needs with the help of plugins. A plugin can help you concatenate all your JS-files, another plugin might run your JS-code through JSHint. Here’s a full list of Grunt plugins.

Package.json is the file where you store npm metadata about the project (name, version, homepage, file dependencies, directories). It’s in this file you list all of your Grunt plugins that you are going to use.

The Gruntfile.js is where the magic happens. In here, you’ll setup all your Grunt plugins and what tasks that should be available. Grunt tasks are a way to separate the usage of plugins. You might want to run some plugins during the development phase of a project, and some when you’re going push the code to production.


Installing Grunt

First of all, you need Node.js, which you can install here. After that, you install the Grunt CLI with this command:

npm install -g grunt-cli

That’s it, you’re all set! The next step is to configure your package.json file.

Configuring your package.json

Your package.json and Gruntfile.js should always be on your projects root folder. This is a basic structure for the package.json file:

{
// Project name
"name": "GruntSetup",

// Version
"version": "0.1.0",

// Description
"description": "My Grunt setup",

// List of plugins that we are using, and what version
"devDependencies": {
// Required to run Grunt tasks
"grunt": "~0.4.1",
// SASS compiler
"grunt-contrib-sass": "~0.3.0"
}
}

The neat thing about devDependencies is that you can easily install all the plugins with just one command: npm install, that really comes in handy if someone else starts on your project.

Setting up Gruntfile.js

Aww yiss, here’s where the fun start. The structure of a Gruntfile.js consists of 3 parts.
Project configuration, loading plugins and task registering. Below is a Gruntfile for the package.json file we created earlier. The SASS plugin below, called grunt-contrib-sass will compile our SASS into CSS, with a few options like if it should be compressed or expanded etc.

module.exports = function(grunt) {

// Configuration of the project and plugins
grunt.initConfig({
  pkg: grunt.file.readJSON('package.json'),
    sass: {
    // This will be executed when we run the 'development' task below
    development: {
      options: {
        style: 'expanded'
      },
      files: {
        'css/style.css': 'css/style.sass'
      }
    },
    // This will be executed when we run the 'deploy' task below
    deploy: {
      options: {
        style: 'compressed'
      },
      files: {
        'css/style.css': 'css/style.sass'
      }
    }
  },
});

// Load the plugin that provides the "sass" task.
grunt.loadNpmTasks('grunt-contrib-sass');

// Our tasks
grunt.registerTask('development', ['sass:development']);
grunt.registerTask('deploy', ['sass:deploy']);

};

To run one of these tasks, we simply type this into our terminal:

grunt development
// or
grunt deploy

Alrighty. Now we have set up a really, really simple Grunt plugin that compiles our SASS file. We’ve also created two different tasks; development will compile our SASS into a .css file that’s expanded. Our production task will do the same thing, but since we’re good developers, we will compress the outputted CSS.

But wait. Why should I use this when Codekit, Livereload, Crunch, Compass.app, Tamagotchi and your microwave can compile SASS just as good? Because Grunt can do so much more than just compiling good ‘ol boring SASS.


Making Grunt awesome

Time to do what Grunt does best, a silly amount of things super automatically. There’s oodles and oodles of plugins that can help you in your project, and to install them you just follow the above Gruntfile.js example and customize your own Grunt setup.

Below I’ll list some of my favorite plugins and end with putting them all together.

Compress images

Image compression is all the rage right now, with responsive design and page weight reduction. So why not use a plugin that does that for you? There’s a few plugin that does this for you, but one of the better ones is grunt-contrib-imagemin which compresses both .png and .jpg for you.

The basic setup is simple, for mode advanced configuration, see the documentation

imagemin: {
  png: {
    options: {
      optimizationLevel: 7 //Compression level
    },
    files: [{
      expand: true, //Dynamic expansion
      cwd: 'your/image/root/folder',
      src: ['your/image/root/folder/*.png'],
      dest: 'your/compressed/image/folder',
      ext: '.png'
    }]
  },
  jpg: {
    options: {
    progressive: true //Lossless or progressive conversion
    },
    files: [{
      expand: true,
      cwd: 'your/image/root/folder',
      src: ['your/image/root/folder/*.jpg'],
      dest: 'your/compressed/image/folder',
      ext: '.jpg'
    }]
  }
}
grunt.loadNpmTasks('grunt-contrib-imagemin');

Remember to also add the plugin into your package.json.

Concatenate

If you’re writing Javascript with a modular pattern, or something else fancy McPantsy. You’ve probably ended up with a bunch of .js files that needs not be concatenated, and sweet baby Jesus that’s a boring task to do. But no fear, grunt-contrib-concat is here!

concat: {
  dev: {
    // The /**/*.js means to look for .js files in all subfolders too
    src: 'path/to/js/root/**/*.js',
    dest: 'path/to/dest/js/main.js'
  }
}
grunt.loadNpmTasks ('grunt-contrib-concat');

Uglify

When you release to your production environment it’s a good idea to uglify (compress) your Javascript to reduce the page weight. grunt-contrib-uglify will help you with that.

uglify: {
  options: {
    mangle: false //To prevent changes to your variable and function names
  },
  my_target: {
    files: {
      'path/to/compressed/script.js': 'path/to/original/script.js'
    }
  }
}
grunt.loadNpmTasks ('grunt-contrib-uglify');

As always, remember to add the plugin into your package.json.

CSS auto prefixer

Thanks to Netscape not monetizing the web in the 90’s, we have vendor prefixes. And it’s a real headache to keep up with that property needs which prefix. But grunt-autoprefixer takes that off your mind! It’s connected to the database of Can I Use so it’s always updated with the latest data.

autoprefixer: {
  dev: {
    options: {
      // What latest browsers it should support,
      // Global usage of more than 1%,
      // What IE versions
      browsers: ['last 3 versions', '> 1%', 'ie 8', 'ie 7']
    },
    files: {
      'path/to/original/style.css': ['path/to/prefixed/style.css']
    }
  }
}
grunt.loadNpmTasks ('grunt-autoprefixer');

The almighty watch plugin

This plugin is what make Grunt go from good to holy guacamole awesome. Grunt-contrib-watch listens to changes in certain files or directories, and when a change is detected it runs a certain task. This makes a great wrapper for the previous plugins mentioned. Let’s do a simple example and combine it with the autoprefixer task we just went through:

// watch serves as a kind of wrapper
  watch: {
    autoprefixer: {
      // When this file changes
      files: ['path/to/style.css'],
      // Run this task
      tasks: ['autoprefixer:dev']
    }
  },
  autoprefixer: {
    dev: {
      options: {
        browsers: ['last 3 versions', '> 1%', 'ie 8', 'ie 7']
      },
      files: {
        'path/to/original/style.css': 'path/to/prefixed/style.css'
      }
    }
  }
grunt.loadNpmTasks ('grunt-contrib-watch');
grunt.loadNpmTasks ('grunt-autoprefixer');

So, when change something in our style.css, the watch plugin will detect that automatically and run the autoprefixer’s dist task. You can basically have a watch-task for anything. Added a new image to your image folder? Have watch run the imagemin task we added.


Putting it all together

So, we’ve went through a few good plugins. Let’s combine them all together with a neat watch task.

We’ll start with the package.json:

{
"name": "GruntSetup",
"version": "0.1.0",
"description": "My Grunt setup",
"devDependencies": {
"grunt": "~0.4.1",
"grunt-contrib-watch": "~0.4.4",
"grunt-contrib-uglify": "~0.2.0",
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-sass": "~0.3.0",
"grunt-autoprefixer": "~0.2.0",
"grunt-contrib-imagemin": "~0.2.0"
}
}

And here’s the complete Gruntfile.js

module.exports = function(grunt) {
  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),

    watch: {
      sass: {
        files: ['path/to/sass/directory/**/*.sass'],
        tasks: ['sass:dev']
      },
      concat: {
        files: ['path/to/js/directory/**/*.js'],
        tasks: 'concat'
      },
      imagemin: {
        files: ['path/to/image/directory/**/*.jpg', 'path/to/image/directory/**/*.png'],
        tasks: 'imagemin'
      },
      autoprefixer: {
        files: ['path/to/css/directory/style.css'],
        tasks: ['autoprefixer:dist']
      }
    },
    sass: {
      dev: {
        options: {
          style: 'expanded',
          trace: true
        },
        files: {
        'path/to/css/directory/style.css': 'path/to/sass/directory/style.sass'
        }
      },
      dist: {
        options: {
          style: 'compressed'
        },
        files: {
          'path/to/css/directory/style.css': 'path/to/sass/directory/style.sass'
        }
      }
    },
    concat: {
      dev: {
        src: 'path/to/js/root/**/*.js',
        dest: 'path/to/dest/js/main.js'
      }
    },
    autoprefixer: {
    dev: {
      options: {
        browsers: ['last 3 versions', '> 1%', 'ie 8', 'ie 7']
      },
      files: {
        'path/to/original/style.css': 'path/to/prefixed/style.css'
      }
    }
    },
    uglify: {
      options: {
        mangle: false
      },
      my_target: {
        files: {
        'path/to/compressed/script.js': 'path/to/original/script.js'
        }
      }
    },
    imagemin: {
      png: {
        options: {
          optimizationLevel:
        },
        files: [{
          expand: true,
          cwd: 'your/image/root/folder',
          src: ['your/image/root/folder/*.png'],
          dest: 'your/compressed/image/folder',
          ext: '.png'
        }]
      },
      jpg: {
        options: {
          progressive: true
        },
        files: [{
          expand: true,
          cwd: 'your/image/root/folder',
          src: ['your/image/root/folder/*.jpg'],
          dest: 'your/compressed/image/folder',
          ext: '.jpg'
        }]
      }
    }

  });

  grunt.loadNpmTasks ('grunt-bump');
  grunt.loadNpmTasks ('grunt-contrib-uglify');
  grunt.loadNpmTasks ('grunt-contrib-concat');
  grunt.loadNpmTasks ('grunt-autoprefixer');
  grunt.loadNpmTasks ('grunt-contrib-sass');
  grunt.loadNpmTasks ('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-imagemin');

  // Using the 'grunt development' commando will autoprefix, compile sass, concatenate and activate the watch task
  grunt.registerTask('development', ['autoprefixer:dist', 'sass:dev', 'concat', 'watch']);
  // The production task will autoprefix, compile sass and compress the outputted CSS, concatinate JS, compress it, and compress images
  grunt.registerTask('production', ['autoprefixer:dist', 'sass:prod','concat', 'uglify', 'imagemin']);
};

Our development task is what we’re gonna run during development. When you run the task, it will begin with prefix your CSS, compress your SASS, concatenate the JS-files and finally start the watch task. So you only run this command once, and the watch task takes care of the rest for you.

When we’re done writing our fabulous code and it’s release time, we simply run our production task, which does almost the same thing. In addition to our development task it will also compress the code and the images. And since production means no further development, we don’t need the watch task for that.


Final thoughts

As you can see, Grunt is highly powerful and highly customizable. You’ll probably end up using the same base of plugins through your projects, but don’t forget to go through the full list of plugins. You might find some that fits your current project perfectly. For more information about Grunt, check out the official documentation.