Blog

04
June 2015

Gavin Pickin

Cordova Hooks - Deep Drive into my JS Hint Hook - written in NodeJS

Android, Conferences, Cordova / Phonegap, Ionic, IOS, Javascript, Mobile Development, Node.js

After my session about Dev.Objective() called “Getting your Hooks into Cordova - Workflows and Build Helpers”, I decided I should do a deep dive into each of my Hooks I discussed in my presentation, and today we’ll look at my JS Hint Hook, written in NodeJS.

My presentation mini site is available here on my blog at http://www.gpickin.com/devobj2015/cordovahooks/

As I add more blog posts about this topic, I will continue to update the mini site.
The minisite has links to the gists of all the hooks I had, the slides and links to the few blog posts I did find on the topic.

For more information on what hooks are, please refer to my Session Review and Overview here: Post Conference - Session Review - Getting your hooks into Cordova- Workflows and Build Helpers

The next hook in my hybrid mobile workflow with Cordova, is my JS Hint hook.

This hook is setup to run in the BEFORE_BUILD.

Have you ever been working on your app, save your progress, switch to the command line, Cordova build your platform, the in xcode run the app, wait for the app to push to your phone, then try out the new feature, it doesn't work, so then you spend 15 minutes debugging the problem to realize you missed a semi colon, or a curly bracket?

I have, way too many times, so I decided I wanted to Lint my code... and save myself the brain numbing frustration of an absent minded bug.
Note: Of course I could have my IDE do that, but this is nice to catch any file, not just the ones I am working in.

I have used JS Lint before, Douglas Crockford's Linter for Javascript, but like me, someone else decided that it went too far. JS Hint was born from JS Lint, with the idea that syntax and best practices be the focus, not style. I used JS Hint whenever possible.

More info on JS Hint: http://jshint.com/about/
More info on JS Lint: http://www.jslint.com/

This was my first look into Cordova Hooks, I wanted to find a way to run JS Hint on every build I ran, so I could catch those silly mistakes, and save debugging for the real bugs. So I found this article by Nic Raboy called ‘Minifying Your App’s Source Code’ located on the Ionic blog http://blog.ionic.io/minifying-your-source-code/

I followed the instructions, and had the npm modules installed, and the hook installed, and once I remembered the magic trick with Cordova hooks, make them executable, my App was hinting my code on every build, in a few minutes.

You need two modules for this hook, jshint and async. You can install them like this:

$ npm install jshint
$ npm install async

 

Then you need to download the hook file from the Gist below, and put the file in the before_build folder (change the number to change priority), make it executable, and then modify the folders you would like to Hint on every build.

What does a successful build look like?

$ cordova build ios

Running command: /Users/gavinpickin/myapp/hooks/before_prepare/02_jshint.js /Users/gavinpickin/Dropbox/Apps/myapp
Linting www/js/services/attendeeDAO.js
Linting www/js/index.js
File www/js/services/attendeeDAO.js has no errors.
-----------------------------------------
Linting www/js/services/attendeeService.js
File www/js/index.js has no errors.
-----------------------------------------
File www/js/services/attendeeService.js has no errors.
-----------------------------------------
Linting www/js/services/attendeeSyncService.js
File www/js/services/attendeeSyncService.js has no errors.

-----------------------------------------
Linting www/js/services/auditDAO.js
File www/js/services/auditDAO.js has no errors.

 

What does a bad build look like?

$ cordova build ios

Running command: /Users/gavinpickin/Dropbox/Apps/myapp/hooks/before_prepare/02_jshint.js /Users/gavinpickin/Dropbox/Apps/myapp
Linting www/js/services/attendeeDAO.js
Linting www/js/index.js
Errors in file www/js/services/attendeeDAO.js
1:1 -> Expected an assignment or function call and instead saw an expression. -> funct ion newAttendeeDAO() {
1:6 -> Missing semicolon. -> funct ion newAttendeeDAO() {
1:7 -> Expected an assignment or function call and instead saw an expression. -> funct ion newAttendeeDAO() {
1:10 -> Missing semicolon. -> funct ion newAttendeeDAO() {
1:27 -> Missing semicolon. -> funct ion newAttendeeDAO() {
-----------------------------------------
File www/js/index.js has no errors.

 

The URL for this Hook is https://gist.github.com/gpickin/04a7acfc4907f3ed27f6

Remembering, I didn’t write this one, lets look at the file. If you don’t care about how it works, no problem… just edit the foldersToProcess array, and ignore the rest.

#!/usr/bin/env node

First we need to let Cordova know this is a NodeJS file.

 

var fs = require('fs');
var path = require('path');
var jshint = require('jshint').JSHINT;
var async = require('async');

Next, we include the modules we’ll be using. fs filesystem and path are the standards, but we also list the 2 we installed for this hook here as well.

 

var foldersToProcess = [ 'js', 'js/services' ];

This is where we set the folders we want JS Hint to lint. This hook currently is a flat directory only hook, so if you have subfolders, like services in this example, under js, we need to specify them both. We could use a lesson learned from a previous hook to recursively walk the file system, but this is not modified (at this time).

 

foldersToProcess.forEach(function(folder) {
    processFiles("www/" + folder);
});

This starts the process, looping over the foldersToProcess array, and for each of those elements, it calls processFiles with the full path.

 

function processFiles(dir, callback) {
    ...
}

Next is the processFiles function, which runs through the directories we want to process, and then calls another function from there, ‘lintFile()’ that actually lints the file, and outputs the associated console debugging info. Lets look into processFiles more

 

var errorCount = 0;
fs.readdir(dir, function(err, list) {
    if (err) {
        console.log('processFiles err: ' + err);
        return;
    }
    … //continue in the processFiles()
}

Here we set an error counter, and then read the directory in question, if there is an error, we log it to the console, and return, otherwise keep processing this directory.

 

async.eachSeries(list, function(file, innercallback) {       //1
    file = dir + '/' + file;                                 //2
    fs.stat(file, function(err, stat) {                      //3
        if(!stat.isDirectory()) {                            //4
            if(path.extname(file) === ".js") {               //5
                lintFile(file, function(hasError) {
                    if(hasError) {                           //6
                        errorCount++;
                    } 
                    innercallback();
                });
            }
            else {
                innercallback();
            }
        }
        else {
            innercallback();
        }
    });
}, function(error) {            // 7
    if(errorCount > 0) {
        process.exit(1);
    }
});
  1. Next, we loop through the list of files in that directory.
  2. Get the full path of the file
  3. Get the file system stats using fs.stat() on the full path
  4. If it is a directory then call the callback to exit to the loop
  5. If it is not a directory, and the file extension is js, lint the file by calling lintFile() on the file.
  6. If the callback is called with an error, we add to the error count, regardless we call the inner callback to exit to the loop
  7. When the async series is done… if there is an errorCount of more than 0, then we will exit the process… which means this hook stops the WHOLE build process.

Now, lets look into the lintFile()

function lintFile(file, callback) { 
    console.log("Linting " + file);                    //1
    fs.readFile(file, function(err, data) {            //2
        if(err) {                                      //3
            console.log('Error: ' + err); 
            return; 
        } 
        if(jshint(data.toString())) {                  //4
            console.log('File ' + file + ' has no errors.');      //5
            console.log('-----------------------------------------'); 
            callback(false);                           //6
        } 
        else {                                         
            console.log('Errors in file ' + file);     //7
            var out = jshint.data(),                   //8
                errors = out.errors; 
            for(var j = 0; j < errors.length; j++) {   //9
                console.log(errors[j].line + ':' + errors[j].character + ' -> ' + errors[j].reason + ' -> ' + errors[j].evidence); 
            } 
            console.log('-----------------------------------------'); 
            callback(true);                            //10
        } 
    }); 
}
  1. First output to the console we are linting this file
  2. Use fs.readFile to read the file. 
  3. Following the Node convention, if there is an error, the first argument will not be null, and we want output an error and exit early.
  4. Next, we hint the file by calling JSHint on data.toString()…
  5. If the return of that function is true, we output there was no errors,
  6. Then we call the callback with false, saying no errors found
  7. If the result of linting the file is false, then we output that there is an error in the file. 
  8. We pull the JSHint data out with jshint.data()
  9. Then we loop through the errors, outputting the errors to the screen.
  10. We finish by calling the callback with true, telling the hook we have an error, and then the hook knows to output the errors, and stop the cordova build (as we discussed at the end of the async earlier).

I love this hook, and I will use it in all my projects for all the time it saves me. Its not just the automatic bug identification, it saves all the time that getting the app takes to get to your phone. Awesome. 

Thanks again to those who contributed to this... and thanks for reading

Nic Raboy : ‘Minifying Your App’s Source Code’ on the Ionic Blog http://blog.ionic.io/minifying-your-source-code/
The URL for this Hook is https://gist.github.com/gpickin/04a7acfc4907f3ed27f6
More info on JS Hint: http://jshint.com/about/
More info on JS Lint: http://www.jslint.com/

Blog Search