“未知提供程序: 提供程序 <-a”如何找到原始提供程序?

当我加载 AngularJS 应用程序的缩小版(通过 UglifyJS)时,我在控制台中得到以下错误:

Unknown provider: aProvider <- a

现在,我意识到这是由于变量名错位造成的。没有损坏的版本就很好。然而,我 希望利用变量名错位,因为它大大减少了我们的 JS 输出文件的大小。

出于这个原因,我们在构建过程中使用了 Ngmin,但是它似乎并没有解决这个问题,即使它在过去对我们很有用。

因此,为了调试这个问题,我在我们的 uglify grunt 任务中启用了源映射。它们生成得很好,Chrome是的从服务器加载地图。然而,我仍然得到同样的无用的错误消息,尽管我以为现在应该看到提供程序的原始名称。

我如何让 Chrome 使用源地图来告诉我哪个供应商是问题所在,或者,我如何用另一种方式找到供应商?

41923 次浏览

To minify angular all you need is to do is to change your declaration to the "array" declaration "mode" for example:

From:

var demoApp= angular.module('demoApp', []);
demoApp.controller(function demoCtrl($scope) {
} );

To

var demoApp= angular.module('demoApp', []);
demoApp.controller(["$scope",function demoCtrl($scope) {
}]);

How to declare factory services?

demoApp.factory('demoFactory', ['$q', '$http', function ($q, $http) {
return {
//some object
};
}]);

I'd still love to know how I could have found the place in our source code that caused this issue, but I have since been able to find the problem manually.

There was a controller function declared on the global scope, instead of using a .controller() call on the application module.

So there was something like this:

function SomeController( $scope, i18n ) { /* ... */ }

This works just fine for AngularJS, but to make it work right with mangling, I had to change it to:

var applicationModule = angular.module( "example" );
function SomeController( $scope, i18n ) { /* ... */ }
applicationModule.controller( "SomeController", [ "$scope", "i18n", SomeController ] );

After further tests, I actually found instances of more controllers that also caused issues. This is how I found the source of all of them manually:

First of all, I consider it rather important to enable output beautification in the uglify options. For our grunt task that meant:

options : {
beautify : true,
mangle   : true
}

I then opened the project website in Chrome, with the DevTools open. Which results in an error like the one below being logged:

enter image description here

The method in the call trace we're interested in, is the one I marked with an arrow. This is ABC0 in injector.js. You're going to want to place a breakpoint where it throws an exception:

enter image description here

When you now re-run the application, the breakpoint will be hit and you can jump up the call stack. There will be a call from ABC0 in injector.js, recognizable from the "Incorrect injection token" string:

enter image description here

The locals parameter (mangled to d in my code) gives a pretty good idea about which object in your source is the problem:

enter image description here

A quick grep over our source finds many instances of modalInstance, but going from there, it was easy to find this spot in the source:

var ModalCreateEditMeetingController = function( $scope, $modalInstance ) {
};

Which must be changed to:

var ModalCreateEditMeetingController = [ "$scope", "$modalInstance", function( $scope, $modalInstance ) {
} ];

In case the variable does not hold useful information, you can also jump further up the stack and you should hit a call to invoke which should have additional hints:

enter image description here

Prevent this from happening again

Now that you've hopefully found the problem, I feel that I should mention how to best avoid this from happening again in the future.

Obviously, you could just use the inline array annotation everywhere, or the (depending on your preference) $inject property annotation and simply try not to forget about it in the future. If you do so, make sure to enable strict dependency injection mode, to catch errors like this early.

Watch out! In case you're using Angular Batarang, StrictDI might not work for you, as Angular Batarang injects unannotated code into yours (bad Batarang!).

Or you could let ng-annotate take care of it. I highly recommend doing so, as it removes a lot of potential for mistakes in this area, like:

  • DI annotation missing
  • DI annotation incomplete
  • DI annotation in wrong order

Keeping the annotations up-to-date is simply a pain in the ass and you shouldn't have to do it if it can be done automatically. ng-annotate does exactly that.

It should integrate nicely into your build process with grunt-ng-annotate and gulp-ng-annotate.

I just had the same problem and resolved it by simply replacing ngmin (now deprecated) with ng-annotate for my grunt build task.

It seems that yeoman angular has also been updated to use ng-annotate as of this commit: https://github.com/yeoman/generator-angular/commit/3eea4cbeb010eeaaf797c17604b4a3ab5371eccb

However if you are using an older version of yeoman angular like me, just replace ng-min with ng-annotate in your package.json:

-    "grunt-ngmin": "^0.0.3",
+    "grunt-ng-annotate": "^0.3.0",

run npm install (then optionally npm prune), and follow the changes in the commit to edit Gruntfile.js.

Oliver Salzburg's write-up was fantastic. Upvoted.

Tip for anyone who might have this error. Mine was simply caused by forgetting to pass in an array for a directive controller:

BAD

return {
restrict: "E",
scope: {
},
controller: ExampleDirectiveController,
templateUrl: "template/url/here.html"
};

GOOD

return {
restrict: "E",
scope: {
},
controller: ["$scope", ExampleDirectiveController],
templateUrl: "template/url/here.html"
};

use ng-strict-di with ng-app

If you're using Angular 1.3 you can save yourself a world of hurt by using ngStrictDi directive with ngApp:

<html lang="en" ng-app="myUglifiablyGreatApp" ng-strict-di>

Now — pre-minification — anything that doesn't use annotations will blow up your console and you can see the friggin' name without hunting through mangled stack traces.

Per the docs:

the application will fail to invoke functions which do not use explicit function annotation (and are thus unsuitable for minification)

One caveat, it only detects that there are annotations, not that the annotations are complete.

Meaning:

['ThingOne', function(ThingA, ThingB) { … }]

Will not catch that ThingB isn't part of the annotation.

Credit for this tip goes to the ng-annotate folks, which is recommend over the now deprecated ngMin.

Also do not forget the resolve property of the route. It also must be defined as the array:

$routeProvider.when('/foo', {
resolve: {
bar: ['myService1', function(myService1) {
return myService1.getThis();
}],
baz: ['myService2', function(myService2) {
return myService2.getThat();
}]
}
});

in order to know what the original variable name was you can change how uglify mangles the variables:

../node_modules/grunt-contrib-uglify/node_modulesuglify-js/lib/scope.js

SymbolDef.prototype = {
unmangleable: [...],
mangle: function(options) {
[...]
this.mangled_name = s.next_mangled(options, this)+"_orig_"+this.orig[0].name;
[...]
}
};

and now the error is much more obvious

Error: [$injector:unpr] Unknown provider: a_orig_$stateProvider
http://errors.angularjs.org/1.3.7/$injector/unpr?p0=a_orig_%24stateProvider
at eval (eval at <anonymous> (http://example.com/:64:17), <anonymous>:3155:20)

EDIT

So obvious now...

Gruntfile.js

uglify: {
example: {
options: {
beautify: true,
mangle: true
},
[...]
},
[...]
}

../node_modules/grunt-contrib-uglify/node_modulesuglify-js/lib/scope.js

var numberOfVariables = 1;
SymbolDef.prototype = {
unmangleable: [...],
mangle: function(options) {
[...]
this.mangled_name = s.next_mangled(options, this)+"_orig_"+this.orig[0].name+"_"+numberOfVariables++;
[...]
}
};

now each variable is mangled to a unique value that also contains the original... just open up the minified javascript and search for "a_orig_$stateProvider_91212" or whatever... you will see it in it's original context...

couldn't be any easier...

A quick and dirty fix for this if you don't require Uglify to mangle/shorten your variable names is to set mangle = false in your Gruntfile

    uglify: {
compile: {
options: {
mangle   : false,
...
},
}
}

With generator-gulp-angular:

   /** @ngInject */
function SomeController($scope, myCoolService) {


}

Write /** @ngInject */ before each controller, service, directive.