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:
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.
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.
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.
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']);
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.
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 methodgrunt.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 listenersgrunt.event.off(event, listener)
: This method removes an event listener from the array of listenersgrunt.event.removeAllListeners([event])
: This removes all listeners from the array of listenersThe 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 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.
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.
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 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.
18.216.149.94