Hooks

Hooks are pieces of code that are executed at certain points in the application build lifecycle. They let you extend the Cordova CLI framework in a number of ways.

There are two types of hooks–project specific and module level. Module level hooks are used if you leverage the Cordova CLI in a larger node.js application. Module level hooks provide events that your larger project can attach to “using the standard EventEmitter methods”. I have no experience with this and on researching for an example, this seems to be more of a “Cordova platform developer” type of feature, so I won’t discuss this further.

Project level hooks, on the other hand, are indispensable parts of a Cordova CLI application. They are executable scripts run before and after each stage of the Cordova CLI lifecycle. They live in a sample-cordova-project/.cordova/hooks/before_xxx or sample-cordova-project/.cordova/hooks/after_xxx directory, where xxx is the project lifecycle stage (prepare, build, etc). The current lifecycle stages are listed here, and here’s an example you can “borrow” from.

Hooks can be written in any programming language, but they are executed on every platform you are building your project on. So I’d stay away from C#, unless you are only deploying to Windows Phone. I have written shell scripts and node.js scripts. node.js is server side javascript, and is what Cordova CLI is written in. node.js is a safe choice because you can be sure that script will run anywhere you are using Cordova CLI.

The first argument of hook scripts is the project base directory.

Accessing the base directory in a node.js hook


1 #!/usr/bin/env node
2 var fs = require('fs');
3 
4 var rootdir = process.argv[2];
5 
6 // ... using rootdir

Accessing the base directory in a shell script


1 #!/bin/sh
2 
3 ROOTDIR=$1
4 
5 # ... using $ROOTDIR

Because hooks have the base directory, they can manipulate files in all project directories. In the future, hooks will get access to more data, including which platform the build is targeting, but for now all the scripts receive is the base directory.

You can use hooks for a wide variety of tasks, including:

  • adding needed plugins to a project, in after_platform_add
  • copying resources like icons and splash screens to appropriate locations within a platform directory, in after_prepare
  • run unit tests written before building a binary, in before_build
  • downloading remote API content and making it available to your application for quicker startup, in before_prepare.
  • moving platform specific configuration files (like AndroidManifest.xml), in after_platform_add. Make sure this happens before you install any plugins, because they can also modify platform configuration files.
  • injecting deployment specific values, such as which server the mobile application should be communicating with, in after_prepare.
  • changing native code to support multiple versions of Cordova, if you need to support multiple versions with breaking changes. For example, the package of CordovaPlugin, which changed between 2.9 and 3.0 could be modified.

If a hook is going to be manipulating a file that you change a lot during development, like a JavaScript file containing business logic, you want to have the hook execute during a common lifecycle stage, like after_prepare; otherwise you can have the hook execute after a less common event, which will speed up your build process.

Execution

You can see which hook scripts are executing by running the CLI with the verbose switch: cordova -d [command]. Each hook must be executable, otherise you will get an error message.

Not Executable Hook Script Error Message


1 [Error: Script "/path/to/rootdir/sample-cordova-application/.cordova/hooks/after_prepare/h\
2 ookscript.sh" exited with non-zero status code. Aborting. Output: /bin/sh: /path/to/rootdi\
3 r/sample-cordova-application/.cordova/hooks/after_prepare/hookscript.sh: Permission denied

Each hook is executed in OS specific order within the directory containing all the scripts for a lifecycle stage (before_xxx or after_xxx). On Windows, ae.js runs before aG.js, but on linux, aG.js runs before ae.js. Rather than rely on this implicit order, you should prefix each hook with a three or four digit number, to make the execution order explicit. For example:

  • 001_script.js
  • 010_script.js
  • 020_script.sh
  • 120_script.js

executes these scripts in the order you would expect, where:

  • copy_resources.js
  • modify_config_files.sh
  • delete_modified_files.js
  • copy_files.js

will execute in this order:

  • copy_files.js
  • copy_resources.js
  • delete_modified_files.js
  • modify_config_files.sh

which could have unfortunate side effects.

Stopping builds

Hooks can also stop execution of a build. If, for example, you are running your unit tests on every before_prepare event, and the unit tests fail, the build should stop–no sense in deploying to an emulator or building for a device if the unit tests fail. Stopping the build might be useful if a remote API is not available and you require it to build the application because you are preloading some data.

In order to stop execution, you can exit your hook script with a non zero exit code. You can also throw an error in a node.js script, but just exiting is probably cleaner.

Stopping Build Execution in a NodeJs Hook


1 #!/usr/bin/env node
2     
3 console.log("unable to complete the hook");
4 process.exit(1);

You will see an error in the build process like

Output of Stopping Execution


1 $ cordova build android 
2 [Error: Script "/path/to/rootdir/sample-cordova-application/.cordova/hooks/after_prepare/h\
3 ookscript.js" exited with non-zero status code. Aborting. Output: unable to complete the h\
4 ook

Node Scripts

If you choose to write node.js scripts, use the synchronous API for your filesystem operations. Node.js defaults to asynchronous filesystem access. While this is non-blocking and faster than the synchronous access, asynchronous access can lead to confusing results when moving or processing the same files with more than one hook. For example, operations you think will be completed in order are reversed, but only sometimes. Yes, faster builds are always better, but I have found the majority of my development time was spent waiting on emulators or thinking, not on hook scripts.

One benefit of using node.js scripts, beyond being cross platform, is that you can abstract common functionality out into a local npm package and share it between different Cordova projects. You can even leverage npm’s versioning system so that bugfixes can migrate between your hook scripts.

If you are using node.js scripts on windows, make sure that node.js is the default handler for files with the .js suffix. If another program handles your JavaScript hook scripts, you may see bizarre errors. To correct it, follow these instructions.