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

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 are available.</p> …

My setup for downloading & streaming movies and tv

I recently signed up for Netflix and am retiring my headless home media pc. This blog will have to serve as its obituary. The box spent about half of its life running FreeNAS, and half running Archlinux. I’ll briefly talk about my experience with FreeNAS, the migration, and then I’ll get to the robust setup I ended up with.

The machine itself cost around $1000 in 2014. Powered by an AMD A4-7300 3.8GHz cpu with 8GB of memory. A SilverStone DS380 case is both functional, quiet and looks great. The hard drives have been updated over the last two years until it had a full compliment of 6 WD Green 4TiB drives - all spinning bits of metal though.

Initially I had the BSD based FreeNAS operating system installed. I had a single hard drive in its own ZFS pool for TV and Movies, and a second ZFS pool comprised of 5 hard drives for documents and photos.

FreeNAS is straight forward to use and setup, provided you only want to do things supported out of the box or by plugins. Each plugin is install…

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:

importimaplibimap_server=imaplib.IMAP4_SSL("imap.gmail.com",993)imap_server.login(username,password)imap_server.select(…