Cordova hooks (4/4 steps to properly set mobile app project using Ionic framework)

/ Read time 10.5min

And here we are on the last article of “Properly set mobile app project using Ionic framework” topic.

We already went through three steps before:

Now it’s time for sugar at the end with introducing Ionic hooks. These tiny pieces of code will power up our development significantly.

Hooks are pieces of code that Cordova CLI executes at certain points in your Cordova application build. We’ll be using hooks to integrate the Cordova CLI into Gulp, so typically we’ll be writing project level hooks to manipulate files in our project.

Hooks live in the root/hooks/ folder, in a subdirectory named after project command: before_xxx or after_xxx, where xxx is the project command (prepare, build, run etc). The supported project commands are listed here.

Handle plugins

Every Ionic app needs plugins. Rather than have a document with all plugins your project needs and ask each new developer to download and install them, we’ll attach new hook which will handle this when you add a platform to a project. We’ll add it under the after_platform_add folder.

010_install_plugins.js


#!/usr/bin/env node

var exec = require('child_process').exec;
var path = require('path');
var sys = require('sys');

var packageJSON = require('../../package.json');
var cmd = process.platform === 'win32' ? 'cordova.cmd' : 'cordova';
// var script = path.resolve(__dirname, '../../node_modules/cordova/bin', cmd);

packageJSON.cordovaPlugins = packageJSON.cordovaPlugins || [];
packageJSON.cordovaPlugins.forEach(function (plugin) {
 exec('cordova plugin add ' + plugin, function (error, stdout, stderr) {
   sys.puts(stdout);
 });
});

The hook will read package.json file and install everything from cordovaPlugins object. You’ll only have to specify the list of plugins your app requires for the start. We set only two plugins for the start: com.ionic.keyboard and cordova-plugin-whitelist.

This is handy when starting with blank project and when platform hasn’t been added yet. We also want to register each additionally installed plugin to package.json file so we can keep track of all plugins in the project. The package.json file will be committed in Git/SVN repository and every new developer will have an access to plugin list and install each of them with previously mentioned task.

Of course we’ll not be installing each plugin from the list manually, one by one. We have a Gulp for doing these dirty things. In previous article we mentioned gulp reinstall-plugins task and we will use it now. The hook will be attached under the after_plugin_add folder.

010_register_plugins.js


#!/usr/bin/env node

var fs = require('fs');
var packageJSON = require('../../package.json');

packageJSON.cordovaPlugins = packageJSON.cordovaPlugins || [];
process.env.CORDOVA_PLUGINS.split(',').forEach(function (plugin) {
 if(packageJSON.cordovaPlugins.indexOf(plugin) == -1) {
   packageJSON.cordovaPlugins.push(plugin);
 }
});

fs.writeFileSync('package.json', JSON.stringify(packageJSON, null, 2));

The same thing, after removing plugin from project, we want to unregistered it and remove it from package.json. We’ll create after_plugin_rm folder and add hook there.

010_unregister_plugins.js


#!/usr/bin/env node

var fs = require('fs');
var packageJSON = require('../../package.json');

packageJSON.cordovaPlugins = packageJSON.cordovaPlugins || [];

process.env.CORDOVA_PLUGINS.split(',').forEach(function (plugin) {
 var index = packageJSON.cordovaPlugins.indexOf(plugin);
 if (index > -1) {
   packageJSON.cordovaPlugins.splice(index, 1);
 }
});

fs.writeFile('package.json', JSON.stringify(packageJSON, null, 2));

Now we have nice automatic process for handing plugins which we don’t have to think about while setting new project. Just add platform and all plugins will be installed and registered for us.

Prepare code for build

We already have set up Gulp tasks to minify code and detect environment. It would be handy to run all this before building the app for device (and maybe a little bit more). It’s because when we make some change in the code we want to make sure our code will get minified when transferring the app on device, even if we don’t run Gulp manually.

No problem. We’ll create another hook under before_build folder. Actually we’ll do the same under before_run folder because run command does the same as build but with installing the application on connected device at the end of the process.

010_build_assets.js


#!/usr/bin/env node

var exec = require('child_process').exec,
   child;

console.log('Runnung Gulp tasks. Please wait...');

var gulpCommand = 'gulp build-prepare';

if (process.env.CORDOVA_CMDLINE && process.env.CORDOVA_CMDLINE.indexOf('--production') > 0) {
	gulpCommand = 'gulp build-prepare --production';
}

child = exec(gulpCommand,
 function (error, stdout, stderr) {
   console.log('stdout: ' + stdout);
   console.log('stderr: ' + stderr);
   if (error !== null) {
     console.log('exec error: ' + error);
   }
});

So what this hook actually does is executes Gulp task on the same way we do from command line. It also checks if --production flag has been set and based on that constructs gulp command.

command line
ionic build android --production
ionic run android

Here’s gulp build-prepare task which contains not much logic but just calls other Gulp tasks.

gulpfile.js


gulp.task('build-prepare',[
   'release-detect',
   'default',
   'build-images',
   'version-increase'
 ], function() {
 console.log('build-prepare DONE');
});

The first thing to do is to detect if the build is for development or production (release-detect task) and emphasize that in console with some color (in this case red). This is not necessary but it’s handy to make sure we run build for correct environment.

Next we run a default task which consists of all tasks we mentioned in the previous tutorial, but let’s show it once again in short form

gulpfile.js


gulp.task('default', ['build-css', 'preprocess-js', 'build-templatecache', 'build-js', 'uglify', 'build-vendor', 'build-html']);

Here we can see which tasks are being called inside the default task, and in which order they are being executed, what is VERY important. For example, we can’t execute build-js before uglify because, uglify task will perform uglification on the previous bundle and not the latest one.

A new task here is build-images which goes through all resource (icons and splash screens) and project images and reduces their size. This is very helpful since we have a lot of splash screens images and because we are speaking about mobile app it’s very important to gain performance wherever we can.

And the last thing to do is version-increase task. Basically we open config.xml file, find a line of code where the version is defined and increase the number by one. This is only performed when we build the app for production because we build for development very frequently and this number would increase a lot after a while.

gulpfile.js


gulp.task('version-increase', function() {
 if (argv.production) {
   var fs = require('fs');

   fs.readFile("config.xml", 'utf8', function (err,data) {
     if (err) {
       return console.log(err);
     }

     var version = data.match(/(]* version=\"[0-9]+\.[0-9]+\.)([0-9]+)(\")/i)[2];
     console.log('current minor version number is ',version, " increasing it to ", parseInt(version)+1);
     version++;

     var result = data.replace(/(]* version=\"[0-9]+\.[0-9]+\.)([0-9]+)(\")/i, '$1' + version + '$3');

     fs.writeFile("config.xml", result, 'utf8', function (err) {
        if (err) return console.log(err);
     });
   });
 }
});

Do cleanup

We automated process a lot but there’s still one more thing we would like to add which reduces the size of our app a lot (more than double). This is very important when uploading the app on google play and app store services.

We need to delete unnecessary files after cordova prepares application for generating .apk file (in android scenario) which you can install on your mobile phone. There’s a perfect place to create a hook for this and it’s under the after_prepare folder. This hook will be calling gulp task just as 010_build_assets.js does.

020_delete_files.js


#!/usr/bin/env node

var exec = require('child_process').exec,
   child;

console.log('Deleting unnecessary files...');

child = exec('gulp delete-files',
 function (error, stdout, stderr) {
   console.log('stdout: ' + stdout);
   console.log('stderr: ' + stderr);
   if (error !== null) {
     console.log('exec error: ' + error);
   }
});

gulpfile.js


gulp.task('delete-files', function() {
 //remove unnecessary files
 console.log('delete-files STARTED');
 del([
   'platforms/android/assets/www/lib/**',
   'platforms/android/assets/www/js/**/*.js',
   'platforms/android/assets/www/js/**/*.html',
   '!platforms/android/assets/www/js/bundles/app.bundle.min.js',
   '!platforms/android/assets/www/js/bundles/vendor.min.js',
   'platforms/android/assets/www/build/**',
   'platforms/android/assets/www/templates/**',
   'platforms/android/assets/www/css/app.css',
   'platforms/android/assets/_where-is-www.txt',

   'platforms/ios/www/lib/ionic/js/ionic-angular.js',
   'platforms/ios/www/lib/ionic/js/ionic.bundle.js',
   'platforms/ios/www/lib/ionic/js/ionic.js',
   'platforms/ios/www/lib/ionic/js/angular/angular-animate.js',
   'platforms/ios/www/lib/ionic/js/angular/angular-resource.js',
   'platforms/ios/www/lib/ionic/js/angular/ionic-sanitize.js',
   'platforms/ios/www/lib/ionic/js/angular/angular.js',
   'platforms/ios/www/lib/ionic/js/angular-ui/angular-ui-router.js',
   'platforms/ios/www/lib/ionic/scss',
   'platforms/ios/www/lib/ionic/version.json',
   ])
   .then(function() {
     console.log('delete-files DONE');
   });
});

Why is this important? When Cordova builds your app (for Android platform for example) it creates .apk file under the platforms\android\build\outputs\apk folder (or platforms\android\ant-build if using Cordova < version 5.*). This file is executable file which you can install on android phones.

You remember we bundled all files using Browserify into single one and the rest of the JavaScript files are not necessary for production. The same goes for ionic and angular libraries we downloaded via gulp install. It’s part of the vendor.min.js now. We don’t need angular templates as well, since we built it inside the JavaScript via Gulp build-templatecache task.

After prepare phase is finished we need to hook up and delete all unnecessary files. This way Cordova will take only bundled files and put them inside the apk file. Your apk file will reduce in size drastically and will be more suitable for download from app stores.

We covered all most important stuff to prepare Ionic app for development and production. There is a lot of things to take care of but once you set up things correctly there’s no fear if application starts to grow up.

So let’s say a new developer joins the project or someone wants to use your boilerplate to start building a new app. What he/she needs to do in order to run the app on mobile device and have everything ready to start coding.

That’s it! You are ready to go and new project is fully prepared for new features to be added.

So to recap this topic, we have published entire code from blog series on GitHub https://github.com/netmedia/ionic-starter-kit where you can download or clone repository.

We hope you find it useful for your next project and stay tuned for more articles from NETMedia team.

Hello from NETMedia, EU based production team delivering high impact projects for selected direct clients, software/web development and digital agencies.

Like what you see? Let's have a chat about the cooperation.

contact us now
 
Cookies help us deliver our services and better user experience. By using our website, you agree to our use of cookies.
OK
More