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

100

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

Unknown provider: aProvider <- a

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

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

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

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

Der Hochstapler
источник
Вы можете попробовать добавить отдельные комментарии к каждому исходному файлу 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 .

Der Hochstapler
источник
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.

Xuwen
источник
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" или что-то еще ... вы увидите это в исходном контексте ...

не может быть проще ...

user3338098
источник
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