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

Driveby contribution to Python Cryptography

While at PyConAU 2016 I attended the Monday sprints and spent some time looking at a proposed feature I hoped would soon be part of cryptography. As most readers of this blog will know, cryptography is a very respected project within the Python ecosystem and it was an interesting experience to see how such a prominent open source project handles contributions and reviews.

The feature in question is the Diffie-Hellman Key Exchange algorithm used in many cryptography applications. Diffie-Helman Key Exchange is a way of generating a shared secret between two parties where the secret can't be determined by an eavesdropper observing the communication. DHE is extremely common - it is one of the primary methods used to provide "perfect forward secrecy" every time you initiate a TLS connection to an HTTPS website. Mathematically it is extremely elegant and the inventors were the recipients of the 2015 Turing award.

I wanted to write about this particular contribution because man…

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…

Markdown Editor Component for Angular2

Thought I'd share a component I've been hacking on for angular2: a syntax highlighted markdown editor with rendered preview.

The code including a basic example is available on github. Because Angular2 hasn't yet been released this is really just me kicking the tyres.



This component relies on two libraries:

- marked for rendering markdown as html
- and ace editor for editing markdown
Basic Usage Example Add to your html template:
<markdown-editor (save)="updatedText($event)" [initial-text]="markdownContent"></markdown-editor> Remember to include the Markdowndirective in your @Component annotation:
@Component({ selector:'about', directives: [CORE_DIRECTIVES, Markdown] }) Another Example You can also control the component with external ui:
<button (click)="md.editMode = true">Custom Edit Button</button><markdown-editor [initial-text]="myMarkdownText" [show-edit-but…