Notes From Heck

Code Monkey Speaketh.

Minifying + Compressing an AngularJS App

This short tutorial demonstrates how to prepare an AngularJS app for deployment to a static web server, with all the bells and whistles needed to score an A on YSlow.

Key Assumptions

  1. Your dev setup is on Linux, or other Bash-compatible. (Build script written for Bash.)
  2. You use Git for SCM. (The build script will use git for some operations. Feel free to alter the script, and get rid of this dependency.)
  3. The app is structured as recommended by the angular-seed project. (Build script expects certain folders to be present at specific locations. You can adapt it to your project structure.)


Go grab the following:

  1. YUI Compressor
  2. Git
  3. Gzip (Installed by default on most *NIX systems. Pull from distro repos otherwise.)
  4. Node.js (For testing stuff on your localhost. Not required if you have/prefer some other server for delivering static content.)
  5. Stomach for shell scripts


I wanted to split my web application into two distinct components:

  1. A client-side, JS-driven presentation layer.
  2. A lightweight, REST-based backend.

I've had to sort out a lot of issues to get both to cooperate while running on different servers on different domains, use digest-based authentication instead of cookies (REST is stateless), and so on, but that's another post. This one focuses on efficiently delivering the UI portion - HTML + CSS + JS + Media - which from a server POV is static content.

Preparing AngularJS Scripts for Minification

The AngularJS docs provide some information on how to prepare controllers for minification here. Quoting from the page:

Since angular infers the controller's dependencies from the names of arguments to the controller's constructor function, if you were to minify the JavaScript code for PhoneListCtrl controller, all of its function arguments would be minified as well, and the dependency injector would not be able to identify services correctly.

PhoneListCtrl is part of the angular-phonecat application, used for driving the on-site tutorial.

Basically, every controller defined by your application needs to be explicitly injected with whatever dependencies it has. For the example above, it looks something like:

PhoneListCtrl.$inject = ['$scope', '$http'];

There is one more way defined on the site, but I prefer the method above.

However, this is not enough to get minified scripts working right. YUI Compressor changes closure parameter names, and this doesn't go down well with Angular. You need to use inline annotations in defining custom services. You can find a usage example here.

Additionally, you can collate all content from controllers.js, directives.js, services.js and filters.js into app.js to reduce the number of calls made to the server.
Don't forget to modify your index.html / index-async.html to reflect this change.

The Build Script

If you're sticking to the folder structure provided by angular-seed, you'll have an app folder in your project root. Adjacent to this, create a build folder to contain the minified and compressed output files generated by the build script. You can tell git to ignore this folder by adding the following line to .gitignore:


You can put your build script anywhere you like, and run it from anywhere in the project folder. I have put it inside the conveniently provided scripts folder.

Once you run this script, every app/file.[css | js] would have a working copy at build/file.min.[css | js]. Every other file in the app folder will be either:

  1. compressed and copied (name unchanged) into the build folder if it is a text file, or
  2. simply copied into the build folder if it is a binary file (like an image).

Your CSS and JS references need to be updated to their corresponding min versions in index.html / index-async.html.

Now that you've got a compressed, minified version of your app in the build folder, you can deploy it to any static server. But you do need to set your HTTP response headers properly, or the browser WILL show garbage. Most importantly, any compressed content must be served with the HTTP response header:

Content-Encoding: gzip

Additionally, for every file that is static content, it makes sense to set a far future date using an Expires header similar to the following:

Expires: Thu, 31 Dec 2037 20:00:00 GMT

## The NodeJS _web-server.js_ Script The contents of the _build_ folder are technically ready to be uploaded to any web server, but you will probably want to run the app from your localhost to first check if everything works fine. The built-in _web-server.js_ is very useful to quickly launch and test your app, but it needs a few mods in order to serve the compressed content from the build folder correctly. The _Content-Encoding_ header is sufficient to render the page correctly, but if you're a stickler for good YSlow grades even on your localhost, you will want to add the _Expires_ headers as well. Search for the following response codes in your _web-server.js_ and add the lines listed below:
  1. 200 (writeDirectoryIndex), 500, 404, 403, 301:
'Expires': 'Thu, 31 Dec 2037 20:00:00 GMT',
  1. 200 (sendFile) (After var file = fs.createReadStream(path);):
var fileType = StaticServlet.MimeMap[path.split('.').pop()];
var contentType = fileType || 'text/plain';
res.writeHead(200, {
    'Content-Type': contentType,
    'Expires': 'Thu, 31 Dec 2037 20:00:00 GMT',
    'Content-Encoding': ((path.indexOf('./build') === 0) && ((contentType.indexOf('text') === 0) || (contentType.indexOf('application/javascript') === 0))) ? 'gzip' : ''

That's it! Now when you run the web-server.js, all content from the build folder will be correctly served with the gzip header (unless it is a binary).

Author image
Bangalore, India
I’m a developer, a hobbyist biker, and a Linux enthusiast. When not riding into the sunset, and not being a general nuisance, I like to experiment with new systems and concepts in technology.