Sunday 5 October 2014

Use Grunt to Minify your AngularJs Files

When my company decided to rebuild our primary product last year it allowed us devs to go crazy and attempt to implement as many best practices as possible. As part of this we wanted to use SASS so we needed something that would allow us to transpile and minify our code. As we had gone that far we thought we might as well minify our JavaScript files as well. I'm sad to say that we didn't go so far as to minify our HTML, and we didn't compress anything either. However, we did want to use a task runner that would do all this for us and so we started using Grunt.
There's a lot of discussion in the community at the moment about whether Grunt or Gulp is the best tool to be using. I only have any experience of using Grunt. From what I've read I think that this might change in the future as Gulp does appear to be more powerful, but for now I'm sticking with what I know.

In these days of mobile web we're all thinking about how we can reduce our payload down to phones that are using connections which users are paying for by the Gb, or even worse sometimes by the Mb. I watch friends select what things they do on their phones as they have a gig or half a gig of data that they can use for the month. The smaller we can make our code the better it is for our end user. Not only does it help them not eat through their data allowance, but it should also deliver our site to them quicker.

A Note about Minification and AngularJs

AngularJs has dependancy injection. When you declare a controller (or service, or directive, etc.) you tell Angular which other services you require in your code. Something a bit like this:

App.controller('MyController', function($rootScope, $timeout) {
    //do something
});


Then when this gets minified you end up with this:

App.controller('MyController',function(a, b){});

Disaster! How is Angular meant to know what this means? Thankfully this was thought about and a syntax has been created that allows you to get around this problem when minifying. You have to write the above code in this manner:

App.controller('MyController', ['$rootScope', '$timeout', function($rootScope, $timeout) {
    //do something
}]);


This minified looks like this:

App.controller('MyController',['$rootScope','$timeout',function(a,b){}]);

By placing your dependancies as strings in an array with the function that makes up your controller (service, directive...) then you tell Angular which services to replace the minified parameter names with. It is imperative that you keep the order of the strings and the parameters the same, as Angular will replace these in that order. If something is out of order then the wrong service will be called within your function and your code will break.

Setting up Grunt Configuration

In the root of your project you need to create one file called gruntfile.js and another called package.json. The package.json file describes your project and everything about it. It's inherently from Node, and has some features that are required. You must include the name and version of your project. There are certain formats that these things should be in and you can read more about package.json here and gruntifle.js here.

Grunt is built on top of Node, so is really quick and easy to install if you have already have Node installed. I'm also using a few of the Grunt contrib packages, which are officially supported by Grunt. I use Uglify for JavaScript minification, Htmlmin for HTML minification, and Watch to run the others on the fly. First of all you need to install these on the command line from the root folder of your project:

npm install grunt --save-dev
npm install grunt-contrib-uglify --save-dev
npm install grunt-contrib-htmlmin --save-dev
npm install grunt-contrib-watch
 --save-dev

By adding the --save-dev to the end of these commands each one will be automatically stored in your package.json file, which should result in your file looking something like this:

{
    "name": "my-app
    "version": "0.1.0",
    "devDependencies": {
        "grunt": "^0.4.5",
        "grunt-contrib-uglify": "~0.5.0",
        "grunt-contrib-htmlmin": "~0.6.0",
        "grunt-contrib-watch": "~0.6.1"
    }

}

gruntfile.js

The gruntfile.js file is where things become more interesting. This is where the configuration for the grunt packages goes. The wrapper function of module.exports is mandatory, and most grunt plugins will expect their config to be within grunt.initConfig within this. pkg is the lcoation of your package.json file, so up to this point your gruntfile.js should look just like mine:

module.exports = function(grunt) {

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),


The next properties in the grunt.initConfig will be specific to each plugin that you install for Grunt. First off I have Uglify, which minifies my JavaScript files. In the example below options.banner is a string that is prepended to the output file. I use the package name specified in my package.json file and todays date in the given format within comment tags and then add a new line for the minified code to reside on. In the build object I specify the path of my source files (every .js file in every directory in scripts/source) and where I want my minified output to be stored. In this file name I again use a placeholder tag to use my package name.

        uglify: {
            options: {
                banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
            },
            build: {
                src: 'scripts/source/**/*.js',
                dest: 'scripts/release/<%= pkg.name %>.min.js'
            }
        },


Next I have the configuration for Htmlmin, which I use to minify my HTML files. I've taken a pretty standard configuration for this. In dist.options I've chosen to remove comments and collapse whitespace. In dist.files you need to list the files that you compressed in a 'release': 'source' format. This is an object, so put in as many or as few as you like.

        htmlmin: {
            dist: {
                options: {
                    removeComments: true,
                    collapseWhitespace: true
                },
                files: {
                    'releaseFile1.html': 'sourceFile1.html',
                    'releaseFile2.html': 'sourceFile2.html'
                }
            }
        },


Lastly there is the Watch configuration. I use Watch so that when my files change it automatically runs the above tasks. This is great for me as I'm always forgetting to run theses tasks before I run my Protractor tests, and then wonder why things pass/fail when I'm not expecting them to. In scripts.files I detail the files that should be watched and trigger the tasks that are detailed in scripts.tasks. Both of these are string arrays. scripts.options.spawn is set to false so that Watch doesn't send the tasks to be run in new processes, which makes things a little bit faster.

        watch: {
            scripts: {
                files: ['scripts/app/**/*.js', 'html/**/*.html'],
                tasks: ['uglify', 'htmlmin'],
                options: {
                    spawn: false
                }
            }
        }
    });


Finally you need to load the Grunt plugins that you've configured above, and then register tasks to be run. In all three cases here I'm running the default task for each one.

    // Load the plugins that provide the tasks
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-htmlmin');
    grunt.loadNpmTasks('grunt-contrib-watch');

    // Default tasks
    grunt.registerTask('default', ['uglify']);
    grunt.registerTask('default', ['htmlmin']);
    grunt.registerTask('default', ['watch']);

};


In WebStorm 8 native support for Grunt was introduced. Once you've saved the files above open the project pane, right click on gruntfile.js, and click Open Grunt Console. You should see a new pane open that look something like this:


I rarely use any of the buttons on the left, apart from refresh when I add a new plugin to my file. The gruntfile.js pane allows you to either run all tasks, or just one. When you hover over each of the tasks a small green arrow will appear to the right of that task; by clicking it WebStorm will run that task and show the output in the tabbed pane to the right. I tend to just run watch as it is going to run the others, and then forget about it until the next time I re-open the project. As any files are dynamically created you'll find them appear in the project files pane in WebStorm. You should make sure that any references in your project files are to your production files, and not your source files.

No comments:

Post a Comment