Automate your development tasks using Grunt

Grunt.js

Grunt is an extremely useful Node.js package to automate lots of development and continuous integration tasks. The Grunt eco-system has lots of packages available on npm. This enables us to quickly setup our development/continuous integration environment.

Grunt tasks mostly have two required properties. An files array, which is used to configure on what files the tasks is executed, and an options property which configures some task specific settings. The files array supports the globbing and minimatch pattern to match files based on the provided expression.

So what tasks could you use for your projects, or for which project can you use Grunt? How do I configure Grunt tasks? How do I execute them? All these questions I try to answer for you in this article.

If you are working on Node.js related projects, Grunt perfectly suites the job to do some tasks on your javascript. For example listing the javascript using jshint, execute unittests, do some static code analysis etc.

Also for other web development kind of projects Grunt perfectly fits the job. Example minify JavaScript, compile less files to CSS, minify images and/or replace small image urls in your CSS by base64 encoded images.

Still we didn’t reached the top of the iceberg. If you are working on .NET related projects grunt can also be used. There are tasks in the npm registry to compile your code using MSBuild, execute nunit and mspec tests and calculate code coverage using open cover etc. Grunt simplifies configuring the tasks and executing them.

So what if there is no Grunt tasks for the thing you would like to do. There is also a package which can execute commandline/shell jobs. So if you just want to execute some commandline/shell jobs, just use this package to execute these commandline tools more easily.

With a little bit of javascript knowledge you can easily write your own Grunt task. That’s what I did when I wrote the grunt-dotnet-mspec and the grunt-dotnet-codecoverage tasks. Both are available on Github (not yet implemented the full feature set) and I accept pull requests enhancing the grunt tasks. In this blogpost I won’t zoom in on writing your own Grunt tasks.

So enough chitchat for now. Lets provide you a small example gruntfile with an example configuration for tasks.

gruntfile.js
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
module.exports = function(grunt) {
'use strict';

grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),

meta: {
banner: '/*! <%= pkg.name %> - v<%= pkg.version %> <%= grunt.template.today("yyyy-mm-dd") %> */'
},

secret: grunt.file.readJSON('secret.json'),

jshint: {
options: {
jshintrc: '.jshintrc'
},
gruntfile: ['Gruntfile.js'],
serverside: ['server/src/**/*.js', 'server/*.js'],
clientside: ['js/cors/*.js', 'js/*.js', '!js/thirdparty/**'],
tests: ['test/**/*.js']
},

concat: {
options: {
stripBanners: true,
banner: '<%= meta.banner %>'
},
javascript: {
files: {
'build/js/myawesomeapplication.js': [
'js/thirdparty/jquery.iframe-transports.js', 'js/app.js', 'js/validation.js',
'js/controller.js', 'js/router.js', 'js/modules/**/*.js'
]
},
nonull: true
}
},

uglify: {
options: {
banner: '<%= meta.banner %>',
compress: {
drop_console: true,
sequences: true, // join consecutive statemets with the “comma operator”
properties: true, // optimize property access: a["foo"] → a.foo
dead_code: true, // discard unreachable code
drop_debugger: true, // discard “debugger” statements
unsafe: false, // some unsafe optimizations (see below)
conditionals: true, // optimize if-s and conditional expressions
comparisons: true, // optimize comparisons
evaluate: true, // evaluate constant expressions
booleans: true, // optimize boolean expressions
loops: true, // optimize loops
unused: true, // drop unused variables/functions
hoist_funs: true, // hoist function declarations
hoist_vars: false, // hoist variable declarations
if_return: true, // optimize if-s followed by return/continue
join_vars: true, // join var declarations
cascade: true, // try to cascade `right` into `left` in sequences
side_effects: true, // drop side-effect-free statements
warnings: true
},
report: 'gzip'
},
javascript: {
files: {
'build/js/myawesomeapplication.min.js': ['build/js/myawesomeapplication.js']
}
}
}
});

grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');

grunt.registerTask('default', ['']);
grunt.registerTask('minify', ['concat', 'uglify']);
};

In this blogpost I want go into detailed explanation of the specific Grunt tasks since they are pretty well explained on npm and Github you could find detailed information for these task configurations over there.

So how do we execute grunt tasks?

A Grunt task can be executed using grunt-cli which you can install globally using npm.

1
npm install -g grunt-cli

Then you can execute all grunt tasks on your shell (NodeJS commandline on Windows) using following command.

1
grunt taskname

This executes the specified task based on the options and files provided in your gruntfile. A task can also have multiple targets so you can execute the task with different options or on different files. The specific target can be specified by appending it to the taskname using a colon.

1
grunt taskname:targetname

So as an example on the above provided gruntfile we could execute following tasks or a task its specific target.

grunt jshint

This executes all the jshint tasks configured.

1
grunt mspec:backend

That sounds cool right? So now we can execute preconfigured tasks manually by keying in some simple commands on the commandline. Can we take this one step further? We can also define new tasks in our gruntfile to execute multiple tasks at once. For example a ci task which executes all the tasks required for our continuous integration. This gives us the ability to execute multiple tasks at once by calling only one task.

Example:

1
grunt.registerTask('ci', ['jshint', 'concat', 'uglify', 'less', 'cssmin']);

Can be executed as follow…

1
grunt ci

… for example on your Jenkins server automatically every time your Jenkins server gets new code pushed. Or locally every time you want to run all of these tasks. Ofcourse this is not ideal on you local machine since a lot of files change regularly and you don’t want to execute the specific tasks everytime manually when you change something. That why we also want to automate this, so therefore we can use the grunt-contrib-watch task.

Grunt-contrib-watch enables us to watch our files for changes and execute the other grunt jobs automatically. Example: lint your javascript on changes to .js files or compile your LESS files to CSS on changes to your .less files.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module.exports = function(grunt) {
'use strict';
grunt.initConfig({
//other tasks left for brevity
watch: {
scripts: {
files: 'js/**/*.js',
tasks: ['jshint', 'mocha:unittest', 'phantom']
},
less: {
files: 'less/**/*.less',
tasks: ['less']
},
styles: {
files: 'css/**/*.css',
tasks: ['cssmin']
}
}
});

grunt.loadNpmTasks('grunt-contrib-watch');
};

So now you know the whats and hows about Grunt I would like to challenge you to share your personal experiences and preferred Grunt packages using a comment on this article. This way you help the other readers by adding value to this article. Thanks for reading and have fun playing around with Grunt.