Chapter 3. Using Grunt

Now that we've installed and configured Grunt, we're ready to use it. In this chapter, we'll review creating our own tasks and cover the finer points omitted in previous chapters. We shall also cover the various methods of executing tasks over and above simply running Grunt on the command-line. Finally, we will cover how to choose the most appropriate tasks for the job and exactly how to integrate them into our Grunt build.

Creating your own tasks

In this section, we shall explore the creation of Grunt tasks in more detail, specifically normal tasks and multitasks, and asynchronous tasks.

Tasks

As exemplified previously, creating tasks is extremely simple. We provide a name and a function to grunt.registerTask and we're ready to execute. Tasks (as opposed to multitasks) are best suited to build processes that will only be performed once in a given build. A real world example of such a process might be to update a deployment log file, which we could run whenever we deploy, providing a simple history of deployments for future reference. This task might look like:

//Code example 01-deploy-log-task
var fs = require('fs'),
module.exports = function(grunt) {

  grunt.registerTask('log-deploy', function() {
    var message = 'Deployment on ' + new Date();
    fs.appendFileSync('deploy.log', message + '
'),
    grunt.log.writeln('Appended "' + message + '"'),
  });

};

Note

See the Node.js API documentation for more information on each built-in module: http://gswg.io#node-api.

On the first line, we are requiring (or importing) the built-in Node.js file system module: fs. Then, inside our log-deploy task, we'll use the fs.appendFileSync method which will append arbitrary text to a given file (first creating the file, if it doesn't exist). When we run this task, it should create a deploy.log file and display:

$ grunt log-deploy
Running "log-deploy" task
Appended "Deployment on Wed Aug 28 2013 20:43:54 GMT+1000 (EST)"

Done, without errors.

The task object

We can access the properties of the task currently running via the grunt.current.task object. When tasks are executed, the current task object is used as the function context, where it may also be accessed via the JavaScript this operator.

The task object has the following properties:

  • name – a string set to the task name (the first parameter provided to grunt.registerTask).
  • async – a function which notifies Grunt that this task is asynchronous and returns a callback. There is more on this in the Asynchronous tasks section.
  • requires – a function which accepts an array of task names (strings), then ensures that they have previously run. So, if we had a deploy task we might use this.requires(["compile"]), which will ensure we have compiled our code before we deploy it.
  • requiresConfig – an alias to the grunt.config.requires function, briefly touched on in Chapter 2, Setting Up Grunt. This function causes the current task to fail if a given path configuration property does not exist.
  • nameArgs – a string set as the task name including arguments used when running the task.
  • args – an array of all arguments used to run the task.
  • flags – an object which uses each of the args as its keys and true as the value. This allows us to use the arguments as a series of switches. So, if we ran the task foo with grunt foo:one:two, then this.flags.two would be true but this.flags.three would be undefined (which is falsy).
  • errorCount – a number representing the number of calls to grunt.log.error.
  • options – a function used to retrieve the task's configuration options which is functionally equivalent to grunt.config.get([this.name, "options"]). However, in the next section on multitasks, the options function becomes more useful.

The following is a simple example demonstrating the use of the task object:

//Code example 02-task-object
module.exports = function(grunt) {

  grunt.registerTask('foo', function() {
    console.log('My task "%s" has arguments %j',
                this.name, this.args);
  });

};

Now, if we run this task with grunt foo:bar:bazz we should see:

$ grunt foo:bar:bazz
Running "foo:bar:bazz" (foo) task
My task "foo" has arguments ["bar","bazz"]

Done, without errors.

For more information on the task object, refer to http://gswg.io#grunt-task-object, and for more information on the this operator in JavaScript, refer to http://gswg.io#this-operator.

Task aliasing

Instead of providing a function to grunt.registerTask, we can also provide an array of strings; this will create a new task that will sequentially run each of the tasks listed in the array, essentially allowing us to give a name to a set of other tasks. For example, we could create three tasks: build, test, and upload, then alias them as a new task upload by using the following code:

//Code example 03-task-aliasing
module.exports = function(grunt) {

  grunt.registerTask('build', function() {
    console.log('building...'),
  });
  grunt.registerTask('test', function() {
    console.log('testing...'),
  });

  grunt.registerTask('upload', function() {
    console.log('uploading...'),
  });

  grunt.registerTask('deploy', ['build', 'test', 'upload']);
};

So, when we run grunt deploy, it will perform all three tasks in sequence:

$ grunt deploy
Running "build" task
building...

Running "test" task
testing...

Running "upload" task
uploading...

Done, without errors.

Now, let's assume that our build process was more complex. We could further divide up each of these three tasks into smaller subtasks. For instance, our fictitious build task above could also be an alias made up of build-related tasks, such as compile-coffee-script, compile-tests, copy-html, and so on. In the next section, we'll see that multitasks fit the mold of most build processes, and therefore, when it comes time to name (or alias) a set of tasks, we'll most likely be referencing multitasks and their targets.

Multitasks

As with many build tools, the majority of Grunt tasks perform static checks or transforms on groups of files. This was the impetus for the introduction of multitasks. As we have seen in previous chapters, multitasks are like tasks, however, they accept multiple configurations. Grunt will use each property (except options) of a multi task's configuration as an individual configuration, called a target. This allows us to define a single task which is capable of being run many times, each time performing different actions based on each configuration. For example, let's review how we would implement a copy multitask, which copies files based on a set of one-to-one (source to destination) mappings:

//Code example 04-copy-multi-task
grunt.registerMultiTask('copy', function() {

  this.files.forEach(function(file) {
    grunt.file.copy(file.src, file.dest);
  });

  grunt.log.writeln('Copied ' + this.files.length + ' files'),
});

This task iterates through the this.files array copying each file object's source (src) to its destination (dest). In order to run this task, we must define at least one target. So, let's initialize our copy task configuration with two targets, each with two simple file mappings:

//Code example 04-copy-multi-task
grunt.initConfig({
  copy: {
    target1: {
      files: {
        'dest/file1.txt': 'src/file1.txt',
        'dest/file2.txt': 'src/file2.txt'
      }
    },
    target2: {
      files: {
        'dest/file3.txt': 'src/file3.txt',
        'dest/file4.txt': 'src/file4.txt'
      }
    }
  }
});

We can now run target2 of the copy task using the command grunt copy:target2, which should result in:

$ grunt copy:target2
Running "copy:target2" (copy) task
Copied 2 files

Done, without errors.

Furthermore, if we omit the target name and simply use the command, grunt copy, then Grunt will run all targets of the copy task:

$ grunt copy
Running "copy:target1" (copy) task
Copied 2 files

Running "copy:target2" (copy) task
Copied 2 files

Done, without errors.

Remember, the this.files array is filled with file objects using the methods described in the Configuring Files section in Chapter 2, Setting Up Grunt. This brings us to the next section on the multitask object.

The multitask object

As with tasks, we can access task properties of the currently running multitask via the grunt.current.task property. Similarly, the multitask object is set as the function context (the this operator) when the task is invoked. In addition to all of properties of the task object, the multitask object contains the following:

  • target – a string set to the target name (the property name used inside our Grunt configuration).
  • files – an array of file objects. Each object will have an src property and an optional dest property. This array is useful when we have described sets of files for use in a transform, where there are source (or input) files and optional destination (or output) files.
  • filesSrc – an array of strings representing only the src property of each file object from the above files array. This array is useful for when we have described sets of source files and we have no use for destination files. For instance, plugins that perform static analysis, like JSHint, would only require source files.
  • data – which is the target object itself. It is best used as a fallback if the files array and the options function don't provide the functionality necessary. In most cases, the use of this property is not required.

Although the options function also exists on the task object, the options function on the multitask object performs an extra step:

  • options – a function used to retrieve the combination of the task's and target's configuration options. This is functionally equivalent to merging the results of grunt.config.get([this.name, "options"]) and grunt.config.get([this.name, this.target, "options"]). This is useful because the user of the task can set task-wide defaults and then, within each target, they can override these defaults with a set of target-specific options.

For more information on the multitask object, see http://gswg.io#grunt-task-object.

Asynchronous tasks

Synchronous tasks are considered complete as soon as the task function returns. However, in some cases we may need to utilize libraries with asynchronous APIs. These APIs will have functions that provide their results via callbacks instead of the return statement. If we were to use an asynchronous API in a synchronous task, this would cause Grunt report success (no errors detected) and incorrectly continue onto the next task in the list.

As described previously, both the task object and the multitask object contain an async function, which notifies Grunt that the current task is asynchronous and also returns a done function. This done function is used to manually control the result of our task. For example, we could create a task that retrieves a file using HTTP and stores the contents on disk:

//Code example 05-async-webget
var request = require('request'),
var url = 'https://raw.github.com/jpillora/'+
          'gswg-examples/master/README.md';

module.exports = function(grunt) {
  grunt.registerTask('webget', function() {
    var done = this.async();
    request(url, function(err, response, contents) {
      if(err) {
        done(err);
      } else if(response.statusCode !== 200) {
        done(new Error('Not OK'));
      } else {
        grunt.file.write('FILE.md', contents);
        grunt.log.ok('FILE.md successfully created'),
        done();
      }
    });
  });
};

At the top of our example Gruntfile.js, we are requiring the popular module for performing HTTP requests: request. Since request performs an asynchronous HTTP request, we'll use the task object's async function (this.async()) to both place this task in asynchronous mode, and then retrieve the done function. Subsequently, we can signal failure to Grunt by passing an Error object or false to the done function. Anything else will signal success.

In this example, once we've received the response we shall first check that there were no errors with sending the request. If there are errors, we'll pass the err object straight to done. Next, we'll check if we received response successfully by confirming that the HTTP statusCode is 200. If it is not, we will pass our own custom error "Not OK" to done. Once both error checks have passed, we can finally write the response's contents to disk and then call done(), informing Grunt that this asynchronous task has completed successfully. So, when we run this task, we should see:

$ grunt webget
Running "webget" task
>> FILE.md successfully created

Done, without errors.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.28.108