«Неизвестный поставщик: aProvider <- a» Как мне найти исходного поставщика?


100

Когда я загружаю уменьшенную (через UglifyJS) версию моего приложения AngularJS, я получаю в консоли следующую ошибку:

Unknown provider: aProvider <- a

Теперь я понимаю, что это происходит из-за искажения имени переменной. Версия без запутывания работает нормально. Однако я действительно хочу использовать изменение имени переменной, поскольку оно резко уменьшает размер нашего выходного файла JS.

По этой причине мы используем ngmin в нашем процессе сборки, но, похоже, он не решает эту проблему, хотя в прошлом он хорошо служил нам.

Итак, чтобы отладить эту проблему, я включил исходные карты в нашей задаче uglify grunt. Они генерируются только штраф и Chrome делает загрузку карты с сервера. Тем не менее, я все еще получаю то же бесполезное сообщение об ошибке, хотя у меня создалось впечатление, что теперь я должен увидеть исходное имя поставщика.

Как заставить Chrome использовать исходные карты, чтобы сообщить мне, какой поставщик является проблемой здесь, или, как вариант, как я могу узнать поставщика другим способом?


Вы можете попробовать добавить отдельные комментарии к каждому исходному файлу JS (если это еще не так) и использовать опцию preserveComments в UglifyJS: это даст вам представление о том, какой файл содержит неправильный код.
— JB Nizet

Вы случайно не пользуетесь декораторами? Я обнаружил, что ngmin, похоже, не переписывает декораторы должным образом, когда я использовал его в прошлом, что приводит к таким ошибкам, как ваша.
— dherman

@JBNizet: Мне нравится идея, но добавление этой директивы к параметрам, похоже, не имеет никакого эффекта.
— Der Hochstapler

@dherman: Не могли бы вы привести мне пример декораторов? Я не уверен, какими они были бы в этом контексте.
— Der Hochstapler

См. Github.com/gruntjs/grunt-contrib-uglify (если вы используете grunt). Значение опции должно быть «все».
— JB Nizet

Ответы:


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. Это приводит к регистрации ошибки, подобной приведенной ниже:

введите описание изображения здесь

Нас интересует метод трассировки вызовов, который я пометил стрелкой. Это providerInjectorвinjector.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-annotate и gulp-ng-annotate .


12
Это фантастическая статья, написанная с осторожностью. Я только что столкнулся с этой проблемой, кажется, проблема где-то глубоко в ngmin. Ваши советы помогли мне узнать, где искать. В конце концов, я просто "упорядочил" все мои угловые параметры, и проблема исчезла. Все предыдущие сборки ng-minified просто отлично, и ничего существенного не изменилось. Я не добавил никаких глобальных функций - он просто загадочным образом перестал работать из-за искажения какого-то контроллера / директивы / службы / фильтра?
— zenocon

Это был отличный источник помощи. Я не знал, что вам нужно использовать синтаксис массива (встроенный) также для других функций, таких как разрешение маршрутизатора, .run, .config и т. Д.
— VDest

4
В моем случае это был контроллер в директиве. Если в переменной 'd' вы увидите $ attr, вероятно, это та же проблема. Вы должны заключить параметры в скобки массива для внутреннего контроллера директивы. controller: ["$ scope", function ($ scope) {...}] вместо controller: function ($ scope) {...}
— алексей наумов

Большое спасибо за вашу запись и решение с использованием безопасной инъекции зависимостей / нотации массива для ссылки на функцию var. У меня тоже была эта ошибка, и благодаря вашему решению я смог продолжить движение вперед. ты жжешь!
— Фрэнки Лоскавио

1
Каждый раз, когда у меня возникает этот вопрос, я перечитываю его и хочу снова проголосовать за него. Кстати, вот как настроить uglify({ output : { beautify : true }})
— gulp

30

Рецензия Оливера Зальцбурга была фантастической. Проголосовали.

Совет для всех, у кого может быть эта ошибка. Моя была просто вызвана тем, что я забыл передать массив для контроллера директивы:

ПЛОХОЙ

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)
— Николас Блазген

25

используйте 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.


Для этого нужно больше голосов. Это отлично подходит для отладки приложения, которое никогда не использовало ngInject или синтаксис массива строк.
— Майкл Пирсон

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. Я подозреваю, что у него проблема с какой-то частью нашего источника или его зависимостей. Вот почему я пытаюсь разобраться в сути этой проблемы.
— Der Hochstapler

1
Я рекомендую вам создавать свой код именно таким образом. Таким образом, вы можете использовать любой
— минификатор

3
Я буду создавать наш код таким образом. Но у нас есть внешние зависимости, которых нет. В прошлом ngmin хорошо решал эту проблему для нас. Я предполагаю, что недавнее изменение создало эту проблему. Теперь я хотел бы найти источник этой проблемы, чтобы я мог правильно исправить ее в нашем коде, нашей зависимости или, возможно, в самом ngmin.
— Der Hochstapler

Поскольку проблема звучит как очень специфическая для конкретного компонента или кода, трудно дать руководство, по крайней мере, с моей стороны,
— Далорцо

ngmin не требует использования режима объявления массива, он добавляет множество бесполезных объявлений.
— Nanocom

8

У меня была такая же проблема, и я решил ее, просто заменив ngmin (теперь устаревший) на ng-annotate для моей задачи сборки grunt.

Кажется, что yeoman angular также был обновлен для использования ng-annotate с момента этого коммита: https://github.com/yeoman/generator-angular/commit/3eea4cbeb010eeaaf797c17604b4a3ab5371eccb

Однако, если вы используете старую версию yeoman angular, как я, просто замените ng-min на ng-annotate в своем package.json:

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

run 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();
        }]
    }
});

Это случилось со мной, когда я добавил к своим маршрутам кучу решений. Вы потенциально сэкономили мне часы мучительной отладки, спасибо.
— Пол МакКлин

3

С генератором-глотком-угловым:

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

}

Напишите / ** @ngInject * / перед каждым контроллером, службой, директивой.


2

Быстрое и грязное исправление для этого, если вам не требуется, чтобы Uglify искажал / сокращал имена переменных, - это установить mangle = false в вашем Gruntfile.

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

Это может решить проблему, но результирующий размер сборки будет больше, так как mangle отключен.
— NotABot 02

все еще меньше, чем совсем не уродливо
— mjwrazor
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.