【问题标题】:"Unknown provider: aProvider <- a" How do I find the original provider?“未知提供者:aProvider <- a”如何找到原始提供者?
【发布时间】:2014-03-08 11:04:41
【问题描述】:

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

Unknown provider: aProvider <- a

现在,我意识到这是由于变量名修改造成的。未修改的版本工作得很好。但是,我确实想使用变量名修饰,因为它大大减小了我们的 JS 输出文件的大小。

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

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

我如何让 Chrome 使用源映射来告诉我这里的问题是哪个提供商,或者,我怎样才能以其他方式找到提供商?

【问题讨论】:

  • 您可以尝试为每个 JS 源文件添加不同的 cmets(如果还没有的话),并使用 UglifyJS 的 preserveComments 选项:这会让您知道哪个文件包含不正确的代码。
  • 你碰巧在使用装饰器吗?我发现 ngmin 在我过去使用它时似乎没有正确地重写装饰器,这会导致像你这样的错误。
  • @JBNizet:我喜欢这个想法,但是将该指令添加到选项中似乎没有任何效果。
  • @dherman:你能给我举个装饰器的例子吗?我不确定在这种情况下它们会是什么。
  • github.com/gruntjs/grunt-contrib-uglify(如果你使用咕噜声)。选项的值应该是“all”。

标签: angularjs google-chrome uglifyjs source-maps


【解决方案1】:

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

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

原来是这样的:

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

这对 AngularJS 来说很好用,但是为了让它与 mangling 一起工作,我不得不把它改成:

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

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

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

options : {
    beautify : true,
    mangle   : true
}

然后我在 Chrome 中打开了项目网站,同时打开了 DevTools。这会导致记录如下错误:

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

当您现在重新运行应用程序时,将触发断点,您可以向上跳转调用堆栈。将有来自invoke in injector.js 的呼叫,可从“不正确的注入令牌”字符串中识别:

locals 参数(在我的代码中被改写为 d)可以很好地了解源中的哪个对象是问题所在:

在我们的源代码上快速查看grep 会发现modalInstance 的许多实例,但是从那里开始,很容易在源代码中找到这个位置:

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

必须改为:

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

如果变量不包含有用的信息,您还可以进一步向上跳堆栈,您应该调用invoke,这应该有额外的提示:

防止这种情况再次发生

既然您希望找到问题所在,我觉得我应该提一下如何最好地避免这种情况在未来再次发生。

显然,您可以在任何地方使用inline array annotation,或者(取决于您的偏好)$inject property annotation,并且尽量不要在将来忘记它。如果这样做,请确保启用strict dependency injection mode,以便及早发现此类错误。

小心!如果您使用 Angular Batarang,StrictDI 可能不适合您,因为 Angular Batarang 会将未注释的代码注入您的代码(糟糕的 Batarang!)。

或者你可以让ng-annotate来处理它。我强烈建议这样做,因为它消除了该领域的许多潜在错误,例如:

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

使注释保持最新只是一件麻烦事,如果可以自动完成,您就不必这样做。 ng-annotate 正是这样做的。

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

【讨论】:

  • 这是一篇精彩的文章,写得很仔细。我刚刚遇到了这个问题,似乎是某个地方的 ngmin 深处的问题。你的提示帮助我知道去哪里找。最后,我只是“阵列化”了我所有的角度参数,问题就消失了。之前的所有构建 ng-minified 都很好,并没有发生重大变化。我没有添加任何全局函数——它只是通过破坏一些控制器/指令/服务/过滤器而神秘地停止了工作?
  • 这是一个很好的帮助来源。我不知道您还必须对其他功能使用数组(内联)语法,例如路由器解析、.run、.config 等。
  • 就我而言,它是指令中的控制器。如果在 'd' 变量中你会看到 $attr,那可能是同一个问题。您应该将参数包装在内部指令控制器的数组括号中。控制器:["$scope", function($scope){...}] 而不是控制器:function($scope){...}
  • 非常感谢您为 var 函数参考使用安全依赖注入/数组表示法编写和解决方案。我也有这个错误,由于你的解决方案,我能够继续前进。你摇滚!
  • 每当我遇到这个问题时,我都会再读一遍,并想再次投票给这个。顺便说一句,这里是如何设置 gulp 版本uglify({ output : { beautify : true }})
【解决方案2】:

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"
};

【讨论】:

  • 这太厚脸皮了...直到最近一次更新,Uglify 才对我造成这种情况!
  • 我的问题是一样的,但事实证明我需要在函数之前添加/* @ngInject */。它似乎完成了复杂的注入部分,无需键入每个包含的模块(我使用的是 Yeoman)
【解决方案3】:

将 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 或字符串数​​组语法的应用程序非常有用。
【解决方案4】:

要缩小角度,您只需将声明更改为“数组”声明“模式”,例如:

发件人:

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 的原因。我怀疑它与我们的源代码或其依赖项的某些部分存在问题。这就是为什么我试图找到这个问题的根源。
  • 我的建议是您以这种方式创建代码。所以你可以使用任何缩小器
  • 正在以这种方式创建我们的代码。但是我们有没有外部依赖。 ngmin 过去已经为我们很好地解决了这个问题。我认为最近的变化造成了这个问题。我现在想找到这个问题的根源,以便我可以在我们的代码、依赖项或可能在 ngmin 本身中正确修复它。
  • 由于这个问题听起来非常具体到特定组件或代码很难提供指导,至少从我的角度来看
  • ngmin 不需要你使用数组声明方式,它增加了很多无用的声明。
【解决方案5】:

我刚刚遇到了同样的问题,并通过简单地将 ngmin(现已弃用)替换为我的 grunt 构建任务的 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),并按照commit中的更改编辑Gruntfile.js

【讨论】:

    【解决方案6】:

    为了知道原始变量名称是什么,您可以更改 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”或其他任何内容...您将在原始上下文中看到它...

    再简单不过了……

    【讨论】:

      【解决方案7】:

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

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

      【讨论】:

      • 当我为我的路线添加了一堆解析时,这发生在我身上。您可能为我节省了数小时痛苦的调试时间,谢谢。
      【解决方案8】:

      使用生成器-gulp-angular:

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

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

      【讨论】:

        【解决方案9】:

        如果你不需要 Uglify 来修改/缩短你的变量名,一个快速而肮脏的解决方法是在你的 Gruntfile 中设置 mangle = false

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

        【讨论】:

        • 这可能会解决问题,但由于禁用了 mangle,因此生成的构建大小会更大。
        • 还是比不丑还小
        猜你喜欢
        • 2012-10-07
        • 2014-06-01
        • 1970-01-01
        • 2015-01-25
        • 1970-01-01
        • 2013-09-27
        • 2016-11-29
        • 2018-09-28
        • 2014-12-01
        相关资源
        最近更新 更多