Skip to main content

Building Mobile Applications with Gulp

Apache Cordova can be used to compile HTML5 applications for iOS and Android. This technique allows a developer to easily deploy a static web app to multiple mobile devices. For many genres of application its a perfectly acceptable strategy, especially with javascript performance getting better and better. Personally I want to have cross platform deployment while avoiding the hassle of programming in ObjectiveC for iOS as well as Android’s dialect of Java.
AngularJS is a widely popular javascript framework for all sorts of interactive web apps. Including my own Party Sense project. It is very well suited for developing static website based mobile applications. Because angularjs is used all over the web it doesn’t have much specific support for mobile development, it can however be used to build up common mobile user interactions for example things like tabs, swipe cards etc.
To avoid having to start from scratch ionicframework bridges the gap between cordova and angularjs. Ionic is essentially a mobile-optimized library of HTML, CSS and JS components for building interactive apps with angularjs. The mentality seems very similar to the Python development world:
Develop once, deploy everywhere.
Although you can just throw your code straight into the www directory and run cordova build platform; I like to maintain a working development version of the mobile app and then compile/minimize/concatenate the source into the final product. The main tool I use for building web applications is gulp.js - the streaming build system for node.
Gulp like most build systems has the concept of tasks and dependencies. Here is an example task which depends on the bump and tag tasks:
gulp.task('release', ['bump', 'tag'], function () {
    console.log('Installing latest stable release of dependencies from bower');
    bower.commands.install();
});
Across a number of projects I have a few common tasks.

Minify and Concat

This task is the most important, it rewrites css and javascript paths found within html, minify and uglify things. Inside my entry point html file I have notation like the following:
<!-- build:css css/style.css -->
<link rel="stylesheet" href="css/stylesheet.css"/>
<!-- endbuild -->
This allows me to have my verbosely commented development css stylesheets split across multiple files and the build system will take the entire contents tagged with build:css and after any processing put the output into css/style.css. The processing I do uses the incredible gulp-usemin plugin in this task:
gulp.task('usemin', function(){
    gulp.src('app/*.html')
        .pipe(usemin({
            css: [minifyCss(), 'concat', rev()],
            html: [minifyHTML({empty: true})],
            vendorjs: [uglify(), rev()],
            js: [jshint.reporter('default'), uglify(), rev()]
        }))
        .pipe(gulp.dest('www/'));
});
Running this task copies any top level html documents across to my output directory and rewrites any js/css paths. The innocuous command rev() puts a unique revision on the generated javascript file to avoid all kinds of cache pain.
Compression and minification can act much better on the entire app, I usually aim to have no more than three javascript files. The first is the ionic.bundle.min.js which includes Ionic, AngularJS, and a few other angular modules. The second is vendor.js which is a squashed and concatenated file containing all the project dependencies. For example in one application I use underscore and jsbn, so the index.html file has the following section:
<!-- build:vendorjs vendor.js -->
<script src="lib/underscore/underscore.js"></script>
<script src="lib/jsbn/jsbn.js"></script>
<script src="lib/jsbn/jsbn2.js"></script>
<!-- endbuild -->
These unminified javascript libraries will get squashed together into something like vendor-4f7118c3.js, and the resulting index.html file will contain <script src=vendor-4f7118c3.js></script>.
I do the same type of thing for my application code, but this code also gets passed through jshint to let me know if I’m doing something wrong!
<!-- My Application Code -->
<!-- build:js app.js -->
<script src="js/app.js"></script>
<script src="js/filters.js"></script>
<script src="js/controllers.js"></script>
<script src="js/services/services.js"></script>
<script src="js/directives.js"></script>
<!-- endbuild -->

Angular Templates

During development there is no problem with creating a network request for every template, but it would add unnecessary overhead for a deployed app or website. The next task takes a folder of angular html templates and combines and compresses them. I add a 'templates' dependency to my main app:
var app = angular.module('myapp', [ ... , 'templates']);
Which would result in an error running in development mode, so I create a dummy app/templates.js file:
angular.module('templates', [])
.run(function($log){
    $log.info("Running in Development Mode");
});
The gulp templates task then creates a new templates.js file which contains all information for all templates in a much smaller single request.
gulp.task('templates', function () {
    gulp.src(['./app/templates/*.html'])
        .pipe(minifyHTML({ quotes: true }))
        .pipe(templates({filename: 'templates.js', root: 'templates', standalone: true}))
        .pipe(size({title: 'HTML fragments'}))
        .pipe(gulp.dest('www/'));
});

Images

Often full resolution images are too large, I use the following task to set a maximum width and optimize all my images. As image resizing and compression can take an appreciable time I only run when changed.
gulp.task('images', function () {
    "use strict";
    var DEST = 'www/img';
    gulp.src(paths.images)
        .pipe(changed(DEST))
        .pipe(imageResize({width: 1080}))
        .pipe(imagemin({optimizationLevel: 4, progressive: true, interlace: true}))
        .pipe(gulp.dest(DEST));
});

sass

Probably goes without saying but ionic uses sass and after tweaking any of their style you have to run a sass preprocessor.
gulp.task('sass', function (done) {
    gulp.src(paths.sass)
        .pipe(sass())
        .pipe(gulp.dest('./app/css/'))
        .pipe(minifyCss())
        .pipe(rename({ extname: '.min.css' }))
        .pipe(gulp.dest('./www/css/'))
        .on('end', done);
});

gulp.task('watch', function() {
  gulp.watch(paths.sass, ['sass']);
});

Library Copy

I seem to often end up with a copy operation for things like fonts and javascript maps. I usually define all the paths and copy them from app to www with this task:
gulp.task('libcopy', function () {
    // the base option sets the relative root for the set of files,
    // preserving the folder structure
    gulp.src(paths.libs, {base: 'app'})
        .pipe(gulp.dest('www/'));

    gulp.src('app/data/**/*', {base: 'app'})
        .pipe(gulp.dest('www/'));
});

Cordova Icon

cordova-icon is a node script for generating iOS and Android icons. I have a pre-build hook that cordova calls before building for iOS or Android.
Resize icon for all targets

Popular posts from this blog

Python and Gmail with IMAP

Today I had to automatically access my Gmail inbox from Python. I needed the ability to get an unread email count, the subjects of those unread emails and then download them. I found a Gmail.py library on sourceforge, but it actually opened the normal gmail webpage and site scraped the info. I wanted something much faster, luckily gmail can now be accessed with both pop and imap. After a tiny amount of research I decided imap was the better albiet slightly more difficult protocol. Enabling imap in gmail is straight forward, it was under labs. The address for gmail's imap server is: imap.gmail.com:993 Python has a library module called imaplib , we will make heavy use of that to access our emails. I'm going to assume that we have already defined two globals - username and password. To connect and login to the gmail server and select the inbox we can do: import imaplib imap_server = imaplib . IMAP4_SSL ( "imap.gmail.com" , 993 ) imap_server . login ( use...

Bluetooth with Python 3.3

Since about version 3.3 Python supports Bluetooth sockets natively. To put this to the test I got hold of an iRacer from sparkfun . To send to New Zealand the cost was $60. The toy has an on-board Bluetooth radio that supports the RFCOMM transport protocol. The drive  protocol is dead easy, you send single byte instructions when a direction or speed change is required. The bytes are broken into two nibbles:  0xXY  where X is the direction and Y is the speed. For example the byte 0x16 means forwards at mid-speed. I was surprised to note the car continues carrying out the last given demand! I let pairing get dealt with by the operating system. The code to create a  Car object that is drivable over Bluetooth is very straight forward in pure Python: import socket import time class BluetoothCar : def __init__ ( self , mac_address = "00:12:05:09:98:36" ): self . socket = socket . socket ( socket . AF_BLUETO...

Matplotlib in Django

The official django tutorial is very good, it stops short of displaying data with matplotlib - which could be very handy for dsp or automated testing. This is an extension to the tutorial. So first you must do the official tutorial! Complete the tutorial (as of writing this up to part 4). Adding an image to a view To start with we will take a static image from the hard drive and display it on the polls index page. Usually if it really is a static image this would be managed by the webserver eg apache. For introduction purposes we will get django to serve the static image. To do this we first need to change the template. Change the template At the moment poll_list.html probably looks something like this: <h1>Django test app - Polls</h1> {% if object_list %} <ul> {% for object in object_list %} <li><a href="/polls/{{object.id}}">{{ object.question }}</a></li> {% endfor %} </ul> {% else %} <p>No polls...