Up until this point, we have learnt how to configure and create tasks. Now it is time to run them!
Some Node.js command-line tools, such as express
, may also be used as a module, whereas Grunt may only be used via the command-line. Once we've globally installed the grunt-cli
module, our system will have access to the grunt
executable.
To run our newly loaded or created tasks, we need to provide Grunt with a list of task names as space-separated command-line arguments. This will result in Grunt executing each specified task in sequence; which means we can easily dictate the order of task execution. We could run foo
then bar
with:
$ grunt foo bar
Or, we run bar
then foo
with:
$ grunt bar foo
There is a special case, however, when we execute grunt
on its own. Grunt interprets this as grunt default
and subsequently will attempt to run the default
task. Therefore, by registering a default
task, we can make it easy to run our most common task. Similar to our previous example in the Task aliasing section, we could alias our build
and test
tasks as the default
task with the following Gruntfile.js
file:
//Code example 06-default-tasks module.exports = function(grunt) { grunt.registerTask('build', function() { console.log('building...'), }); grunt.registerTask('test', function() { console.log('testing...'), }); grunt.registerTask('default', ['build', 'test']); };
Now, we can simply run grunt
, which should result in:
$ grunt Running "build" task building... Running "test" task testing... Done, without errors.
We can run multitasks in a similar fashion; however, when we specify a multitask, Grunt will execute all of its targets. If we wanted to run a particular target then we can append it to the task name. So, if we wanted to run the
foo
task's target1
target, then we would execute grunt foo:target1
. For example, let's convert our build
and test
tasks in the previous example to multitasks and test this out:
//Code example 07-default-multi-tasks module.exports = function(grunt) { grunt.initConfig({ build: { main: {}, extra: {} }, test: { main: {}, extra: {} } }); grunt.registerMultiTask('build', function() { console.log('building target ' + this.target + '...'), }); grunt.registerMultiTask('test', function() { console.log('testing target ' + this.target + '...'), }); grunt.registerTask('default', ['build:main', 'test:main']); };
We can explicitly run the build
task's main
target and then the test
task's main
target with:
$ grunt build:main test:main Running "build:main" (build) task building target main... Running "test:main" (test) task testing target main... Done, without errors.
However, extending from the previous example, we could also add these targets in our default
task alias. As you can see in the previous code, we have placed targets inside our array of task names, and therefore, when we run grunt
we should see the same output:
$ grunt Running "build:main" (build) task building target main... Running "test:main" (test) task testing target main... Done, without errors.
Additionally, when we specify a task we may also include an optional, colon-separated, list of arguments. For example, the following Gruntfile.js
defines a foo
task, which prints its first and second parameters:
//Code example 08-task-args module.exports = function(grunt) { grunt.registerTask('foo', function(p1, p2) { console.log('first parameter is: ' + p1); console.log('second parameter is: ' + p2); }); };
Now, we can run the foo
task with the arguments bar
and bazz
using:
$ grunt foo:bar:bazz Running "foo:bar:bazz" (foo) task first parameter is: bar second parameter is: bazz Done, without errors.
However, when we wish to run a multitask, before we can specify arguments we must first specify the target. Let's convert the previous example's foo
task into a multitask:
//Code Example 09-multi-task-args module.exports = function(grunt) { grunt.initConfig({ foo: { ping: {}, pong: {} } }); grunt.registerMultiTask('foo', function(p1, p2) { console.log('target is: ' + this.target); console.log('first parameter is: ' + p1); console.log('second parameter is: ' + p2); }); };
Similarly, but with the inclusion of ping
as the target:
$ grunt foo:ping:bar:bazz Running "foo:ping:bar:bazz" (foo) task target is: ping first parameter is: bar second parameter is: bazz Done, without errors.
With these examples in mind, we can see that we could create aliases which use tasks, multitasks, targets, and arguments all together, resulting in an extremely flexible build.
Not to be confused with configuration options, runtime options must be specified on the command-line in addition to our list of tasks. Runtime options are used to create Grunt-wide settings for a single execution of Grunt. Runtime options must be prefixed with at least one dash, "-", otherwise they will be seen as task name. Runtime options are best used when one or many tasks have a configuration setting that we wish to modify only some of the time. For instance, when we execute Grunt we can enable our optimize
option to direct each task specified to run in an optimized mode. This could remove debug statements, compress output, and so on. Once we've specified a runtime option on the command-line, we can retrieve its value using the
grunt.option
function.
For example, let's say we have the following Gruntfile.js
:
//Code example 10-runtime-opts module.exports = function(grunt) { console.log('bar is: ' + grunt.option('bar')); grunt.registerTask('foo', function() { //nothing here... }); };
Now, if we run this empty foo
task with no options, we'll see:
$ grunt foo bar is: undefined Running "foo" task Done, without errors.
Then, if we run this task again with the bar
option set:
$ grunt foo --bar bar is: true Running "foo" task Done, without errors.
If we like, we can give the bar
option a specific value using the =value
suffix:
$ grunt foo --bar=42 bar is: 42 Running "foo" task Done, without errors.
In this case we are using the
grunt.option
function outside of the task. This is important since it means we can use our runtime options to assist with the configuration of our tasks. Note the console.log
output occurs before the "Running "foo" task"
output; this is because Grunt executes our Gruntfile.js
in order to initialize our tasks and configuration, and only then the tasks specified on the command line are run in sequence.
For details on the Grunt Runtime Options API, refer to http://gswg.io#grunt-options. In Chapter 4, Grunt in Action, in the Step 5 – tasks and options section, we will review a technique to achieve environment specific builds through the use of runtime options.
When we are provided with an existing project for which there is no explicit documentation regarding the Grunt build, we can start off by listing the available tasks using the
grunt --help
command. When we use grunt.registerTask
or grunt.registerMultiTask
, we may optionally include a description. Let's review an example of this:
//Code example 11-task-help module.exports = function(grunt) { grunt.registerTask('analyze', 'Analyzes the source', function() { console.log('analyzing...'), } ); grunt.registerMultiTask('compile', 'Compiles the source', function() { console.log('compiling...'), } ); grunt.registerTask('all', 'Analyzes and compiles the source', ['analyze','compile'] ); };
Now, if we run grunt --help
, we should see the following excerpt within the output:
$ grunt --help Grunt: The JavaScript Task Runner (v0.4.2) Usage grunt [options] [task [task ...]] … Available tasks analyze Analyzes the source compile Compiles the source * all Analyzes and compiles the source …
The Grunt static help text has been omitted, leaving only the dynamic text. Here, we can see that Grunt has listed each of our tasks, along with its description, and multitasks are suffixed with a star *
. This is useful because it might not be obvious to those new to this build that the all
task runs both the analyze
task and the compile
task.
Although it is possible to execute Grunt from another program, it is intended to be used as a command-line utility, and therefore its API is only to be used with the grunt
executable. We can, however, programmatically run tasks within other tasks, allowing us to conditionally run a series of tasks.
The following example is very similar to Code example 04-linting
from Chapter 1, Introducing Grunt. This time, however, instead of defining our JSHint rules inside our Gruntfile.js
, we are defining them in a portable .jshintrc
file. This is favorable to some as it provides the ability to use a company-wide JavaScript coding style:
//Code example 12-conditional-lint module.exports = function(grunt) { // Load the plugin that provides the "jshint" task. grunt.loadNpmTasks('grunt-contrib-jshint'), // Project configuration. grunt.initConfig({ jshint: { options: { jshintrc:'.jshintrc' }, target1: 'src/**/*.js' } }); };
With this configuration, however, the jshint
task will fail if the .jshintrc
file is missing:
$ grunt jshint Running "jshint:target1" (jshint) task ERROR: Can't find config file: .jshintrc
Therefore, if we wanted to make our jshint
task run only when we provide a .jshintrc
file, then we could make another task that controls the execution of the jshint
task:
// A new task to make "jshint" optional grunt.registerTask('check', function() { if(grunt.file.exists('.jshintrc')) { grunt.task.run('jshint'), } });
In our new check
task, we shall first verify that the .jshintrc
exists, and then we'll programmatically run the jshint
task using the grunt.task.run
function. Now, when we run the check
task without a .jshintrc
file, Grunt should do nothing and report success:
$ grunt check Running "check" task Done, without errors.
Though, when we include our .jshintrc
file along side our Gruntfile.js
and rerun our check
task, we should see the following:
$ grunt check Running "check" task Running "jshint:target1" (jshint) task >> 1 file lint free. Done, without errors.
For an example of a .jshintrc
file, please refer to http://gswg.io#jshintrc-example. For a summary of JavaScript Linting, please return to the Static Analysis or Linting section of Chapter 1, Introducing Grunt.
One of the most popular Grunt plugins is grunt-contrib-watch
(http://gswg.io#grunt-contrib-watch) as it allows us to place Grunt in the background and have it automatically run our tasks as they're needed. Written by Kyle Robinson Young, the watch
task instructs Grunt to watch a particular set of files for changes and execute a particular task or set of tasks in response. In the following example, we'll watch our source files, and then run our JavaScript concatenation task concat
whenever any of these files are changed:
//Code example 13-watch module.exports = function(grunt) { // Load the plugins that provide the "concat" and "watch" tasks. grunt.loadNpmTasks('grunt-contrib-concat'), grunt.loadNpmTasks('grunt-contrib-watch'), // Project configuration. grunt.initConfig({ srcFiles: ["src/a.js", "src/b.js", "src/c.js"], concat: { target1: { files: { "build/abc.js": "<%= srcFiles %>" } } }, watch: { target1: { files: "<%= srcFiles %>", tasks: ["concat"] } } }); // Define the default task grunt.registerTask('default', ['concat', 'watch']); };
At the top of our Gruntfile.js
file, we'll load both the plugins that provide the concat
and watch
tasks. We will then configure them using a shared srcFiles
property. This means we can modify our source files once, and all tasks using this set of files will stay current. This helps to keep our build DRY (http://gswg.io#dry) by creating a single source of truth. All targets of the watch
task (only target1
in this case) require a tasks
property that should specify a list of tasks to run when one of the target's files
are changed. Finally, we'll provide a default task that runs concat
followed by watch
. Running grunt
at this point should produce:
grunt Running "concat:target1" (concat) task File "build/abc.js" created. Running "watch" task Waiting...
At this point, our watch task is running and is Waiting...
for one of our files
to change; so if we modify and save src/b.js
, we should see the following appended to our output:
OK >> File "src/b.js" changed. Running "concat:target1" (concat) task File "build/abc.js" created. Done, without errors. Completed in 0.648s at Tue Sep 17 2013 21:57:52 GMT+1000 (EST) Waiting...
Our concat
task was run, and our watch
task is Waiting...
again, ready for more changes. Since we are watching our source files, we can now minimize our terminal window and continue with our development workflow, knowing that Grunt is running in the background, taking care of the "grunt" work for us.
3.144.48.204