“Unknown provider: aProvider <- a” 如何找到原始的提供者?

103

当我加载经过UglifyJS压缩的AngularJS应用程序的最小化版本时,控制台会显示以下错误:

Unknown provider: aProvider <- a

现在,我意识到这是由于变量名混淆所导致的。未混淆版本可以正常工作。然而,我 确实 想要使用变量名混淆,因为它可以大幅减小我们的JS输出文件大小。

出于这个原因,我们在构建过程中使用ngmin,但它似乎无法解决这个问题,尽管它在过去为我们服务良好。

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

我如何让Chrome使用源代码映射告诉我哪个提供程序是有问题的,或者换句话说,我如何以其他方式找到提供程序?


如果还没有这样做的话,您可以尝试为每个JS源文件添加不同的注释,并使用UglifyJS的preserveComments选项:这将使您了解哪个文件包含有误的代码。 - JB Nizet
你是否正在使用装饰器?我发现在过去使用ngmin时,它似乎无法正确地重写装饰器,这会导致像你遇到的错误。 - dherman
或者你可以在这里发布源代码的链接。应该不难找出导致问题的原因。Chrome有非常好的代码美化器,可以帮助阅读压缩后的源代码。然后只需要跟踪异常抛出的位置就可以了。 - Michał Miszczyszyn
@JBNizet:是的,我目前仍然遇到的问题是,即使我能够标记所有文件,我也无法确定从抛出错误的点开始哪个文件导致了问题。调用堆栈是一堆混乱的变量,我无法分析实际导致错误的源代码位置。即使使用美化过的源代码和多个断点,我也无法找到问题的源头。此时整个调用层次结构似乎非常复杂。 - Oliver Salzburg
@Miszy:很遗憾,我不能在这里发布源代码,因为它不是公共项目。我知道异常抛出的位置。但是我不知道如何从那里确定有问题的源代码位置,因为评估Angular定义的代码相当复杂。 - Oliver Salzburg
显示剩余9条评论
9个回答

198

我仍然想知道如何在我们的源代码中找到导致此问题的地方,但是我现在已经能够手动找到问题了。

全局作用域上声明了一个控制器函数,而不是在应用模块上使用.controller()调用。

因此类似于这样:

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

这对于AngularJS来说很好用,但为了让代码混淆后仍然能正常运行,我不得不将其修改为:

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

经过进一步的测试,我实际上发现了更多控制器引起问题的情况。这就是我手动找到它们的方法:

首先,我认为在uglify选项中启用输出美化非常重要。对于我们的grunt任务,这意味着:

options : {
    beautify : true,
    mangle   : true
}

我随后在Chrome中打开了项目网站,并打开了DevTools。这导致类似下面这样的错误被记录:

enter image description here

我们感兴趣的调用堆栈中的方法是我标记的那个箭头所指向的,它是injector.js中的providerInjector。您需要在它抛出异常的地方设置断点:

enter image description here

现在重新运行应用程序时,将会触发断点,您可以跳转到调用堆栈的上方。其中有一个来自injector.js中的invoke的调用,可从“Incorrect injection token”字符串中识别出:

enter image description here

locals参数(在我的代码中转换为d)可以很好地指出源代码中的哪个对象存在问题:

enter image description here

在我们的源代码上执行快速的grep,找到许多modalInstance实例,但从那里开始,很容易就能找到源代码中的这个位置:

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

必须更改为:

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

如果变量没有包含有用信息,您还可以跳转到堆栈的更高层,这时您应该会遇到调用invoke的地方,它应该会提供额外的提示:

图像描述

防止再次发生

现在您已经找到了问题,我认为我应该提一下如何最好地避免将来再次出现这种情况。

显然,您可以在每个地方都使用内联数组注释,或者(根据您的喜好)$inject 属性注释,并尽量不要忘记。如果您这样做,请确保启用严格依赖项注入模式,以尽早捕获此类错误。

注意!如果您正在使用Angular Batarang,则StrictDI可能无法正常工作,因为Angular Batarang会将未注释的代码注入到您的代码中(坏Batarang!)。

或者您可以让ng-annotate来处理它。我强烈推荐这样做,因为它可以消除此区域中出现的大量潜在错误,例如:

  • DI注释丢失
  • DI注释不完整
  • DI注释顺序错误

保持注释更新只是一件麻烦事,如果可以自动完成,就没有必要这样做。ng-annotate正是如此。

它应该与grunt-ng-annotategulp-ng-annotate非常好地集成到您的构建过程中。


12
这是一篇非常用心撰写的好文章。我遇到了这个问题,似乎是在ngmin的某个深处出了问题。你的提示帮助我知道了该去哪里寻找答案。最终,我只需将我的所有angular参数都变成了数组形式,问题就解决了。之前的所有构建都很好地进行了ng-minify,并且没有发生什么可观的变化。我没有添加任何全局函数 -- 它只是在搞乱一些控制器/指令/服务/过滤器时,神秘地停止了工作。 - zenocon
这是一个非常有帮助的资源。我不知道你还必须在其他函数中使用数组(内联)语法,例如路由器解析、.run、.config等。 - VDest
4
在我的情况中,是指令中的控制器出了问题。如果在“d”变量中看到了$attr,那可能是同样的问题。您应该使用数组括号将参数包装起来以供内部指令控制器使用。 控制器:[" $scope”, function($scope){...}] 而不是 控制器:function($scope){...} - alex naumov
1
每次我遇到这个问题,我都会再次阅读并想再次投票支持它。 顺便说一下,这是如何设置gulp版本uglify({ output : { beautify : true }})的方法。 - Eugene Gluhotorenko
禁用Uglify,将ng-strict-di添加到ng-app行中: <body ng-app="lacote" ng-strict-di> Angular会告诉您问题出在哪里!修复并重新启用Uglify。 - PyWebDesign
显示剩余6条评论

30

Oliver Salzburg的写作非常棒。已点赞。

对于任何可能出现此错误的人,提示一下。我的问题仅仅是忘记为指令控制器传递一个数组:

BAD

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

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

2
这真是个恶作剧...直到最近的更新,Uglify才没有给我带来这个问题! - SamMorrowDrums
我的问题也是一样的,但事实证明我需要在函数前添加 /* @ngInject */。它似乎可以完成复杂的注入部分,而无需输入每个包含的模块(我正在使用 Yeoman)。 - Nicholas Blasgen

26

使用ng-strict-di和ng-app一起使用

如果您正在使用Angular 1.3,可以通过在ngApp中使用ngStrictDi指令来避免许多麻烦:

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

现在,在缩小化之前,任何不使用注释的东西都会使您的控制台崩溃,并且您可以看到该名字,而无需查找混乱的堆栈跟踪。

根据文档:

未使用显式函数注释(因此不适合缩小)的函数将无法调用

一个注意点,它仅检测是否存在注释,而不检测注释是否完整。

意思是:


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

不会捕捉到ThingB不是注释的一部分。

感谢ng-annotate团队提供此提示,强烈推荐在现在被弃用的ngMin之上使用它。


这个需要更多的点赞。这对于调试从未使用过ngInject或字符串数组语法的应用程序非常有用。 - Michael Pearson

11

要缩小Angular的所有内容,你只需要将声明更改为“数组”声明“模式”,例如:

来自:

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

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

如何声明工厂服务?

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

我知道。这就是为什么我们使用ngmin的原因。我怀疑它与我们的某些源代码或其依赖项有问题。这就是为什么我正在努力找到这个问题的根本原因。 - Oliver Salzburg
1
我的建议是按照这种方式编写代码。这样你就可以使用任何缩小器。 - Dalorzo
3
我目前正在按照这种方式编写我们的代码,但是我们依赖外部库,它们不是这样做的。过去,ngmin 已经成功地解决了这个问题。我认为最近的更改导致了这个问题。现在,我想找到这个问题的源头,以便我可以在我们的代码、依赖项或可能是 ngmin 本身中对其进行适当的修复。 - Oliver Salzburg
由于该问题似乎非常特定于某个组件或代码,因此很难提供指导,至少从我的角度来看。 - Dalorzo
ngmin不要求您使用数组声明模式,它会添加许多无用的声明。 - Nanocom

8
我刚刚遇到了同样的问题,并通过将 grunt 构建任务中的 ngmin(现已弃用)替换为 ng-annotate 解决了这个问题。
看起来 yeoman angular 也已经更新,使用 ng-annotate,具体信息请见此提交记录:https://github.com/yeoman/generator-angular/commit/3eea4cbeb010eeaaf797c17604b4a3ab5371eccb 然而,如果你像我一样正在使用较旧版本的 yeoman angular,只需在 package.json 中将 ng-min 替换为 ng-annotate 即可。
-    "grunt-ngmin": "^0.0.3",
+    "grunt-ng-annotate": "^0.3.0",

运行npm install(然后可选地运行npm prune),并在提交记录中跟踪更改以编辑Gruntfile.js


7
为了知道原始变量名,您可以更改uglify混淆变量的方式:

../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;
    [...]
  }
};

现在错误更加明显了。

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)

编辑

现在看来很明显了...

Gruntfile.js

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

../node_modules/grunt-contrib-uglify/node_modules/uglify-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++;
    [...]
  }
};

现在每个变量都被混淆为一个包含原始值的唯一值...只需打开压缩后的javascript并搜索“a_orig_$stateProvider_91212”或其他任何内容...您将在其原始上下文中看到它...这再也不容易了...

4

使用generator-gulp-angular:

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

}

在每个控制器、服务、指令之前写上/** @ngInject */

4

另外不要忘记路由的resolve属性。它也必须定义为数组:

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

当我向我的路由添加了一堆解析器时,这种情况也发生在我身上。你可能为我节省了数小时痛苦的调试时间,谢谢。 - Paul McClean

2
如果您不需要使用Uglify来混淆/缩短变量名称,那么可以在Gruntfile中设置mangle = false来快速解决此问题。
    uglify: {
        compile: {
            options: {
                mangle   : false,
                ...
            },
        }
    }

这可能会解决问题,但由于禁用了混淆,生成的构建大小将会更大。 - NotABot
仍然比完全不混淆要小 - mjwrazor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接