Sunday 22 February 2015

Use ng-annotate with Grunt in WebStorm

After my last post on using $inject for protecting your dependancy injection against minification I did some further reading on ng-annotate. In his style guide, John Papa says that he uses $inject because it mirrors the technique used by ng-annotate, which he uses to automatically create minification safe dependancies and here is how you can too.

What It Does

ng-annotate parses through the code files you tell it about and looks for anywhere you've created dependancy injection like this:

function Controller($scope, $http, someOtherService){}

When it spots this it then uses AngularJs' $inject to create dependancy injection protection so that when you minify your JavaScript files the references aren't lost. It does this by creating a line of code for each set of dependancies like this:

Contoller.$inject = ['$scope', '$http', 'someOtherController'];

How to Make It Happen

First, you need to have Grunt installed in your project. If you need some help with this, you can check out my post on using Grunt for minification. We'll be building upon that post to run ng-annotate. There is a way to do this with Gulp, but I haven't made the move from Grunt to Gulp yet. If you want to use this with Gulp, check out how to in John Papa's AngularJs style guide. Once you have Grunt installed, you need to run

npm install grunt-ng-annotate --save-dev

on the command line within your project's directory.

In your gruntfile you need to include the options for ng-annotate inside grunt.initConfig:

ngAnnotate: {
    options: {
        remove: true,
        add: true,
        singleQuotes: true
    },
    app: {
        files: {
            'scripts/app.js': 'scripts/app.js'
        }
    }
}


The first object - options - has three properties. remove is a boolean that tells ng-annotate whether or not to remove any existing $injects from your code. I set this to true because I know that on occasion I introduce bugs into my code by being distracted half way through typing something, and then not returning to it. I end up with a half typed word somewhere that causes my script to error. By allowing ng-annotate to remove any $injects it ensures that (for this line of code at least) there are no typos. It also means that if you've updated your dependancies you only have to do it in one place, and Grunt can take care of the rest of it.

add is also a boolean, but this time it tells ng-annotate that it should insert $injects into your code. This is the primary reason for having ng-annotate in Grunt so set it to true. When it is, ng-annotate parses your code files and finds any instances where you've used dependancy injection and creates an $inject for it.

I prefer to use single quotes for my strings in JavaScript, as this allows you to write HTML attributes without having to escape the double quotes that surround their values. Most of the time I don't write any HTML in my JavaScript, but for those occasions that I do I prefer to be able to keep to a convention that I've used throughout my JavaScript files. It's the difference between this:

htmlString = "<a href=\"someLink.html\">";

and this:

htmlString = '<a href="someLink.html">';

As a result I was so happy to see the singleQuotes option. When set to true this uses single quotes to encapsulate the strings in the array that it passes to $inject. You set this to true or false depending on what your convention is. The default is false.

There are other options that you can use, such as for regex or processing already minified files, but I found the three above to be satisfactory. For further information, check out the grunt-ng-annotate documentation on GitHub.

After the options object you can have one to n objects describing the different file groups that you want ng-annotate to annotate. Here I've only used one object and I've called it app, but you could have one for every AngularJs module that you've created or for every project that you have and label it so that it is clear at a glance which configuration you're looking at.

The files listed in the files object are written as 'output': 'input' in the example above. There are varying ways of passing file names, but this is the simplest and at this time the only option I think I'm likely to use. If you want to explore this option further, consult the GitHub documentation. In my example I want ng-annotate to parse the file that I'm working on and rewrite it. This is fine for my needs as I'm likely to use this prior to minification and check-in, and I don't feel the need to keep a copy of my code from every stage of the development process (though this may change if I feel the need).

Once you've configured everything, just add the load and register statements at the bottom of the file:

grunt.loadNpmTasks('grunt-ng-annotate');
grunt.registerTask('default', ['ngAnnotate']);


I've set this up to run using grunt-contrib-watch, so that whenever I save the file this happens automatically. To do this you just need to add the string 'ngAnnotate' into the task array of the configuration of watch in you gruntfile.js.

tasks: ['someTask', 'anotherTask', 'ngAnnotate']

You can run this task directly in WebStorm using the built in Grunt Console. Right click on your gruntfile.js in the Project pane, and select Open Grunt Console from the context menu. This will open the Grunt pane at the bottom of your WebStorm window. This should automatically load the tasks that are registered in your gruntfile.js and list them, but if it doesn't click the blue refresh icon on the left. You can then run any of your tasks by clicking the small, green play arrow that appears to the right of each task as you hover on it. I've wired this one into my grunt-contrib-watch task along with a few others and so run that so that a few tasks are run each time I save a JavaScript file.

No comments:

Post a Comment