Tutorial hero
Lesson icon

How to Minify an Ionic 1.x Application for Production

Originally published May 13, 2015 Time 10 mins

Whilst you’re building an application with Ionic, it’s great to have code separated out into separate files with plenty of white space to increase readability. This makes it much easier to work on, but when we’re ready to submit the application to App Stores we don’t need it to be readable anymore (machines are much better at reading code than we are).

So before you build the final production ready version of your Ionic application, you might want to first minify it. Minifying code is essentially the process of stripping away everything unnecessary: everything is condensed onto a single line, files are concatenated together, variables are renamed to be shorter and so on. There’s two main reasons you might want to do this:

  1. To reduce the file size of your resulting application and increase performance
  2. Obfuscate your code so that prying eyes can’t obtain your source code (something which is quite possible in PhoneGap applications)

Ionic has no automated process to do this but it’s relatively simple to set up your own process using Gulp and Grunt. A lot of this is based off of this tutorial by Agustin Haller which was tremendously helpful to me. The only problem I had with it though is that it relies on cordova hooks, and if you’re using PhoneGap Build to build your application then you won’t be able to use these.

We are going to use a few different tools here, but the overall process we are trying to achieve is:

  1. Convert all .html templates into AngularJS templates in $templateCache (gulp-angular-templatecache)
  2. Prevent dependency injection from breaking when the app is minified (since variables get renamed) (gulp-ng-annotate)
  3. Concatenate files (gulp-useref)
  4. Minify / Obfuscate the code (grunt-contrib-uglify)

Part of this we will build into Ionic so that the process is executed every time we make a change in the app (whilst ionic serve is running) and the rest we will achieve with a grunt task. In a previous post I discussed how to use grunt to automatically upload apps to PhoneGap Build and we will be building on that.

UPDATE: If you want to use plain old PhoneGap instead of PhoneGap Build make sure you check out this post as well.

1. Install the dependencies

Before we can get started you will need to install all the Gulp and Grunt tools that we want to use. To do that run the following commands from within your project directory:

npm install gulp-angular-templatecache --save-dev
npm install gulp-ng-annotate --save-dev
npm install gulp-useref --save-dev
npm install grunt-contrib-uglify --save-dev
npm install grunt-contrib-compress --save-dev

NOTE: By adding –save-dev onto the end this will add the tool as a dependency in your package.json file

2. Modify your Gulp file

When you create an Ionic application a file called gulpfile.js is automatically created. This handles some automatic tasks like watching your app and building your css files with SASS when necessary. We’re going to build a few more tasks on top of that to do the things we want by using gulp-angular-templatecache, gulp-ng-annotate and gulp-useref.

To do that we will need to make a few modifications to gulpfile.js

  • Require the three gulp tools we are using at the top of the file:
var templateCache = require('gulp-angular-templatecache');
var ngAnnotate = require('gulp-ng-annotate');
var useref = require('gulp-useref');

You will also need to modify the paths array so that Gulp knows where you want stuff to happen.

  • Modify the paths array to reflect the following:
var paths = {
  sass: ['./scss/**/*.scss'],
  templateCache: ['./www/templates/**/*.html'],
  ng_annotate: ['./www/js/*.js'],
  useref: ['./www/*.html'],
};

You may need to modify these paths depending on the way your application structure is set up, but for a “normal” Ionic setup this should work.

  • Add three new Gulp tasks to the end of the gulp file:
gulp.task('templatecache', function (done) {
  gulp
    .src('./www/templates/**/*.html')
    .pipe(templateCache({ standalone: true }))
    .pipe(gulp.dest('./www/js'))
    .on('end', done);
});

gulp.task('ng_annotate', function (done) {
  gulp
    .src('./www/js/*.js')
    .pipe(ngAnnotate({ single_quotes: true }))
    .pipe(gulp.dest('./www/dist/dist_js/app'))
    .on('end', done);
});

gulp.task('useref', function (done) {
  var assets = useref.assets();
  gulp
    .src('./www/*.html')
    .pipe(assets)
    .pipe(assets.restore())
    .pipe(useref())
    .pipe(gulp.dest('./www/dist'))
    .on('end', done);
});
  • Since we want these tasks to run automatically whenever there is a change in the application, we also have to add them to the existing watch task:
gulp.task('watch', function () {
  gulp.watch(paths.sass, ['sass']);
  gulp.watch(paths.templatecache, ['templatecache']);
  gulp.watch(paths.ng_annotate, ['ng_annotate']);
  gulp.watch(paths.useref, ['useref']);
});
  • and finally we will also need to add them to the default gulp task:
gulp.task('default', ['sass', 'templatecache', 'ng_annotate', 'useref']);

3. Modify your ionic.project file

You will need to add the same tasks we just created to the ”gulpStartupTasks” option in your ionic.project file. The end result should look like this:

{
  "name": "MyApp",
  "app_id": "b4v4v4v4",
  "gulpStartupTasks": [
    "sass",
    "templatecache",
    "ng_annotate",
    "useref",
    "watch"
  ],
  "watchPatterns": [
    "www/**/*",
    "!www/lib/**/*"
  ]
}

4. Modify your index.html file

There are also a few changes that are required to your index.html file. First, you will need to include the templates.js file that is created using gulp-angular-templatecache.

  • Add the following to your index.html file before you include your app.js file:
<script src="dist/dist_js/app/templates.js"></script>
  • An important change that prevents breaking dependency injection is to add the ng-strict-di directive on whatever tag, likely the body tag, you specify your application on:
<body ng-app="MyApp" ng-strict-di>
  • Finally, to concatenate the files you are including in index.html you will need to modify them to look like this:
<!-- build:css dist_css/styles.css -->
    <link href="css/style.css" rel="stylesheet">
    <link href="css/ionic.app.css" rel="stylesheet">
    <!-- endbuild -->

    <!-- build:js dist_js/templates.js -->
    <script src="dist/dist_js/app/templates.js"></script>
    <!-- endbuild -->

    <!-- build:js dist_js/app.min.js -->
    <script src="dist/dist_js/app/app.js"></script>
    <script src="dist/dist_js/app/controllers.js"></script>
    <script src="dist/dist_js/app/services.js"></script>
    <!-- endbuild -->

This will create a single file called styles.css in the dist_css folder, and a single file called app.min.js in the dist_js folder. If you open up your built index.html file (the one located inside of the dist folder) you will notice that the references are changed to include just the single concatenated file.

5. Modify references to your templates

After using gulp-angular-templatecache we can no longer refer to our templates like this:

$stateProvider
  .state('intro', {
    url: '/',
    templateUrl: 'templates/intro.html',
    controller: 'IntroCtrl',
  })
  .state('main', {
    url: '/main',
    templateUrl: 'templates/main.html',
    controller: 'MainCtrl',
  });

Instead you will need to modify them so that they only reference the file name, like this:

$stateProvider
  .state('intro', {
    url: '/',
    templateUrl: 'intro.html',
    controller: 'IntroCtrl',
  })
  .state('main', {
    url: '/main',
    templateUrl: 'main.html',
    controller: 'MainCtrl',
  });

You will also need require the templates module that is generated in your angular module in app.js:

angular.module('MyApp', ['ionic', 'controllers', 'services', 'templates']);

6. Minify / Uglify the Code

Now we will be using a file in the root of our project called Gruntfile.js to run uglify (which I think makes it sound like I’m casting some Harry Potter spell on my code). For more background on grunt, take a look at this post, as we will be building on top of that. You can also just download the entire file in the bonus content box below.

  • Add the following to your grunt files initConfig:
uglify: {
      my_target: {
        files: {
          'www/dist/dist_js/app.min.js': ['www/dist/dist_js/app.min.js']
        }
      }
    },

This will take the app.min.js file we previously created and create a new minified and uglified app.min.js file. Now you can run the uglify process by running:

grunt uglify

from the command line.

7. Create a .zip package of the built files

Before sending the app off to PhoneGap Build we first need to create a compressed app.zip package containing all the minified files we created. So just as we did above we’ll be creating another grunt task, but this time I’m going to specify two different options, debug and release:

compress: {
      debug: {
        options: {
          archive: 'app.zip'
        },
        files: [
          {expand: true, src: ['**/*'], cwd: 'www/'}
        ]
      },
      release: {
        options: {
          archive: 'dist/app.zip'
        },
        files: [
          {expand: true, src: ['dist_js/app.min.js'], cwd: 'www/dist/'},
          {expand: true, src: ['dist_js/templates.js'], cwd: 'www/dist/'},
          {expand: true, src: ['dist_css/*'], cwd: 'www/dist/'},
          {expand: true, src: ['lib/**'], cwd: 'www/'},
          {expand: true, src: ['images/**'], cwd: 'www/'},
          {expand: true, src: ['resources/**'], cwd: 'www/', dot: true},
          {expand: true, src: ['config.xml'], cwd: 'www/'},
          {expand: true, src: ['index.html'], cwd: 'www/dist/'}
        ]
      }
   }

If I’m debugging an application I don’t want to minify before building because it makes it almost impossible to debug, so with the options above I can either zip up the minified files by running:

grunt compress:release

or I can zip up the unminified files by running:

grunt compress:debug

If you are using the debug version it just zips up everything in the www folder. But if you use the release version then it only zips up the specific files you want, so if you add new files or folders that need to be included in your final build make sure that you add them in here as well. I’ve added an images and resources folder for example.

8. Create a grunt task to run all of these commands

You don’t want to have to individually run:

grunt uglify
grunt compress:release
grunt phonegap-build:release

every time you want to create a build. So add the following to your grunt file (after the default task):

grunt.registerTask('build-app-release', [
  'uglify',
  'compress:release',
  'phonegap-build:release',
]);
grunt.registerTask('build-app-debug', [
  'compress:debug',
  'phonegap-build:debug',
]);

NOTE: Remember, if you want to include the upload to PhoneGap Build then make sure you follow this post as well. Otherwise, just remove the phonegap-build commands above.

Now we can simply run grunt build-app-release or grunt build-app-debug to run through all the necessary tasks.

That’s it!

Ok, that was quite a lot of work but now you can automatically build, minify and upload your application to PhoneGap Build all by executing one simple command.

It’s likely that you will need to fiddle around with this process a bit to get it to suit you, so if you have any trouble feel free to leave a comment below.

Learn to build modern Angular apps with my course