Gulp tasks (3/4 steps to properly set mobile app project using Ionic framework)
Welcome back to blog series about organising Ionic mobile project. Last time we were talking about organising Angular code and making maintainable and modular JavaScript file structure.
If you are new with this topic, check previously published posts:
- Four steps to properly set mobile app project using Ionic framework – Introduction
- Blank Ionic mobile project setup (1/4 steps to properly set mobile app project using Ionic framework)
- Organize AngularJS code (2/4 steps to properly set mobile app project using Ionic framework)
Now it’s time to minimize and bundle all code we have written so far so we could ship it to the browser as faster as it can. For this purpose we’ll use Gulp. Today everyone knows what Gulp is and what could be achieved with it. In this section we’ll discuss which Gulp tasks are helpful to bring out the best from performance from Ionic mobile application. To simplify gulpfile.js
we’ll define paths for all files we’ll be dealing with so once we change project folder structure this is the only place to make changes in order to Gulp continue work properly.
var paths = {
sass: ['./scss/app.scss'],
html: ['./www/build/index.html'],
templates: ['./www/js/**/*.html', './www/templates/**/*.html'],
vendor: ['./www/lib/ionic/js/ionic.bundle.js'],
js: ['./www/js/app.js']
};
gulp-sass, gulp-minify-css, yargs, gulp-rename and gulp-if
For now gulpfile.js
contains only gulp-sass
task which came with Ionic template by default. This task converts SCSS files into CSS which are readable for browser. This is fine but we want something more. While converting SCSS file into CSS it would be nice to minify it at the same time and also do some additional stuff so we don’t need to create more Gulp tasks but do all in one. For minification purpose we’ll use gulp-minify-css
. Minification reduces file size but it’s not suitable for development so we need a way to differentiate development environment from production. We can achieve this with yargs
by passing –flag in command line. Also the production version of CSS file will go in separate file and we’ll add .min.css suffix to differentiate it from development version. We’ll introduce another task gulp-rename
. The last thing we need is a task by which we can determine if the production flag has been set, and that’s gulp-if
. With all these tasks mentioned, here’s how it would look in action.
Command line
gulp build-css --production
gulpfile.js
gulp.task('build-css', function() {
console.log('build-css STARTED');
gulp.src(paths.sass)
.pipe(sass({errLogToConsole: true}))
.pipe(gulpif(argv.production, minifyCss({keepSpecialComments: 0})))
.pipe(gulpif(argv.production, rename({extname: '.min.css'})))
.pipe(gulp.dest('./www/css/'))
.on('end', function() {
console.log('build-css DONE');
});
});
In just a few lines of code we did a lot, and that’s the beauty of Gulp. We also added two console.log()
commands to track Gulp workflow in console more easily.
browserify, vinyl-source-stream, gulp-uglify and gulp-concat
CSS has been handled and now it’s time to jump to JavaScript. We mentioned earlier that we’ll bundle all JavaScript files into one using browserify. Browserify will take care of dependency injection we specified in the code and create one nice file which will be injected in the DOM. For this purpose we’ll use browserify and vinyl-source-stream npm packages.
Basically you can say that vinyl-source-stream convert the readable stream you get from browserify into a vinyl stream that is what gulp is expecting to get.
vinyl stream is a Virtual file format, and it is fundamental for Gulp. Thanks to vinyl streams Gulp doesn’t need to write a temporal file between different transformations. And this is one of the main advantages it have over Grunt.
gulpfile.js
gulp.task('build-js', function() {
console.log('build-js STARTED');
return browserify(paths.js)
.bundle()
.pipe(source('app.bundle.js'))
.pipe(gulp.dest('./www/js/bundles/'))
.on('end', function() {
console.log('build-js DONE');
});
});
Ok, the bundle is created but we want to uglify it for production. We’ll create a separated task for this purpose.
gulpfile.js
gulp.task('uglify', function() {
if (argv.production) {
console.log('uglify STARTED');
return gulp.src('./www/js/bundles/app.bundle.js')
.pipe(uglify())
.pipe(rename({suffix: '.min'}))
.pipe(gulp.dest('./www/js/bundles/'))
.on('end', function() {
console.log('uglify DONE');
});
}
});
Uglify task will be executed only if the –production flag has been set. We can check the passed arguments (from console) with normal if condition as well and not only using gulp-if task as we did in build-css task from above. This is very flexible and helpful while preparing the application for different environments.
The project source JavaScript is prepared, for both environments. Next we have to do is to bundle vendor scripts (ionic and angular dependencies). We’ll separate it from source files because it will be downloaded by gulp install task from Ionic repository and we’ll always need the latest version. This is pretty simple and we’ll use gulp-concat to achieve this.
gulpfile.js
gulp.task('build-vendor', function() {
console.log('build-vendor STARTED');
return gulp.src(paths.vendor)
.pipe(concat('vendor.js'))
.pipe(rename({suffix: '.min'}))
.pipe(gulp.dest('./www/js/bundles/'))
.on('end', function() {
console.log('build-vendor DONE');
});
});
gulp-preprocess
We generated different bundles for development and production environment but the question is how to inject them into index.html file automatically. gulp-preprocess
is a package which looks into html and javascript files, processes it and removes lines of code which do not match current context or environment configuration. Here’s the example:
We’ll have one html file build/index.html
where we define conditions statements inside the html comment tags. Gulp will process that file, compile it and create new index.html with all javascript/css snippets for specified environment.
build/index.html
<!-- @if ENVIRONMENT='development' -->
<link href="css/app.css" rel="stylesheet">
<!-- @endif -->
<!-- @if ENVIRONMENT='production' →
<link href="css/app.min.css" rel="stylesheet">
<!-- @endif -->
<!-- @if ENVIRONMENT='development' -->
<script src="js/bundles/app.bundle.js"></script>
<!-- @endif -->
<!-- @if ENVIRONMENT='production' -->
<script src="js/bundles/app.bundle.min.js"></script>
<!-- @endif -->
command line
gulp build-html --production
gulpfile.js
gulp.task('build-html', function() {
console.log('build-html STARTED');
gulp.src(paths.html)
.pipe(preprocess({context: {ENVIRONMENT: argv.production ? 'production' : 'development'}}))
.pipe(gulp.dest('./www/'))
.on('end', function() {
console.log('build-html DONE');
});
});
This is great but we want more, as always. It would be nice to process javascript as well and have different javascript code based on environment, for example adoptable project configuration file (API endpoint etc). We’ll create www/js/config.js
file, with required conditions, which will compiled into www/js/common/config.js
.
www/js/config.js
var configModule = angular.module('App.Config');
configModule.constant('AppConfig', {
// @if ENVIRONMENT == 'production'
apiEndpoint: 'production-domain.com/api-endpoint'
// @endif
// @if ENVIRONMENT == 'development'
apiEndpoint: 'development-domain.com/api-endpoint'
// @endif
});
command line
gulp preprocess-js --production
www/js/common/config.js
configModule.constant('AppConfig', {
apiEndpoint: 'production-domain.com/api-endpoint'
});
gulpfile.js
gulp.task('preprocess-js', function() {
console.log('preprocess-js STARTED');
gulp.src('./www/js/config.js')
.pipe(preprocess({context: {ENVIRONMENT: argv.production ? 'production' : 'development'}}))
.pipe(gulp.dest('./www/js/common/'))
.on('end', function() {
console.log('preprocess-js DONE');
});
});
gulp-angular-templatecache
This is a very simple task which says: “I don’t want to download every template from the server. I want to cache it in JavaScript file instead and reduce the network traffic which can gain performance and speed up my app”.
We’ll need to prepare some stuff to make this work. By default gulp-angular-templatecache
uses the Angular module templates to populate $templateCache
and assumes the templates module is already defined. So we’ll create new empty file www/js/utility/templates.js
and in app.js
instantiate the module.
app.js
angular.module('templates', []);
require('./utility/templates');
Also, we need to change templateUrl
in the routes definition, to use templates from templates module and not from www/js/my-templates
path (Mentioned in the previous post of this tutorial under the App structure section).
app.js
appModule.config([
'$stateProvider',
'$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('app', {
url: "/app",
abstract: true,
templateUrl: 'common/templates/menu.html',
controller: 'CommonCtrl'
})
.state('app.playlists', {
url: '/playlists',
views: {
'menuContent' :{
templateUrl: 'playlist/templates/playlist.html',
controller: 'PlaylistCtrl'
}
}
})
.state('app.single', {
url: '/playlists/:playlistId',
views: {
'menuContent': {
templateUrl: 'playlist/templates/playlistDetails.html',
controller: 'PlaylistDetailsCtrl'
}
}
});
$urlRouterProvider.otherwise('/app/playlists');
}
]);
reinstall-plugins
This is custom task which reads package.json file and installs all plugins listed under cordovaPlugins
object. This way we can easily keep a list of plugins required for the project and install them when new developer joins the project or when switching on new machine. The more words about this will be in the next tutorial where we’ll be talking about Cordova hooks.
gulpfile.js
gulp.task('reinstall-plugins', function() {
console.log('reinstalling ionic plugins...');
var pluginlist = [
"com.ionic.keyboard",
"cordova-plugin-whitelist"
];
// no need to configure below
var fs = require('fs');
var path = require('path');
var sys = require('sys')
var exec = require('child_process').exec;
function puts(error, stdout, stderr) {
sys.puts(stdout)
}
pluginlist.forEach(function(plug) {
exec("ionic plugin add " + plug, puts);
});
});
That’s it. Our project is much more optimized for production and automated for development. Let’s summarize what we have achieved in this chapter:
- generate CSS files from SCSS
- bundle multiple JavaScript files into one
- minify and uglify code
- distinguish development from production environment
- create html and JavaScript files on the fly, based on environment flag
- cache angular templates to reduce network traffic
- reinstall all plugins in case something got stuck in plugin section
There’s only one final step to fulfill this story about making sustainable and stable Ionic app and ship it to production. Next time we’ll talk about Ionic hooks and see how they can help us in daily development process and how to use them on the right way.
Got a project?
Let's talk.
Like what you see?
Or have a project, idea, requirement or scope.