Grunt and Gulp

How to Minify an Ionic Application for Production



·

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.

BONUS CONTENT: Enter your email address below to download the entire Gruntfile.js, gulpfile.js, index.html and ionic.project files used in this tutorial

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.

What to watch next...

  • Pingback: 100 Ionic Framework Resources()

  • Todd Hale

    Stuck on #6 creating a Gruntfile.js. Downloaded sample gruntfile (though not interested in phonegap build). Getting error:
    >> Destination www/dist/dist_js/app.min.js not written because src files were empty.
    >> No files created.

    And this disqus edit box stinks. Frequently gets in a funk and can’t edit.

    • Hi Todd, did you run ‘ionic serve’ before hand? You need to run that and make sure all the Gulp tasks have run (which will create the app.min.js file) before executing the Grunt tasks. Also, if you’re using PhoneGap and not PhoneGap Build then you should check out this post, as Grunt isn’t needed at all: http://www.joshmorony.com/how-to-minify-an-ionic-application-using-gulp-and-cordova-hooks/

      • Todd Hale

        Gotcha. Sounds great. I’ll try the non-PGBuild version. Thanks for the help!

  • metric152

    I’m having trouble with step 4. I need to include the template.js file before my app.js file, but I have to include it after the ionic.bundle.js and that’s in the section. If I copy the templates.js file with it’s comments inside of the build breaks.



    Can you help me with the order?

  • metric152

    I was able to make this work but now I’m having an issue getting with the icon fonts. Nothing is showing up in the dist folder. Is there a gulp command to copy files to the destination?

  • Pingback: What's the Difference Between Ionic and AngularJS? | HTML5 Mobile Tutorials | Ionic, Phaser, Sencha Touch & PhoneGap()

  • Mút Mập Mạp

    Hi, i wonder how we exclude all original source code from /www folder, means I want to include the minified files only. Thank you.

  • Rahul Varadharajan

    upon installing “npm install templatecache –save-dev” i dint get any dist folder or template.js file. Then how can put script line in index.html file of . i Have searched every where i couldnt find that file. templatecache dependency is available in project,json

  • Warner

    I think this is for ionic 1. How do we do this in ionic 2?

  • Taehwa

    If you guys see an error ‘useref.assets is not a function’, check out this https://github.com/jonkemp/gulp-useref/issues/153

    • Zach Lyon

      Thanks for that tip!

      “`
      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);
      });

      “`

  • shruthi

    hi.help me.stuck on #4 how the app.min.js file creating in dist_js folder as single file.can you please explain

    • Walter Montes

      I’m having this problem too

    • Walter Montes

      It is because you are missing this comments on your html file

      ….

      Remove the spaces

  • beaver

    Hi, i followed all the steps and before i run the gulp taks i first want to run as ionic serve but doing so i get the following error saying that it can not find module ‘gulp-util’. how to solve this and what went wrong?

  • Ladna Meke

    gulp-useref doesn’t concatenate my fonts

  • Joseph Freemaker

    Steps to convert all *.html templates into AngularJS templates in $templateCache appear straight forward. However no templates.js file is created when ionic server is run. Why?
    Did the following:
    1. Ran: npm install gulp-angular-templatecache –save-dev
    2. gulpfile.gs was edited to contain the five additions for templateCache
    3. Added to index.html in front of
    4. Added ‘templates’ to angular.module.
    5. ‘ionic serve’ command produces no templates.js file containing the *.html file located at www/templates.
    Any help would be appreciated.

    • Dede Hamzah

      Run `gulp` first to generate that.

  • Fayez Abu Helow

    Gruntfile.js does not exist in bonus page, any help please

  • Sahil Daga

    Hey Josh, Gulpfile.js have been removed by ionic-app-scripts.Can you please update the tutorial.
    Thanks a ton for all the great tutorials.

  • dev2 User1

    Hi Josh, great tutorial. Can you make one for Ionic 3?