Catch AngularJs minification errors

How to prevent hard to debug AngularJS injection errors due to minified code.

JavaScript code is usually minified to minimize the download payload in production. The minification removes white space and replaces long variable and parameter names with shorter ones. Unfortunately this can break the code in spectacularly hard to fix ways.

AngularJs relies on special function parameter names to perform its dependency injection. For example, a controller function can ask for the model scope object using special $scope parameter and the run time will inject the right object reference no matter the parameter order.

1
2
3
angular.module('example').controller(function ($scope, ...) {
$scope.foo = ...
});

A typical JavaScript minifier has no idea that parameter $scope is special and should not be replaced with much shorter name (like a for example) and will typically break the production

1
2
angular.module('example').controller(function(a, ...){a.foo=...})
// angular has no idea what to inject as first argument

Use guards

The suggested guard against minification is to provide an Array of string names instead of relying on function parameter names

1
2
3
angular.module('example').controller(['$scope', function ($scope) {
$scope.foo = ...
}]);

Even if the minifier replaces $scope function parameters with shorter names, it cannot modify the first string '$scope' argument inside the array. The dependency injection knows to read names from the array and inject the into the function at last position in the array. One has to use the Array guard in all places where dependency injection is performed, but overall it is a good solution.

Do not use ng-min

ng-min is a minifier that knows about dependency injection and can preserve the function parameters. Unfortunately it is unreliable. Look at the list of open issues - there are cases too complicated for ng-min to process correctly, or unsupported at all (like dependency injection in routing code). So I recommend against using ng-min in general.

Catching missed minification guards

We have spent several hours recently debugging a minification bug in AngularJs code. The error just said unknown provider a, which was not helpful. Finally, we figured out a problem - a custom directive with missing controller guard.

To prevent these problem, we modified our JavaScript unit test Karma configuration file to accept additional command line argument --minified

karma.conf.js
1
2
3
var useMinified = process.argv.some(function (argument) {
return argument === '--minified';
});

If the user specified minified flag, we load minified bundles instead of simply concatenated ones

karma.conf.js
1
2
3
4
5
6
files: [
'vendor/jquery/js/jquery-1.10.2.js',
'vendor/angular/angular.js',
'vendor/angular/angular-*.js',
(useMinified ? 'apps/dist/app.min.js' : 'apps/dist/app.js'),
'apps/**/*.spec.js'

The unit tests are loaded unminified from separate .spec.js files, only the bundled app code is loaded either directly or from minified file.

We also skip coverage preprocessor when using minified bundle to avoid waisting time

karma.conf.js
1
2
3
preprocessors: {
'apps/dist/app.js': (useMinified ? null : 'coverage')
...

Our Jenkins is setup to run unminified unit tests once, and then the same unit tests against the minified bundle.

// exec shell 1
karma start karma.conf.js  --single-run
// exec shell 2
echo Running minified bundle
karma start karma.conf.js  --single-run --minified

If doubling the unit test time is unacceptable, you could run the two steps in parallel.

Conclusion

Debugging JavaScript minification errors is hard, and setting up Karma runner to separately detect a difference between concatenated and minified code gives us peace of mind.