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 taskNameor 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 taskNameor 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.initConfigparameters differently:- Grunt looks for a config that matches the taskName.
-
MultiTask can have multiple configurations referred as
this.targetand 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
--forceand it can show the stack trace if--stackis given. e.g.grunt taskName --force --stack. -
grunt.fail.fatal(error [, errorcode]): similar to
warn, displays message to STDOUT and terminate Grunt. Cannot be--forceed and it emits a beep unless--no-colorparameter 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.writelnwill do the same asgrunt.log.writebut 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
srcpathtodestpath.
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.argswill 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.initthen 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.targetwill be equal totarget1and 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
srcand 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.