Grunt, simply defined, is a task runner built over Node.js that can be used to automate certain tasks in almost any project, in any language. Grunt and Grunt plugins are installed and managed via npm.
If you’re unaware, npm is a package manager that provides a central repository for custom open source modules for Node.js and JavaScript. npm makes it simple to manage modules versions and distribution. For this tutorial project, we used the npm install command to install the required module.
Why use Grunt?
In one word? Automation.
Grunt helps you in performing repetitive tasks like:
- Minification
- Compilation
- Unit testing
- Linting and more
Installing Grunt CLI
In order to get more productive with Grunt, you will need to install Grunt’s command line interface (CLI) globally. Run the following command:
npm install -g grunt-cli
This will put the grunt command in your system path, allowing it to be run from any directory. Note that installing grunt-cli does not install the Grunt task runner! The job of the Grunt CLI is to run the version of Grunt which has been installed next to a Gruntfile. This allows multiple versions of Grunt to be installed on the same machine simultaneously.
How to Use Grunt in Your Project
For a how-to example. let’s build one empty project from scratch and configure Gruntin it.
Create a Project Root Directory
First, create an empty project root directory and create the following files in it:
- package.json: This file is used by npm to store metadata for projects published as npm modules. We will list Grunt and the Grunt plugins that we need in our project as devDependencies in this file. This file needs to be in the root directory of the project. You can also use npm init command to generate this file.
- Gruntfile.js: This file is used to configure or define tasks as well as load Grunt plugins. The Gruntfile.js file also needs to be in the root directory of the project. You can also name the file as Gruntfile.coffee if you need to configure tasks in coffee script. We will discuss the contents of this file in detail in an upcoming section of this article.
Add the Grunt module to your project
Next we need to add the Grunt module to our project via npm. Let’s run the following command to install the module:
> npm install grunt --save-dev
This will install Grunt and also make an entry in the package.json mentioning this module as a devDependency. Modules marked as devDependencies are ones that are only installed in the development environment; in production environments they are ignored.
Understanding the Gruntfile
The Gruntfile is a valid JavaScript or CoffeeScript file that belongs in the root directory of your project next to the package.json file, and should be committed with your project source. A Gruntfile is comprised of the following parts:
- The “wrapper” function
- Project and task configuration
- Loading Grunt plugins and tasks
- Custom tasks
Let us discuss each of these in detail.
About the “Wrapper” Function in a Gruntfile
The wrapper function is nothing but a function assigned to your module exports that encapsulates all the Grunt code. This function is called by the Grunt engine and serves as the entry point for Grunt configurations. Hence, our Gruntfile at this point looks like the text below:
module.exports = function(grunt) {
// Do grunt-related things in here
};
Every Gruntfile and Grunt plugin uses this basic format, and all of your Grunt code must be specified inside this function.
Project and Task Configuration in a Gruntfile
Most Grunt tasks rely on configuration data defined in an object passed to the grunt.initConfig method. That’s why we need to specify configurations as desired by the plugins we use. We can also specify our own configurations which we intend to use. Lets define some random configurations:
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
//Lets read the project package.json
pkg: grunt.file.readJSON('package.json'),
//Some other configs
someKey: 'Some Value',
author: 'Modulus.io'
});
};
Grunt also have several helper methods; one of them is reading files. Hence we used grunt.file.readJSON to read our package.json file and store its parsed object in the pkg key. You can also refer the config values in your strings as variables, making your config strings dynamic. Here’s an example below:
module.exports = function (grunt) {
// Project configuration.
grunt.initConfig({
//Lets read the project package.json
pkg: grunt.file.readJSON('package.json'),
//Some other configs
someKey: 'Some Value',
author: 'Modulus.io',
filePostfix: '-<%= pkg.name %>-<%= pkg.version %>',
greetingMessage: 'Running Grunt for project: <%= pkg.name %>, Version: <%= pkg.version %>, Author: <%= author %>'
});
};
Here we used the <%= varName %> notation to specify dynamic text in strings. We can refer the config values directly here as demonstrated in the example above.
Loading Grunt Plugins and Tasks
Many commonly used tasks like concatenation, minification and linting are available as Grunt plugins. As long as a plugin is specified in package.json as a dependency and has been installed via npm install, it may be enabled inside your Gruntfile.
Let’s install a simple plugin, grunt-clean, into our project and try to configure that in our Gruntfile. Install the module via npm by running the command:
> npm install grunt-contrib-clean --save-dev
Now we’ll load the task in our Gruntfile via the grunt.loadNpmTask(‘pluginName’)method and do some configurations as required. Now our Gruntfile reads as shown below:
module.exports = function (grunt) {
// Project configuration.
grunt.initConfig({
//Lets read the project package.json
pkg: grunt.file.readJSON('package.json'),
//Some other configs
someKey: 'Some Value',
author: 'Modulus.io',
filePostfix: '-<%= pkg.name %>-<%= pkg.version %>',
greetingMessage: 'Running Grunt for project: <%= pkg.name %>, Version: <%= pkg.version %>, Author: <%= author %>',
//Configure Clean Module
clean: ['.tmp', 'dist', 'npm-debug.log']
});
// Load the plugin that provides the "clean" task.
grunt.loadNpmTasks('grunt-contrib-clean');
};
In the above example, we configured clean to delete the .tmp and dist directories as well as the npm-debug.log file in the project root. You can specify any number of files/dirs there. Each plugin has documentation specifying its necessary configurations and format.
Tip: The grunt –help command will list all available tasks.
Now, try to run the following command in your project root:
> grunt clean
This command will delete the specified files and directories you configured.
Specifying Subtasks in Gruntfile
You can also configure sub tasks as they are supported by most Plugins. To understand this concept, let’s review the following example:
module.exports = function (grunt) {
***javascript*** // Project configuration. grunt.initConfig({ //Lets read the project package.json pkg: grunt.file.readJSON('package.json'), //Some other configs someKey: 'Some Value', author: 'Modulus.io', filePostfix: '-<%= pkg.name %>-<%= pkg.version %>', greetingMessage: 'Running Grunt for project: <%= pkg.name %>, Version: <%= pkg.version %>, Author: <%= author %>', //Configure Clean Module clean: { npm: 'npm-debug.log', temp: ['temp', '.tmp'], dist: ['dist', 'out/dist'] } }); // Load the plugin that provides the "clean" task. grunt.loadNpmTasks('grunt-contrib-clean'); };
Here during configurations we specified an object with different keys including npm, temp and dist. These are random names we came up with in order to divide the clean task into subtasks. Now we can run subtasks to delete all mentioned files/dirs or delete a subgroup. This is demonstrated below:
#This will delete all files, npm-debug.log, temp, .tmp, dist, out/dist > grunt clean
#This will delete only npm-debug.log > grunt clean:npm
#This will delete only temp, .tmp > grunt clean:temp
#This will delete only dist, out/dist > grunt clean:dist
In the previous example, we specified the key we provided in the config to run the subtask. The syntax to use for writing the key is:
> grunt TaskName:SubTaskKeySpecifiedInConfig
Custom Tasks
You can define tasks with custom names, which can be a combination of existing tasks or purely your own implementation. If you are implementing custom tasks, you must implement them in JavaScript.
Let’s try to define a task which is combination of existing tasks and a pure implementation by us.
module.exports = function (grunt) {
// Project configuration.
grunt.initConfig({
//Lets read the project package.json
pkg: grunt.file.readJSON('package.json'),
//Some other configs
someKey: 'Some Value',
author: 'Modulus.io',
filePostfix: '-<%= pkg.name %>-<%= pkg.version %>',
greetingMessage: 'Running Grunt for project: <%= pkg.name %>, Version: <%= pkg.version %>, Author: <%= author %>. ',
//Configure Clean Module
clean: {
npm: 'npm-debug.log',
temp: ['temp', '.tmp'],
dist: ['dist', 'out/dist']
}
});
// Load the plugin that provides the "clean" task.
grunt.loadNpmTasks('grunt-contrib-clean');
//Lets register a basic task.
grunt.registerTask('print-info', 'Lets print some info about the project.', function() {
grunt.log.write(grunt.config('greetingMessage')).ok();
});
//Specify a custom task which is combination of tasks.
grunt.registerTask('my-clean', ['print-info', 'clean:dist', 'clean:npm']);
};
In this latest example above, we created a custom task print-info, and executed some JavaScript in it. We used the grunt.registerTask() method to do so. We also defined a custom task my-clean which is combination of other tasks. Because you can run any JavaScript in your tasks, the possibilities are endless.
Run the following command to see what tasks are available:
> grunt --help
You should see the two custom tasks specified. Now you can run your my-clean task:
> grunt my-clean
Default Task
When you just run grunt in your project root without any tasks specified, then it looks for the default task and runs it. You can specify the default task by simply issuing the command:
> grunt
To specify a default task we simply register a task named default. Let’s do that; now our Gruntfile looks like this:
module.exports = function (grunt) {
***javascript*** // Project configuration. grunt.initConfig({ //Lets read the project package.json pkg: grunt.file.readJSON('package.json'), //Some other configs someKey: 'Some Value', author: 'Modulus.io', filePostfix: '-<%= pkg.name %>-<%= pkg.version %>', greetingMessage: 'Running Grunt for project: <%= pkg.name %>, Version: <%= pkg.version %>, Author: <%= author %>. ', //Configure Clean Module clean: { npm: 'npm-debug.log', temp: ['temp', '.tmp'], dist: ['dist', 'out/dist'] } }); // Load the plugin that provides the "clean" task. grunt.loadNpmTasks('grunt-contrib-clean'); //Lets register a basic task. grunt.registerTask('print-info', 'Lets print some info about the project.', function() { grunt.log.write(grunt.config('greetingMessage')).ok(); }); //Specify a custom task which is combination of tasks. grunt.registerTask('my-clean', ['print-info', 'clean:dist', 'clean:npm']); //Specify a default task grunt.registerTask('default', ['my-clean', 'clean:temp']); };
Gruntfile Tutorial Summary
In this article we talked about the basics of Grunt and how to use and configure the Gruntfile. We saw how we can define Gruntfile configurations and use them within strings and custom tasks. We also saw the use of multi-tasks (subtasks) and how to configure them.