Minifying an existing AngularJS project
Being a PHP Developer who only recently started using AngularJS I knew almost nothing about the incredible progress front-end development had made since I last created a website.
When we deployed our first AngularJS application over at Qandidate.com a single page view would generate a whopping 56 HTTP requests of which 32 were our own AngularJS files like controllers and services and 12 were 3rd party dependencies.
Since browsers only allow for up to about 8 parallel connections a lot of requests will have to wait for other requests to finish resulting in a poor end-user experience.
In this blog post I will show how you can safely minify and concatenate your AngularJS code.
Optimize all the code
While catching up on the latest technologies I came across this great post by @andersjanmyr. I also attended Aurelio de Rosa's talk "Modern front-end with the eyes of a PHP developer" at Dutch PHP Conference.
They introduced me to Grunt for running tasks and Bower for front-end package management.
Having followed @andersjanmyr's blog post I ended up with a really small set of concatenated and minified assets. Yay!
However, my AngularJS app was broken. Oh noes!
Minify AngularJS part 1
As with many problems the root cause was me :) Or at least me not properly using AngularJS's Dependency Injection. Being lazy I regularly injected dependencies like this:
myApp.controller('MyCtrl', function($q) {});
Minifying this will mess up the name of the dependency. If you annotate it like this it will work correctly:
myApp.controller('MyCtrl', ['$q', function($q) {}]);
So all I had to do was update my JavaScript... Great...
Luckily there is a Grunt plugin called grunt-ng-annotate based on ng-annotate which helps you out by applying just these fixes to your AngularJS JavaScript so it can be safely minified.
ng-annotate
Note: All the code I created for this post is available at GitHub.
You can install ng-annotate
using npm
:
# install globally
sudo npm install -g grunt-ng-annotate
# or install locally
npm install grunt-ng-annotate --save-dev
Note: The save-dev
option will add the package to your package.json
file
under devDependencies
.
In your Gruntfile.js
load the plugin's tasks:
grunt.loadNpmTasks('grunt-ng-annotate');
Hint: look in to matchdep or load-grunt-tasks to load grunt plugins automatically.
Now register the ngAnnotate
task in Gruntfile.js
:
grunt.loadNpmTasks('ngAnnotate');
To demonstrate ng-annotate I created a controller without annotations called 'WithoutAnnotationsCtrl.js':
angular.module("MyMod").controller("MyCtrl", function($scope, $timeout) {
});
I want this file to be processed into:
angular.module("MyMod").controller("MyCtrl", ['$scope', '$timeout', function($scope, $timeout) {
}]);
In order to do so I added the following task config to Gruntfile.js
:
grunt.initConfig({
// Task configuration.
ngAnnotate: {
demo: {
files: {
'WithAnnotationsCtrl.js': ['WithoutAnnotationsCtrl.js']
},
}
}
});
This will tell ng-annotate
to process WithoutAnnotationsCtrl.js
and save the
result as WithAnnotationsCtrl.js
.
You can run the task with grunt ngAnnotate
or simply grunt
.
Note: I you haven't installed Grunt yet, you can do so by running:
sudo npm install -g grunt-cli
Have a look at WithAnnotationsCtrl.js
and you will see the dependencies are
nicely annotated. Now you can safely minify your code!
Minify AngularJS part 2
I will use grunt-contrib-uglify to minify.
Install it with:
npm install grunt-contrib-uglify --save-dev
Load the task in Gruntfile.js
:
grunt.loadNpmTasks('grunt-contrib-uglify');
And add it to the default task AFTER ngAnnotate
:
grunt.registerTask('default', ['ngAnnotate', 'uglify']);
Now I want the annotated JavaScript to be minified to
WithAnnotationsCtrl.min.js
. Just add the task configuration to Gruntfile.js
:
grunt.initConfig({
// Task configuration.
// ngAnnotate: { ... },
uglify: {
demo: {
files: {
'WithAnnotationsCtrl.min.js': ['WithAnnotationsCtrl.js']
}
}
}
});
Now run grunt
again and you will end up with a safely minified file:
angular.module("MyMod").controller("MyCtrl",["$scope","$timeout",function(){}]);
Wrap up
ng-annotate is great tool to clean up your existing AngularJS applications. It made me aware why coding standards matter in front-end as well as back-end applications.
As olov points out in ng-annotate's README AngularJS 1.3 will add a
ngStrictDi
argument to ng-app
which when enabled will make your app fail
when you don't use function annotation.