The Official Ionic Blog

Build amazing native and progressive web apps with HTML5

This is a guest post by Nic Raboy, an application developer with a strong background in Android, AngularJS, Ionic, Java, SQL, and Unity3D. Nic writes often about Ionic and how to build great hybrid apps.

Previously, I wrote about the importance of uglifying your Apache Cordova source code. If you read my previous post you’ll know that hybrid applications are incredibly easy to decompile, so uglifying your code creates additional difficulty for any malicious user.

I also wrote a generic post about how to use
the Grunt task runner to lint and uglify your code. However, using Grunt isn’t the most ideal solution when it comes to
linting your Apache Cordova project for errors and uglifying your code.

In this guide, we’re going to see how to efficiently lint and minify your Apache Cordova project before building. The same information can be applied against Phonegap and Ionic
Framework
projects, too.

Let’s start by creating a fresh Apache Cordova Android and iOS project:

cordova create TestProject com.nraboy.testproject TestProject
cd TestProject
cordova platform add android
cordova platform add ios

Note that if you’re not using a Mac, you cannot add or build for the iOS platform.

This tutorial is going to be broken into two parts:

  • Linting the project for JavaScript errors
  • Uglifying the code for obfuscation purposes

Once you’ve run these steps, your project will be in much better shape.

Linting the project for JavaScript errors

One of my subscribers recommended I check out Cordova Linter for this task. I checked it out and couldn’t figure out how to get it to work. It kept saying my project had no errors, when I know it did. The package had no documentation to prove its process.

This is when I decided to create my own Apache Cordova hook. If you’ve read my previous
post
regarding hooks, you should have a general idea of what we’re going to do.

Create hooks/before_prepare/02_jshint.js, and make sure to give it execute permissions if you’re using Linux or Mac. Based on the file title, you can probably guess we’re going to use JSHint for linting. Open 02_jshint.js, and add the following code:

#!/usr/bin/env node

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

var foldersToProcess = [
    'js'
];

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

function processFiles(dir, callback) {
    var errorCount = 0;
    fs.readdir(dir, function(err, list) {
        if (err) {
            console.log('processFiles err: ' + err);
            return;
        }
        async.eachSeries(list, function(file, innercallback) {
            file = dir + '/' + file;
            fs.stat(file, function(err, stat) {
                if(!stat.isDirectory()) {
                    if(path.extname(file) === ".js") {
                        lintFile(file, function(hasError) {
                            if(hasError) {
                                errorCount++;
                            }
                            innercallback();
                        });
                    } else {
                        innercallback();
                    }
                } else {
                    innercallback();
                }
            });
        }, function(error) {
            if(errorCount > 0) {
                process.exit(1);
            }
        });
    });
}

function lintFile(file, callback) {
    console.log("Linting " + file);
    fs.readFile(file, function(err, data) {
        if(err) {
            console.log('Error: ' + err);
            return;
        }
        if(jshint(data.toString())) {
            console.log('File ' + file + ' has no errors.');
            console.log('-----------------------------------------');
            callback(false);
        } else {
            console.log('Errors in file ' + file);
            var out = jshint.data(),
            errors = out.errors;
            for(var j = 0; j < errors.length; j++) {
                console.log(errors[j].line + ':' + errors[j].character + ' -> ' + errors[j].reason + ' -> ' +
errors[j].evidence);
            }
            console.log('-----------------------------------------');
            callback(true);
        }
    });
}

The above script will look at only the www/js directory, but feel free to add further directories. All files in the directory will be looped through, and if they are JavaScript, the file will be fed into JSHint. If any files contain errors, they will be presented to the screen, and then the script will stop all further processes. This means that if you execute the script with cordova build [platform], the application will not continue to build if errors are found.

02_jshint.js requires two NodeJS libraries in order to function. They can be installed like this from the root of
your project:

$ npm install jshint
$ npm install async

Uglifying the code for obfuscation purposes

One of my other subscribers recommended I check out Cordova Uglify for the obfuscation process. Unlike Cordova Linter, this NPM package actually worked as advertised. From the root of your Apache Cordova project, run the following command:

$ npm install cordova-uglify

When the installation completes, you should find that hooks/after_prepare/uglify.js was created. If you’re on Linux
or Mac, you’ll need to give it execute permissions; otherwise, it won’t be picked up.

You can test that this script worked by running cordova prepare or cordova build [platform].

Conclusion

By default, Apache Cordova does not check your code for errors when building. This means you won’t know if errors exist in your code until you run your application. Linting your code can save you a lot of stress when it comes to finding errors caused by typos or missing/extra brackets.

Your hard work is very easy to decompile by default, so it is a good idea to obfuscate your code by means of uglification before you release your application.

Two videos for this article can be seen below:

  • Luis Ruiz Figueroa

    tengo el siguiente error

    • Kerpanic

      make sure you run `npm install jshint async –save`.

  • Kerpanic

    But how do you pass jsHint configs when feeding it the source code from stdin?
    ionic uses AngularJS and you need different tuning.

  • https://securiteam.io Shadi Habbal

    I prefer using gulp-jshint in a hook as it’s much more cleaner and easier to iterate over directories and pass config to jshint.

    sample code: http://pastebin.com/KSAPhfLy

  • Mike M

    http://forum.ionicframework.com/t/psa-error-itms-90035-ios-submission-to-app-store/25250/2

    This appears to be a bit of a new error, helpful post though!

  • Jia

    Hi Nic,

    Thanks for the great post. I have few third party js files that I don’t want to minify. Is there any option in cordova uglify to exclude these files from minification (something similar to “externs” in google closure)?

  • Gavin Pickin

    Thanks for this post. I used this Hook a lot, and included it in my presentation recently, and broke it down. Of course I gave you all the credit, and thanks, because of this hook, I’m hooked on Cordova Hooks

    http://www.gpickin.com/index.cfm/blog/cordova-hooks-deep-drive-into-my-js-hint-hook-written-in-nodejs

  • http://buddasworld.co.uk buddaboy

    To get the uglify to work I needed to add –release when doing a build.

    eg.

    ionic build [platform] –release
    cordova build [platform] –release

    Or edit the config file in the hooks folder and set:

    “alwaysRun”: true,

    • Ítalo Ayres

      I’ve tried editing the config file and console showed my files being uglified. But I can’t found the result files anywere, and when I open the developer console with my app running, and look at the source files, they are the originals. Do you know what might be happening?

  • http://nicholls.azurewebsites.net/ Juan David Nicholls
  • http://babbler.eu Raffaele Ianniello

    after I gave the ionic prepare command I get this error:

    module.js:328
    throw err;
    ^

    Error: Cannot find module ‘/Users/networker/Desktop/SostaBlu/node_modules/cordova-uglify/node_modules/uglify-js’
    at Function.Module._resolveFilename (module.js:326:15)
    at Function.Module._load (module.js:277:25)
    at Module.require (module.js:354:17)
    at require (internal/module.js:12:17)
    at Object. (/Users/networker/Desktop/SostaBlu/hooks/after_prepare/uglify.js:11:16)
    at Module._compile (module.js:398:26)
    at Object.Module._extensions..js (module.js:405:10)
    at Module.load (module.js:344:32)
    at Function.Module._load (module.js:301:12)
    at Function.Module.runMain (module.js:430:10)
    Error: Hook failed with error code 1: /Users/networker/Desktop/SostaBlu/hooks/after_prepare/uglify.js

    • Kasim Sheyi

      Have you been able to resolve this?

      • Kasim Sheyi

        You can solve the issue by opening your project folder then open node modules folder then open cordova-uglify folder then open the node modules folder and copy all the folders you see their then click the back button twice and paste those folders inside node modules folder then build with cordova build [platform] –release.

        Thats the simple trick i used to solve mine

  • Tmpmai Fkmail

    fuck you nic always asking about logs or full logs

  • tao wang

    hi , folder assets/**.js have not minifyied

    E:auth>ionic build android

    Running command: “C:Program Filesnodejsnode.exe” E:authhooksbefore_prepare

    2_jshint.js E:auth

    Linting www/js/app.js

    File www/js/app.js has no errors.

    —————————————–

    Linting www/js/constants.js

    File www/js/constants.js has no errors.

    —————————————–

    Linting www/js/controllers.js

    File www/js/controllers.js has no errors.

    —————————————–

    Linting www/js/services.js

    File www/js/services.js has no errors.

    —————————————–

    Running command: “C:Program Filesnodejsnode.exe” E:authhooksafter_prepare

    010_add_platform_class.js E:auth

    add to body class: platform-android

    Running command: “C:Program Filesnodejsnode.exe” E:authhooksafter_prepare

    uglify.js E:auth

    ANDROID_HOME=C:UsersAdministratorAppDataLocalAndroidsdk1

    JAVA_HOME=C:Program FilesJavajdk1.8.0_65

  • Manish Mourya

    Facing following error. Please check the complete logs. MAC book PRO 10.11.15

    module.js:339
    throw err;
    ^
    Error: Cannot find module ‘mozjpeg-stream’
    at Function.Module._resolveFilename (module.js:337:15)
    at Function.Module._load (module.js:287:25)
    at Module.require (module.js:366:17)
    at require (module.js:385:17)
    at Object. (/**/node_modules/ionic-minify/lib/minifier.js:8:15)
    at Module._compile (module.js:435:26)
    at Object.Module._extensions..js (module.js:442:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:311:12)
    at Module.require (module.js:366:17)
    Error: Hook failed with error code 1: //**//hooks/after_prepare/ionic-minify.js
    at /usr/local/lib/node_modules/cordova/node_modules/cordova-lib/src/hooks/HooksRunner.js:195:23
    at _rejected (/usr/local/lib/node_modules/cordova/node_modules/q/q.js:797:24)
    at /usr/local/lib/node_modules/cordova/node_modules/q/q.js:823:30
    at Promise.when (/usr/local/lib/node_modules/cordova/node_modules/q/q.js:1035:31)
    at Promise.promise.promiseDispatch (/usr/local/lib/node_modules/cordova/node_modules/q/q.js:741:41)
    at /usr/local/lib/node_modules/cordova/node_modules/q/q.js:557:44
    at flush (/usr/local/lib/node_modules/cordova/node_modules/q/q.js:108:17)
    at doNTCallback0 (node.js:417:9)
    at process._tickCallback (node.js:346:13)

  • Krishna Karki

    There is an error on the file jshint.js on following code

    console.log(errors[j].line + ‘:’ + errors[j].character + ‘ -> ‘ + errors[j].reason + ‘ -> ‘ +
    errors[j].evidence);

    line, character, evidence variable are not always set so its giving following error to me.

    36:2644 -> Too many errors. (100% scanned). -> undefined
    /Applications/apps/band/hooks/before_prepare/01_jshint.js:65
    console.log(errors[j].line + ‘:’ + errors[j].character + ‘ -> ‘ + errors[j].reason + ‘ -> ‘ +
    ^

    TypeError: Cannot read property ‘line’ of null
    at /Applications/apps/band/hooks/before_prepare/01_jshint.js:65:30
    at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:380:3)

  • Antonio ds

    Change line #8 in after_prepare/uglify.js to:

    var dependencyPath = path.join(cwd, ‘node_modules’);

    Works for me.

  • Francesco Mussi

    Great article! Don’t know how could i lived so far without JSHint

  • William

    When I run this (in vanilla Cordova, not in Ionic) the jshint hook only lints the first of my js files. Any idea why?

    Also, I don’t understand how a value for innercallback in asyc.eachSeries is provided — can you give a bit more detail on that?

    Thanks!

    • William

      Whoops, sorry, I made an edit to the script and that was what was causing the loop to break. Still interested in where innercallback comes from though…

  • Kasim Sheyi

    You can solve the issue by opening your project folder then open node modules folder then open cordova-uglify folder then open the node modules folder and copy all the folders you see their then click the back button twice and paste those folders inside node modules folder then build with cordova build [platform] –release.

    Thats the simple trick i used to solve mine