Sometimes you find yourself doing the same tasks again and again, especially during web development. It is time to automate repetitive tasks and use that time in more creative activities. This is where Grunt comes in. Grunt is a popular task runner that runs on NodeJS. It can minify CSS/JavaScript, run linting tools (JSHint, JSlint, CSSlint), deploy to server, and run test cases when you change a file to name a few. All the information I found about Grunt and similar Javascript test runners were too verbose and not very helpful to get started quickly. So, I decided to make this tutorial.
Beginner: Grunt.js 101
Grunt.js is a Javascript task runner. At its bare core it does file manipulation (mkdir, reads, write, copy), print messages and helper methods to organize and configure multiple tasks. It takes care of differences among Operating Systems for you. However, the real power comes in with the number of available plugins ready to use. Usually named grunt-contrib-*
. Let’s start from scratch!
Hello Wold from GruntJS
You need to install Node.js and NPM to follow along with this example.
1 | mkdir grunt101 && cd grunt101 |
If you run the grunt command you will get a message like this:
1 | grunt |
So, let’s create the Gruntfile.js
file:
1 | var grunt = require('grunt'); |
If you run grunt
again, you will see a message. The default task is run when nothing else it is specified. We are going to create a 2nd task called ‘hello’ and it is going to accept a parameter that we can pass along with the task name separated with a colon. As follows: grunt hello:adrian
. We can handle errors using grunt.warn
. Every time a grunt.warn
is found the task will stop executing, and it will give its warning message.. You can override using --force
. Try all this commands and noticed the different effects: grunt
, grunt hello
, grunt hello --force
, grunt hello:adrian
.
1 | var grunt = require('grunt'); |
We can chain multiple grunt tasks by using and array. Change the Gruntfile.js
for the following and see what will happen when you type grunt
.
1 | var grunt = require('grunt'); |
Reference 1: Grunt tasks, config and warnings
Here are some of the methods that we have used so far and some more that we will use in the next examples:
Grunt config
grunt.initConfig(configObject): Initialize a configuration object. It can be accessed by
grunt.config.get
.grunt.config.get([prop]): get the prop value from the
grunt.initConfig
. The property could be deeply nested (e.g.concat.options.dest
) and the values inside<% %>
are expanded.
Grunt tasks
- grunt.registerTask(taskName[, description], taskFunction): register a task.
- taskName: required to register the task and it allows the task to be e executed with
grunt taskName
or called by other grunt task. - description: (optional) string describing task.
- taskFunction: function which can accept parameters separated by colons (:). E.g.
grunt taskName:arg1:arg2
- taskName: required to register the task and it allows the task to be e executed with
- grunt.task.registerTask(taskName, taskList): register task.
- taskName: required to register the task and it allows the task to be e executed with
grunt taskName
or called by other grunt task. - taskList: array of taskNames to be executed, in the order specified, when the taskName is called. E.g.:
grunt.registerTask('concatAll', ['concat:templates', 'concat:javascripts', 'concat:stylesheets']);
- taskName: required to register the task and it allows the task to be e executed with
- grunt.registerMultiTask(taskName[, description], taskFunction): multi-tasks accepts the same parameters as
grunt.registerTask
. However, it readsgrunt.initConfig
parameters differently:- Grunt looks for a config that matches the taskName.
- MultiTask can have multiple configurations referred as
this.target
and the value asthis.data
. - All the “targets” are run if it is not specified otherwise.
1 | grunt.initConfig({ |
You can specify one target grunt print:hello
or run all them grunt print
which will produce this output:
1 | Running "print:target1" (print) task |
Grunt Errors and Warnings
grunt.fail.warn(error [, errorcode]): prints to STDOUT a message and abort grunt executions. It can be override using
--force
and it can show the stack trace if--stack
is given. e.g.grunt taskName --force --stack
.grunt.fail.fatal(error [, errorcode]): similar to
warn
, displays message to STDOUT and terminate Grunt. Cannot be--force
ed and it emits a beep unless--no-color
parameter is passed. It also accepts--stack
. E.g.grunt taskName --no-color --stack
.
Example: Forex and grunt multiple async calls handling
The idea is get conversion rates from a base currency (e.g. USD) to a target currency (e.g. EUR). We are using a registerMultiTask
, so the taskName ‘currency’ matches its property in the config.init
. Notice that we can has additional arbitrary data such as endpoint URL.
Async calls can be a little tricky in Javascript. We are going to do multiple HTTP request. Since http.get
is async Grunt will finish the task before even receiving any response. this.async()
solves the issue, we just need to call it when we are done.
1 | module.exports = function(grunt){ |
Reference 2: Grunt Files and logs
Grunt logs
All them stars with the prefix grunt.log
and accepts a msg
which is displayed to STDOUT (usually the screen). Here are the differences between them:
- writeln([msg]), write(msg) and subhead(msg): writes message to STDOUT.
grunt.log.writeln
will do the same asgrunt.log.write
but without trailing newline.subhead(msg)
will print the message in bold and proceeded by a newline and a trailing newline as well.
The following methods adds a “>>” before the message in the screen which could be of different colors depending on the method:
grunt.log.error([msg])
: print message prefixed with a RED “>>”.grunt.log.ok([msg])
: print message prefixed with a GREEN “>>”.
Grunt files
Files
All has an optional attributes options
that could be encoding
among others.
- grunt.file.write(filepath, contents [, options]): writes contents to file, creates path if necessary.
- grunt.file.read(filepath [, options]): returns file content.
- grunt.file.readJSON(filepath [, options]): reads file content and parse it to JSON.
- grunt.file.delete(filepath [, options]): deletes files recursively.
- grunt.file.copy(srcpath, destpath [, options]): copy file from
srcpath
todestpath
.
Directories
- grunt.file.mkdir(dirpath [, mode]): creates directory and any intermediary. Like
mkdir -p
. - grunt.file.expand([options, ] patterns): returns an array with all the files matching a pattern. It can also accept and array of patterns. Preceding a patter with
!
will negate them. E.g.['**/*.js', !**/*spec.js]
=> get all javascript (including subdirectories) but NOT the ones that ends with spec.js. - grunt.file.recurse(rootdir, callback): expand path and return a callback function with the following signature
callback(abspath, rootdir, subdir, filename)
.
Example 2: Gruntfile for files manipulation
GruntJS comes with built-in functions for basic file system handling. To see the function in action. Create four directories: stylesheets
, javascripts
, templates
and put files on first three. The idea is to concatenate all the files into one index.html and placed it a newly created public
folder.
Here’s the grunt file that will copy and concatenate all the files for us:
1 | module.exports = function(grunt){ |
A more complete example can be found in the repository where we have the join and open function as well.
Reference 3: Inside Grunt tasks
Inside all Grunt task there are number of functions available through this
:
- this.async: designed for async tasks. Grunt will normally end the task without waiting for the callback to be executed. If you need Grunt to wait use
done()
.
1 | var done = this.async(); |
this.requires: list of taskNames that should executed successfully first. E.g.
this.requires(['concat', 'jshint'])
.this.name: this is the name of the task. E.g.
grunt hello
, thenthis.name === 'name'
.this.args: returns an array with the parameters. E.g.
grunt hello:crazy:world
, thenthis.args
will return['crazy', 'world']
.this.options([defaultsObj]): it gets options values from the
config.init
, optionally you can also pass an object containing the default values. Notice in the example below that even though console.log has athis.options({gzip: true})
it gets override by the options parameters. If not one it is specified in theconfig.init
then it will use the default gzip: true.
Inside MultiTasks
Consider this grunt.config.init
example:
1 | module.exports = function(grunt){ |
1 | grunt multiTaskName |
this.target: name of the target current target. If you call it
grunt multiTaskName
, it will run like multiple tasks calling each target one at a time.this.target
will be equal totarget1
and thentarget2
.this.files: return a (single) array that has all the properties for the current target. Take a look the the output above.
this.filesSrc: it expands files and paths against
src
and return an array with them.this.data: contains the raw data of the target parameters.
Intermediate: Using Grunt.js plugins
Chances are that there is a plugin for most of your needs. Last time I checked there were 3,638 plugins for grunt. This are the 10 most popular:
Installing a grunt plugin
Let’s say we want to install jshint.
- Get the plugin module
Download it from npm:
npm install grunt-contrib-jshint --save-dev
or from github:
npm install https://github.com/YOUR_USERNAME/grunt-contrib-YOUR-PLUGIN --save-dev
- Load it in your Gruntfile
grunt.loadNpmTasks('grunt-contrib-jshint');
or
grunt.loadNpmTasks('grunt-contrib-YOUR-PLUGIN');
10 most popular grunt plugins
1- jshint: Validate files with JSHint. Uses .jshintrc
to settings.
1 | { |
2- watch: Run predefined tasks whenever watched file patterns are added, changed or deleted. Spawn runs task in a child process but having set to spawn: false
is faster.
1 | watch: { |
3- uglify: minifies javascript files.
1 | uglify: { |
4- clean: Clean files and folders.
1 | clean: { |
5- concat: Concatenate files.
1 | concat: { |
1 | pkg: grunt.file.readJSON('package.json'), |
6- cssmin: Compress CSS files.
1 | cssmin: { |
1 | cssmin: { |
7- connect: runs server as long as Grunt is running. It can be persistent passing keepalive
like this grunt connect:keepalive
.
1 | connect: { |
8- karma: runs karma testing tool.
1 | karma: { |
1 | karma: { |
9- less: Compile LESS files to CSS.
1 | less: { |
10- concurrent: Run grunt tasks concurrently.
1 | concurrent: { |
In the next blog post, we will continue the tutorial with using GruntJS in a web application, making your own plugins and a comparison between other task runners tools such as Gulp, Gulp, Brunch, Rake::Pipeline and Broccoli.