This post is about automating the build process for web applications built using modern web technologies/ hybrid mobile applications built using html, css, js and Cordova. When I started working on hybrid mobile application development using Apache Cordova and Angular.js, we maintained the entire codebase including the third party libraries (like jQuery, hammer.js, angular js etc.,) and the cordova plugins (plugins are used to leverage the device capabilities in a hybrid mobile app) in our source control. As the project grows big and as we add more and more libraries and plugins, the size of the codebase was increasing so much and maintaining the entire codebase was becoming cumbersome. To ease the process we started with automating a few of the tasks. The idea we had was to maintain only the core codebase which was developed by our team and add everything else dynamically as dependencies. This way the codebase becomes leaner and easier to maintain by the project teams.
Tools and Technologies used:
Most of the tools and technologies required for automating these tasks are available as node.js packages. So the very first step was to introduce a ‘package.json’ file to the project. With this file, all the other node packages which are required by the current project can be maintained as dependencies. Creating a package.json file is as easy as running the below command from the root directory of the application.
npm init
More on using npm init is available in this link https://docs.npmjs.com/cli/init
Next step was to identify the npm packages required for doing the automation. Below are the most important packages which would be required if we think of automating front end components
- Bower – package manager for client side components
- Grunt – javascript task runner
- Gulp – the streaming build system
I haven’t explored much on gulp yet, so will be talking about bower & grunt for the rest of the post.
Bower
Bower is a package manager for client side components. All the third party libraries, custom written common/utility JS libraries used in the project can all be managed using bower. Similar to “package.json” for npm packages, bower uses “bower.json” file. Bower.json follows most of the conventions used in package.json, hence creating bower.json is similar to its npm counterpart.
First Install bower as a global installation, so that it can be used across any other project
npm install -g bower
Next, run the below command to create the bower.json file
bower init
More on bower init in this link http://bower.io/docs/creating-packages/#bowerjson
Adding all the dependent libraries are easy by running the below commands. The `–save-dev` option will add these as dependencies under devDependencies section of bower.json. This would work good, if you are newly creating your project and started using bower from the beginning.
bower install angular --save-dev bower install jQuery --save-dev # add more bower install statements as required
What if you have already got a lot of libraries being referenced in your existing project? You can directly open the bower.json file and edit the devDependencies section to include all of them. The version numbers of the components are represented using the “Semantic Versioning”. More on semantic version over here.
A sample section excerpt from “bower.json” is shown below
"devDependencies": { "angular": "~1.3.14", "bootstrap-switch": "~3.3.2", "fastclick": "~1.0.6", "hammerjs": "~2.0.4", "jQuery": "1.10.2", "jquery-ui": "~1.11.3", "lawnchair": "~0.6.4", "rekapi": "~1.4.4", "shifty": "~1.3.11", "spin.js": "~2.0.2", "underscore": "~1.8.2" }
Now to install these dependencies, run the below command from the root directory of the application
bower install
By default, bower installs all the components under a folder named ‘bower_components’ within the root directory. However, this can be changed by modifying the “.bowerrc” file. Bower also offers a lot more configuration options, which are available at http://bower.io/docs/config/. Bower came to the rescue and now the overhead of maintaining the libraries under source control was no more. Though there are alternate schools of thought in maintaining everything under source control, I was somehow convinced for not maintaining them.
Grunt
Grunt is a javascript task runner which works majorly based on configuration. Grunt requires a Gruntfile.js file where all the required configurations are maintained. The documentation available at gruntjs.com gives a detailed information on creating gruntfile.js.
Install grunt via npm and add it as devDependency by running the below command.
npm install grunt --save-dev
To accomplish the most commonly required tasks like jshint, uglify, concatenate js/css files there are ready to use grunt packages. And of course, you are free to create your own custom grunt task within Gruntfile and invoke the same. The ones I have used are listed below. (consider these as a personal preference and not a recommendation)
- grunt-contrib-uglify – for minifying js/css files
- grunt-contrib-concat– for concatenating js/css files
- grunt-exec – for executing scripts (shell scripts, node scripts, windows commands)
- grunt-string-replace – for doing any pattern matching/replacements
- grunt-cordovacli – to trigger Cordova commands
Since all the required grunt dependencies are available as ‘npm’ packages add them under devDependencies section in the “package.json” file. Sample as follows
"devDependencies": { "cordova": "^4.2.0", "grunt": "^0.4.5", "grunt-cli": "^0.1.13", "grunt-contrib-concat": "^0.5.1", "grunt-contrib-uglify": "^0.8.0", "grunt-exec": "^0.4.6", "grunt-string-replace": "^1.0.0" }
Installing these dependencies is same as installing bower dependencies. Navigate to the application’s root directory and run the below command.
npm install
Next step is to create Gruntfile.js including all these various tasks which we wish to automate. Each and every grunt package’s documentation has sufficient information on how to configure them. The Gruntfile needs to be present inside the root directory of the application. The sample Gruntfile will look like the one below: (This is not a completely working one, indicative content only)
module.exports = function(grunt){ grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), uglify:{ minify_libs: { files: [{ expand: true, cwd: 'www/app/lib/js', src: '**/*.js', dest: 'www/app/lib/min/js' }], options: { banner: '\n/*! <%= pkg.name %> minified via Grunt uglify on <%= grunt.template.today("dd-mm-yyyy") %> */\n', preserveComments: 'some', report: 'min' } } }, concat: { options: { stripBanners: true, banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' + '<%= grunt.template.today("yyyy-mm-dd") %> */', }, dist: { src: [<Include your source files here>], dest: <Include the destination file name here>, }, //css concat css: { options: { keepBreaks: true, // whether to keep line breaks (default is false) debug: true, // set to true to get minification statistics under 'stats' property (see test/custom-test.js for examples) }, src: [<Include your source files here>], dest: '<Include destination file name here>' } }, 'string-replace': { dist: { files: { 'www/app/index.html': 'www/app/index.html', }, options: { replacements: [{ pattern: /lib\/js\//g, replacement: 'lib\/min\/js\/' }] } } }, exec : { install_bower_dep : 'bower install' } //load all the npm grunt tasks grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-string-replace'); grunt.loadNpmTasks('grunt-exec'); //register all the grunt tasks grunt.registerTask('default', ['exec:install_bower_dep', 'uglify:minify_libs', 'concat:css', 'string-replace:dist']); };
To see how everything works, navigate to the application’s root directory and run the below command.
grunt
This would trigger the tasks associated with the “default” task. i.e, with that single command, we should be able to accomplish the below tasks
- Install all the bower dependencies
- Minify all the JS files
- Concatenate all the css files
- Update the index.html page with the references of the minified versions
For a web application, the above tasks should be good enough to get started with build automation. And you could very well extend these to include tasks like jshint, running jasmine unit tests etc.,. In case, you are working on a cordova based hybrid application, we would still need to automate the process of adding platforms, plugins etc.,. This can be accomplished by making use of the grunt task (grunt-cordovacli). This would help us trigger the cordova commands like ‘Cordova platform add’, ‘cordova plugin add <plugin url>’ from grunt.
Another important concept to be aware of, while working with cordova based apps is the hooks that cordova inherently offers. We can tap into the appropriate hooks to accomplish the required tasks. For an example, we could make use of “after_platform_add” hooks to install all the required plugins for the application. An excellent post (which I followed to learn writing hooks) on writing cordova hooks is available here. Create the appropriate hook based on your requirement and trigger the parent cordova command from a grunt task. That’s it!! You could now have almost everything (from adding a platform, installing plugins, installing required bower dependencies, jshint, minifying, concatenating, automated unit tests etc.,) automated for your project.
This was just about my learning and experience in starting with automation, I would definitely be more interested to learn other many ways of doing it.