102

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

Unknown provider: aProvider <- a

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

出于这个原因,我们在构建过程中使用了ngmin,但它似乎并没有解决这个问题,尽管它在过去为我们提供了很好的服务。

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

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

4

9 回答 9

197

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

在全局范围内声明了一个控制器函数,而不是.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。这会导致记录如下错误:

在此处输入图像描述

我们感兴趣的调用跟踪中的方法是我用箭头标记的方法。这是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很好地集成到您的构建过程中。

于 2014-02-14T18:36:59.697 回答
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"
};
于 2014-10-15T11:58:15.020 回答
26

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

如果您使用的是 Angular 1.3,您可以通过将ngStrictDi指令与 ngApp 一起使用来拯救自己:

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

现在 - 预缩小 - 任何使用注释的东西都会炸毁你的控制台你可以看到该死的名字,而无需通过混乱的堆栈跟踪。

根据文档:

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

需要注意的是,它只检测到有注释,而不是注释是否完整。

意义:

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

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

这个技巧的功劳归于ng-annotate 的人,推荐给现在已弃用的 ngMin。

于 2015-02-06T03:33:30.837 回答
11

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

从:

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
    };
}]);
于 2014-02-10T22:05:53.907 回答
8

我只是遇到了同样的问题,并通过简单地将 ngmin(现已弃用)替换为 ng-annotate 来解决我的 grunt 构建任务。

似乎 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

于 2014-09-13T05:08:44.447 回答
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”或其他任何内容...您将在原始上下文中看到它...

再简单不过了……

于 2015-08-04T18:13:37.367 回答
4

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

$routeProvider.when('/foo', {
    resolve: {
        bar: ['myService1', function(myService1) {
            return myService1.getThis();
        }],
        baz: ['myService2', function(myService2) {
            return myService2.getThat();
        }]
    }
});
于 2015-02-25T14:23:37.360 回答
4

使用生成器-gulp-angular:

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

}

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

于 2016-07-29T08:27:41.753 回答
2

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

    uglify: {
        compile: {
            options: {
                mangle   : false,
                ...
            },
        }
    }
于 2015-11-19T18:09:28.860 回答