Introducing the Grunt API

Uptil now, we haven't discussed any details of the Grunt Application Programming Interface (API). If the appearance of grunt in or around all of the gruntfile code snippets that we have seen so far has caught your eye, then you are to be congratulated for noticing that grunt is providing some things that we need in order to set up and configure our automated tasks.

Grunt exposes all of its properties and methods via the grunt object. Returning to the wrapper function that was provided as an example in the beginning of the gruntfile.js section, we can see that its anonymous function takes the grunt object as its only argument:

module.exports = function(grunt) {
  // grunt related code
};

It is through the grunt object that we can access all of its properties and methods and this is why this wrapper method is required.

A discussion of modules will be useful in order to get a better understanding of how node.js can share items, such as objects, properties, and methods, between files. The module.exports is a node.js pattern that allows the code within the function to be encapsulated into a single module of code. This module then can expose its contents in order to export whatever is asked for from the caller. The caller uses require to import a module. So what problem does this solve? In Node.js, declarations made within a file have scope to only that file. In order to provide a means to share these items across files, we can create modules that expose their contents and allow us to import them to other files. Imagine that we have an external JavaScript file, sum.js, and within it, we have a function that returns the sum of two numbers provided as arguments:

//sum.js
var sumOfTwoNumbers = function (x, y) {
    return x + y;
}

Only code within sum.js can call the sumOfTwoNumbers method. However, if we create a module, then Node can map from one file directly to another file. This is accomplished with the require method. The purpose of require() is to load, or import, a module to the calling file. So, for instance, we could load the sum.js file to a file named calculate.js with the following line of code:

//calculate.js
var myModule = require('./sum'),

This would load our sum.js file as a module; however, sum.js still doesn't expose anything because we need to use the module.exports function to expose the contents. In order to do this, we can modify sum.js as follows:

//sum.js
var sumOfTwoNumbers = function (x, y) {
    return x + y;
}
module.exports.sumOfTwoNumbers = sumOfTwoNumbers;

At this point, we now have a sum module that exposes its sumOfTwoNumbers method. We can now use this module to calculate a sum for us in the following manner:

//calculate.js
var myModule = require('./sum'),
var sum = myModule.sumOfTwoNumbers(5, 7);
console.log('The sum of 5 and 7 is: ' + sum);

We can test our small node.js application very easily. Create the two files, sum.js and calculate.js, and place them temporarily in the root of the project site. Using the code from the examples, you should have a sum.js file that looks as follows:

//sum.js
var sumOfTwoNumbers = function (x, y) {
    return x + y;
}
module.exports.sumOfTwoNumbers = sumOfTwoNumbers;

You should also have a calculate.js file that looks like the following:

//calculate.js
var myModule = require('./sum'),
var sum = myModule.sumOfTwoNumbers(5, 7);
console.log('The sum of 5 and 7 is: ' + sum);

Ensure that you are in the root of the sample_project directory:

cd [Your Project Directory]

Now, issue the following command from Terminal in order to run our small node.js program:

node calculate.js

The output in Terminal should be the content of our console.log() method in calculate.js:

Introducing the Grunt API

There are other ways to set up module.exports; however, the point here is simply to introduce the concept of using require to import modules and share code across multiple files. For ECMAScript 6 implementation of modules and module syntax, see the ES6 Module Syntax page at https://github.com/ModuleLoader/es6-module-loader/wiki/Brief-Overview-of-ES6-Module-syntax.

Referring to our gruntfile.js, we can now look at the wrapper function with the new understanding that module.exports is creating a module of our configuration files. The anonymous function that module.exports is set equal to accepts a parameter, grunt. The grunt argument is the grunt object whose properties and methods we are using in our configuration, task loading, task registration, and custom task creation. Here are some examples of how we have used the grunt object in gruntfile.js thus far.

The wrapper function and initConfig

We will have a better look as wrapper function in this section:

module.exports = function(grunt) {
  grunt.initConfig({
          pkg: grunt.file.readJSON('package.json'),
        banner: '/*! <%= pkg.name %>' + ' <%= grunt.template.today("yyyy-mm-dd") %> */
',
        uglify: {
          options: {
                banner: '<%= banner %>'
          },
            ...{
            }
        }
    });
};

The initConfig method is an alias for the grunt.config.init method. The purpose of grunt.initConfig is to initialize the grunt configuration object. Within the configuration, we begin with creating a pkg object with a value of the contents of package.json. Grunt exposes the grunt.file object's readJSON method that takes a parameter of a valid JSON file. The grunt.template.today method is a helper function that can be used to obtain and format today's date. Notice the format string that it takes as an argument. The yyyy-mm-dd is a format string that will output today's date in the format of the full year, two-digit month, and two-digit date of month.

Loading NPM tasks:

Lets see how to load NPM tasks:

  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'),

The grunt loadNPMTasks method takes an argument of a string name that specifies a locally installed plugin that was installed through NPM.

Creating tasks

The grunt.registerTask method registers an alias, 'test' in this example, with a task list of one or more tasks.

grunt.registerTask('test', ['jshint', 'qunit']);

Grunt fail API

The grunt.warn method will display an error and abort Grunt. There is a flag that can be used in order to warn and continue processing tasks, the –force flag. The grunt.fatal method will also display an error and abort Grunt.

The Grunt event API

Grunt itself does not dispatch any events; however, the Grunt event API provides the ability to define event listeners and handler methods. Some event methods include the following methods:

  • grunt.event.on(event, listener): This adds a listener to the array of listeners for the event defined in the method
  • grunt.event.once(event, listener): This adds a one-time-use listener for the specified event, after which time that the event is dispatched, the listener is removed from the array of listeners
  • grunt.event.off(event, listener): This method removes an event listener from the array of listeners
  • grunt.event.removeAllListeners([event]): This removes all listeners from the array of listeners

The Grunt file API

The file API provides methods for file operations, such as reading and writing files and finding files within the filesystem. Grunt leverages many node.js file methods, adding additional options such as logging and error handling.

Reading and writing files are the more common uses of the Grunt File API. The grunt.file.read, by default, returns a string representation of the file's contents. In the Grunt template that was used as an example earlier in this chapter, we saw a method, grunt.file.readJSON. Like the read method, readJSON reads a file and returns a result of JSON formatted data. Another read method is grunt.file.readYAML, which does as expected, and returns YAML formatted data.

Grunt provides methods for write operations, including grunt.file.write, to write content to files and create directories based on the filepath parameter. The write method has a --no-write flag that will prevent write from actually writing the file. This is useful for a test run to ensure that the file writing task behaves as expected. File copy processing is handled by grunt.file.copy. The copy method takes a source, destination, and option as its arguments and will create directories as needed. Like file.write, file.copy also has a --no-write flag.

Deleting files with recursive deletion of directories is accomplished with the grunt.file.delete method. The file.delete method will delete the file path specified in its arguments without deleting the current working directory. However, file.delete has a --force option that will allow the file.delete operation to delete the current directory as well. The file.delete method also has a --no-write flag.

The directories methods in grunt provide two methods—grunt.file.mkdir creates directories along with any directories needed to be created as provided in the dirpath parameter; file.mkdir command provides a mode parameter that is used to specify directory permissions. If no mode is specified, then the file permissions on a newly created directory will default to 0777 (rwxrwxrwx). The second method is grunt.file.recurse; file.recurse allows recursively accessing directories and executing a callback function for each file contained within. The callback receives the absolute path, root directory, subdirectory, and filename as arguments that can then be used in the callback.

The Grunt file API also includes methods for filename expansion (globbing). Some examples that use expansion are grunt.file.expand, which returns an array of file paths or an array of directory paths. The grunt.file.expandMapping method returns an array of src-dest file mapping objects. The grunt.file.match method returns an array of file paths that match a specified globbing pattern. See the NPM glob project for additional information at https://www.npmjs.com/package/glob. Like the file.match method, grunt.file.isMatch matches files to patterns; however, file.isMatch method returns a Boolean—true, if matching file(s) are found and false, if not.

Additionally, the API provides methods for file type operations such as grunt.file.exists, which checks for the existence of a file, returning a Boolean of a given path result. In order to check whether a given path is a symbolic link, grunt.file.isLink will return a Boolean result. To check for the existence of a directory, grunt.file.isDir will return a Boolean given a specified path. Similarly, in order to determine if a specified path is a file, grunt.file.isFile will provide a Boolean result.

The last section of the Grunt file API is methods for path operations. The grunt.file.isPathAbsolute method returns a Boolean result of whether the given path is an absolute path. To check whether more than one path refers to the same path, grunt.file.arePathsEquivalent will return a Boolean. The grunt.file.doesPathContain method returns a Boolean whether a path's descendants are contained within a specified ancestor path.

The Grunt log API

The main purpose of the grunt.log API is to output messages to the console. The grunt.log.write flag creates a log of a string taken as a msg argument. If you wish to write a log with line breaks, grunt.log.writeLn adds a trailing new line character to the end of the msg argument. In order to log a msg as an error, the grunt.log.error method should be used. If the msg is null, red ERROR text will be logged; otherwise, the error message will be logged with a new line character added to its end. Formatting an error message to 80 columns of text is achieved with the grunt.log.errorLns method. To log an OK message in green, use the grunt.log.ok method with a null msg argument; otherwise, it will log the message with a trailing new line character. To wrap an OK message to an 80-column format, use the grunt.log.okLns method. Bold text can be added to a log message using grunt.log.subhead. This will also add a new line character to the end of the message. Logging object properties can be accomplished with the grunt.log.writeFlags method. The log.writeFlags is useful for debugging, as is grunt.log.debug, which logs a debug message only if the --debug flag is used.

The Grunt log API has some utility methods that provide a string result that can be used by other methods, not actually generating log entries themselves. For instance, grunt.log.wordlist returns an array of comma-separated items. The separator can be defined in options and can also take a color option to color the separator. In order to remove all color from a result, grunt.log.uncolor will remove all color information from a string. In order to wrap text at a certain number of characters, the grunt.log.wraptext method should be used. Should a table format of text-aligned to columns be desired, grunt.log.table will generate output in columns.

The Grunt option API

We have actually seen the Grunt options API being used already. In the configuration section of the sample gruntfile.js earlier, we saw the options being defined for the tasks that we were configuring. For instance, in our pseudo-configuration we saw the following use of options:

        uglify: {
          options: {
                banner: '<%= banner %>'
          },
            ...{
            }
        }

The options API provides tasks with a means to share parameters and access command-line flags, such as the --debug flag that was mentioned with the grunt.log.debug method. The grunt.option simply gets or sets an option. It takes a key, such as debug, and a value, such as true, which can then be used to determine if the current task should be run in debug mode. The grunt.option.init initializes an option and grunt.option.flags returns an array of command-line parameters.

Grunt Template API

In the sample gruntfile.js found earlier in this chapter, we saw some examples of templates being used. Recall that we loaded the contents of package.json in an object named pkg and then were able to refer to the properties in the pkg object through dot notation:

dest: 'dist/<%= pkg.name %>.js'

In this snippet, we are referring to the name property of the pkg object with pkg.name. Notice the <%= and %> that surround pkg.name. These characters are template delimiters and allow the string representation of the value of the template within to be expanded when it is used. The grunt.template.process processes Lo-Dash template strings. Discussion of Lo-Dash is out of the scope of this book's purpose; more information on Lo-Dash can be found at https://lodash.com/docs/#template. The delimiters shown are the default template delimiters; however, these can be changed with template.process using options.delimiters to set a custom delimiter. The template.process uses grunt.template.setDelimiters internally to set delimiters, and template.setDelimiters can be used on its own to set custom delimiters too, but it is most commonly achieved with template.process.

It should be noted that the grunt object is exposed within a template, so all of the grunt properties and methods are available inside the template delimiters. A simple example would be setting the formatted date using the grunt.template.today method as we saw previously when creating a banner string that was used in the contrib-uglify configuration:

banner: '/*! <%= pkg.name %>' + ' <%= grunt.template.today("yyyy-mm-dd") %> */
'

The Grunt task API

The last Grunt API that we will cover is the Grunt task API. We have already seen the API in use in our sample gruntfile earlier in this chapter. The three main purposes of the Grunt task API are to register, run, and load external tasks.

The grunt.task.registerTask registers either an alias task or task function. If task.registerTask is being used to register an alias task, task.registerTask creates an alias for one or more tasks contained within an array of tasks. In the case that a description and function are provided as arguments to task.registerTask, the function argument will be executed when the task is run. The grunt.task.registerMultiTask registers what is known as a multi-task. A multi-task is a task that will run all of its named targets if no specified target exists, or if the target is specified, then it will run only that particular target.

The grunt.task.require ensures that a required task successfully completes; otherwise, the task that requires it will be failed. This allows a task dependency to be created. The grunt.task.exists checks for the existence of a task and returns a Boolean based on the result. The grunt.task.renameTask provides the ability to rename a task and perhaps override the old task with new behavior in the new task. External tasks are loaded from a specified directory with grunt.task.loadTasks. Similarly, grunt.task.loadNpmTasks loads external tasks that were installed with NPM. Tasks can be queued and run on command. The grunt.task.run will run an array of tasks independently from Grunt running all tasks from the command line. The grunt.task.clearQueue will empty any tasks that are queued, entirely.

..................Content has been hidden....................

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