In this page we walk you through the creation of a Gruntfile
that covers the usual needs of a simple project. If you already know how to set up a Gruntfile
and you're looking for a quick example, here's one:
module.exports = function(grunt) {
grunt.initConfig({
jshint: {
files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'],
options: {
globals: {
jQuery: true
}
}
},
watch: {
files: ['<%= jshint.files %>'],
tasks: ['jshint']
}
});
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('default', ['jshint']);
};
Every project has its own needs, but most of them have something in common. In this guide we introduce you to a few Grunt plugins to automate basic requirements. The final goal is to teach you how to configure these Grunt plugins so that you can use them in your projects.
For the sake of the example, let's say that you're creating a JavaScript library. The typical folder structure features the following folders: src
, dist
, and test
. The src
folder (sometimes called app
) contains the source code of the library as you author it. The dist
folder (sometimes called build
) contains the distribution, a minified version of the source code. A minified file is one where all unnecessary characters, such as spaces, new lines, comments are removed, without affecting the functionality of the source code. Minified source code is especially useful for users of the project because it reduces the amount of data that needs to be transferred. Finally, the test
folder contains the code to test the project. This set up will be used in the next sections when creating the Gruntfile
configuration.
While developing the library and releasing new versions there are a few tasks that you need to perform on a regular basis. For example, you might want to ensure that the code you write adheres to best practices, or that the code you've written doesn't result in unexpected behaviors. To do that, you can employ a tool called JSHint. Grunt has an official plugin for it called grunt-contrib-jshint which we'll adopt in this example. In particular, you might want to ensure that as you modify your code, you don't break any rules or best practices. So, a good strategy is to check the code at every change you perform. To do that, we'll cover a Grunt plugin called grunt-contrib-watch. The latter runs predefined tasks, such as grunt-contrib-jshint
, whenever files are added, changed, or deleted.
Checking that your source code follows best practices is not enough to guarantee that it's stable and doesn't contain bugs. To create a robust project, you need to test it. There are several libraries you can adopt such as QUnit or Jasmine. In this guide, we describe how to configure QUnit, and specifically grunt-contrib-qunit, to test your code.
When it comes to distributing your work, you want to offer a version as small in size as possible. To create a minified version you need a Grunt plugin like grunt-contrib-uglify. Moreover, unless the project you're developing is very small, chances are that you've split the code in multiple files. While this is a good practice for the developer, you want users to include only one file. So, before minifying the code, you should concatenate the source files to create a single one. To achieve this goal you need a Grunt plugin like grunt-contrib-concat.
To sum up, in this guide we'll use the following five Grunt plugins:
- grunt-contrib-uglify
- grunt-contrib-qunit
- grunt-contrib-concat
- grunt-contrib-jshint
- grunt-contrib-watch
If you're curious about what the final result looks like, the entire Gruntfile
can be found at the bottom of this page.
The first part is the "wrapper" function, which encapsulates your Grunt configuration.
module.exports = function(grunt) {
};
Within that function we can initialize our configuration object:
grunt.initConfig({
});
Next, we can store the project settings from the package.json
file into the pkg
property. This allows us to refer to the values of properties within our package.json
file, as we'll see shortly.
pkg: grunt.file.readJSON('package.json')
This leaves us with this so far:
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json')
});
};
Now we can define a configuration for each of the tasks we mentioned. The configuration object for a plugin lives as a property on the configuration object, that often shares the same name as its plugin. The configuration for grunt-contrib-concat
goes in the configuration object under the concat
key as shown below:
concat: {
options: {
// define a string to put between each file in the concatenated output
separator: ';'
},
dist: {
// the files to concatenate
src: ['src/**/*.js'],
// the location of the resulting JS file
dest: 'dist/<%= pkg.name %>.js'
}
}
Note how in the snippet above we refer to the name
property that's in the JSON file. We access it by using pkg.name
as earlier we defined the pkg
property to be the result of loading the package.json
file, which is then parsed to a JavaScript object. Grunt has a simple template engine to output the values of properties in the configuration object. Here we tell the concat
task to concatenate all files that exist within src/
and end in .js
.
Now let's configure the grunt-contrib-uglify
plugin, which minifies the JavaScript code:
uglify: {
options: {
// the banner is inserted at the top of the output
banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n'
},
dist: {
files: {
'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
}
}
}
This snippet tells grunt-contrib-uglify
to create a file within dist/
that contains the result of minifying the JavaScript files. Here we use <%= concat.dist.dest %>
so uglify will minify the file that the concat task produces.
Up to this point, we have configured the plugins to create the distribution version the library. It's now time to use grunt-contrib-qunit
to automate the testing of the code. To do that, we need to give to specify the location of the test runner files, which are the HTML files QUnit runs on. The resulting code is reported below:
qunit: {
files: ['test/**/*.html']
},
Once done, it's time to set up the configuration to ensure that the code of the project adheres to best practices. JSHint is a tool that can detect issues or potential issues like a high cyclomatic complexity, the use of the equality operator instead of the strict equality operator, and the definition of unused variables and functions.
We advise you to analyze with grunt-contrib-jshint
all the JavaScript files of your project, including Gruntfile
and the test files. An example of configuration of grunt-contrib-jshint
is the following:
jshint: {
// define the files to lint
files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'],
// configure JSHint (documented at http://www.jshint.com/docs/)
options: {
// more options here if you want to override JSHint defaults
globals: {
jQuery: true,
console: true,
module: true
}
}
}
This plugin takes an array of files and then an object of options. These are all documented on the JSHint site. If you're happy with the plugin defaults, there's no need to redefine them in the Gruntfile.
The last plugin left to configure is grunt-contrib-watch
. We'll use it to run the jshint
and the qunit
tasks as soon as a JavaScript file is added, deleted, or modified. When it detects any of the files specified have changed (here, we use the same files we told JSHint to check), it will run the tasks you specify, in the order they appear. This can be run on the command line with grunt watch
.
Turning the previous description into a configuration for grunt-contrib-watch
results in the snippet below:
watch: {
files: ['<%= jshint.files %>'],
tasks: ['jshint', 'qunit']
}
With this snippet, we've set up the configuration for all the plugins mentioned in the introduction. The last step to perform is to load in the Grunt plugins we need. All of these should have been previously installed through npm.
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-qunit');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-concat');
And finally set up some tasks. The most important of these tasks is the default task:
// this would be run by typing "grunt test" on the command line
grunt.registerTask('test', ['jshint', 'qunit']);
// the default task can be run just by typing "grunt" on the command line
grunt.registerTask('default', ['jshint', 'qunit', 'concat', 'uglify']);
The default task is executed when you invoke Grunt
without specifying a task to execute (grunt
).
If you've followed this guide correctly you should have the following Gruntfile
:
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
concat: {
options: {
separator: ';'
},
dist: {
src: ['src/**/*.js'],
dest: 'dist/<%= pkg.name %>.js'
}
},
uglify: {
options: {
banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n'
},
dist: {
files: {
'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
}
}
},
qunit: {
files: ['test/**/*.html']
},
jshint: {
files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'],
options: {
// options here to override JSHint defaults
globals: {
jQuery: true,
console: true,
module: true,
document: true
}
}
},
watch: {
files: ['<%= jshint.files %>'],
tasks: ['jshint', 'qunit']
}
});
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-qunit');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.registerTask('test', ['jshint', 'qunit']);
grunt.registerTask('default', ['jshint', 'qunit', 'concat', 'uglify']);
};