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:
25 Comments
tengo el siguiente error
make sure you run `npm install jshint async –save`.
But how do you pass jsHint configs when feeding it the source code from stdin?
ionic uses AngularJS and you need different tuning.
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
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!
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)?
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
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,
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?
prefiero así https://github.com/jdnichollsc/Ionic-Starter-Template
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
Have you been able to resolve this?
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
fuck you nic always asking about logs or full logs
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
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)
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)
Change line #8 in after_prepare/uglify.js to:
var dependencyPath = path.join(cwd, ‘node_modules’);
Works for me.
Great article! Don’t know how could i lived so far without JSHint
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!
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…
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
Great Post Nic. Helped a Lot.
I was wondering where to put my .jshintrc or JSHint Config file. Can anyone help me with this?
I mixed up Progourd and cordova-uglify everything is fine i also want to secure my C:UsersADMINDesktopmyAppwwwbuildmain.js
By using cordova-uglify code is uglify but by using beatuify js my code main.js is readable how i fix this issue for main.js
For Progourd
You need to uncomment below lines from your project.properties file
To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
everything done successfully but unable to install app in android
2 Trackbacks