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 | angular.module('example').controller(function ($scope, ...) { |
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 | angular.module('example').controller(function(a, ...){a.foo=...}) |
Use guards
The suggested guard against minification is to provide an Array of string names instead of relying on function parameter names
1 | angular.module('example').controller(['$scope', function ($scope) { |
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
1 | var useMinified = process.argv.some(function (argument) { |
If the user specified minified flag, we load minified bundles instead of simply concatenated ones
1 | files: [ |
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
1 | preprocessors: { |
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.