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


100

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

Unknown provider: aProvider <- a

现在,我意识到这是由于变量名修饰。无损版本工作正常。但是,我确实想利用变量名修饰,因为它可以大大减少JS输出文件的大小。

出于这个原因,我们在构建过程中使用了ngmin,但即使过去它为我们提供了很好的服务,它似乎也无法解决该问题。

因此,要调试此问题,我在uglify grunt任务中启用了源映射。它们生成的很好,Chrome 确实从服务器加载了地图。但是,即使我觉得现在应该看到提供程序的原始名称,我仍然收到同样无助的错误消息。

如何让Chrome使用源地图来告诉我这里的问题是哪个提供程序,或者,如何用另一种方式找出提供程序?


您可以尝试向每个JS源文件添加不同的注释(如果不是这样),并使用UglifyJS的prepareComments选项:这样可以使您了解哪个文件包含错误的代码。
JB Nizet 2014年

您是否正巧使用装饰器?我发现ngmin过去使用ngmin似乎无法正确重写装饰器,这会导致类似您的错误。
dherman 2014年

@JBNizet:我喜欢这个主意,但是将该指令添加到选项中似乎没有任何效果。
Der Hochstapler 2014年

@dherman:能给我一个装饰的例子吗?我不确定在这种情况下它们会是什么。
Der Hochstapler 2014年

参见github.com/gruntjs/grunt-contrib-uglify(如果使用grunt)。选项的值应为“ all”。
JB Nizet 2014年

Answers:


193

我仍然很想知道如何在导致此问题的源代码中找到该位置,但此后我便能够手动找到该问题。

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

所以有这样的事情:

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

这对于AngularJS来说还算不错,但是为了使其能够正确处理,我不得不将其更改为:

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

经过进一步测试后,我实际上发现了更多控制器的实例,这些实例也引起了问题。这就是我手动找到所有这些源的方法:

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

options : {
    beautify : true,
    mangle   : true
}

然后,我在Chrome中打开了项目网站,并打开了DevTools。导致出现类似以下错误的错误记录:

在此处输入图片说明

我们感兴趣的调用跟踪中的方法是我用箭头标记的方法。这是providerInjectorinjector.js。您将要在引发异常的地方放置一个断点:

在此处输入图片说明

现在,当您重新运行该应用程序时,将遇到断点,您可以跳出调用堆栈。来自invokeininjector.js的呼叫可以从“错误的注入令牌”字符串中识别出来:

在此处输入图片说明

locals参数(错位,以d我的代码)给出了一个不错的主意,哪些对象在源的问题是:

在此处输入图片说明

快速浏览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某处的一个深层问题。您的提示使我知道在哪里看。最后,我只是对所有角度参数进行“数组化”处理,问题就消失了。ng最小化的所有先前版本都很好,并且没有什么大的改变。我没有添加任何全局函数-它只是通过修改某些控制器/指令/服务/过滤器而神秘地停止了工作?
zenocon

这是非常有用的帮助。我不知道你要使用阵列(在线)的语法也可用于其它功能,如路由器的决心,.RUN,的.config等
VDest

4
就我而言,它是指令中的控制器。如果在“ d”变量中,您将看到$ attr,则可能是同一问题。您应该将参数包装在内部指令控制器的数组括号中。控制器:[“ $ scope”,function($ scope){...}]代替控制器:function($ scope){...}
alex naumov 2014年

非常感谢您使用安全的依赖项注入/数组表示法作为var函数参考的编写和解决方案。我也有这个错误,由于您的解决方案,我得以继续前进。你摇滚!
Frankie Loscavio 2014年

1
每次遇到此问题时,我都会再次阅读并希望再次投票。顺便说一句,这是如何设置uglify({ output : { beautify : true }})
gulp

30

奥利弗·萨尔茨堡(Oliver Salzburg)的写作很棒。已投票。

给可能有此错误的任何人的提示。我的原因仅仅是由于忘记为指令控制器传递数组:

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 2015年

我的问题是相同的,但事实证明,我需要添加的是/* @ngInject */该函数之前的内容。似乎不需要输入每个包含的模块即可完成复杂的注入部分(我使用的是Yeoman)
Nicholas Blasgen 2016年

25

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

如果您使用的是Angular 1.3,则可以通过将ngStrictDi指令与ngApp一起使用,从而避免遭受重创:

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

现在-预缩小- 使用注释的所有内容都会炸毁您的控制台您可以看到friggin的名称,而无需在混乱的堆栈跟踪中搜寻。

根据文档:

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

有一点需要注意,它只能检测到有注解,而不是注释是完整的。

含义:

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

不会发现ThingB不是注释的一部分。

此技巧归功于ng-annotate族,这是对现在已弃用的ngMin的推荐。


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

11

要最小化角度,您需要做的就是将声明更改为“数组”声明“ mode”,例如:

从:

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。我怀疑它与我们的部分来源或其依赖项有关。这就是为什么我试图找到这个问题的根源。
Der Hochstapler 2014年

1
我的建议是您以这种方式创建代码。因此,您可以使用任何粉碎机
Dalorzo 2014年

3
正在以这种方式创建代码。但是我们有没有的外部依赖关系。ngmin过去为我们很好地解决了这个问题。我认为最近的更改导致了此问题。现在,我想找到此问题的根源,以便可以在我们的代码,依赖项或ngmin本身中正确修复它。
Der Hochstapler 2014年

由于这个问题听起来像是一个非常具体的特定组件或代码难以提供指导,至少从我的目的
Dalorzo

ngmin不需要您使用数组声明模式,它会添加许多无用的声明。
Nanocom

8

我只是遇到了同样的问题,并通过将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_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++;
    [...]
  }
};

现在,每个变量都被整形为一个唯一的值,该值也包含原始值...只需打开缩小的JavaScript并搜索“ a_orig_ $ stateProvider_91212”或其他任何内容,您就会在其原始上下文中看到它。

再简单不过了...


4

也不要忘记resolve路线的属性。还必须将其定义为数组:

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

当我向路线添加一堆解决方案时,这发生在我身上。谢谢,您可能为我节省了数小时的痛苦调试工作。
保罗·麦克莱恩


2

如果您不需要Uglify修改/缩短变量名,则可以快速而又肮脏地解决此问题,方法是在Gruntfile中设置mangle = false

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

这可能解决了该问题,但是由于禁用了mangle,因此生成的生成大小将更大。
NotABot

仍然比不丑陋还要小
mjwrazor
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.