„Nieznany dostawca: aProvider <- a” Jak znaleźć pierwotnego dostawcę?

100

Kiedy ładuję zminimalizowaną (przez UglifyJS) wersję mojej aplikacji AngularJS, pojawia się następujący błąd w konsoli:

Unknown provider: aProvider <- a

Teraz zdaję sobie sprawę, że jest to spowodowane zniekształceniem nazw zmiennych. Wersja niemangled działa dobrze. Ja jednak nie chcą skorzystać z przekręcona nazwa zmiennej, gdyż drastycznie zmniejsza rozmiar pliku wyjściowego naszej JS.

Z tego powodu używamy ngmin w naszym procesie kompilacji, ale wydaje się, że nie rozwiązuje tego problemu, mimo że dobrze nam służył w przeszłości.

Tak więc, aby debugować ten problem, włączyłem mapy źródeł w naszym uglify gruntowym zadaniu. Są one generowane w porządku i Chrome ma załadować mapy z serwera. Mimo to nadal otrzymuję ten sam nieprzydatny komunikat o błędzie, chociaż miałem wrażenie, że powinienem teraz zobaczyć oryginalną nazwę dostawcy.

Jak sprawić, by Chrome używał map źródłowych, aby powiedzieć mi, który dostawca jest tutaj przyczyną problemu, lub alternatywnie, jak mogę znaleźć dostawcę w inny sposób?

Der Hochstapler
źródło
Możesz spróbować dodać oddzielne komentarze do każdego pliku źródłowego JS (jeśli jeszcze nie jest) i użyć opcji preserveComments UglifyJS: to dałoby ci wyobrażenie o tym, który plik zawiera nieprawidłowy kod.
JB Nizet,
Czy używasz dekoratorów? Zauważyłem, że ngmin nie wydaje się poprawnie przepisywać dekoratorów, gdy używałem go w przeszłości, co powoduje błędy takie jak twój.
dherman,
@JBNizet: Podoba mi się ten pomysł, ale dodanie tej dyrektywy do opcji nie wydaje się mieć żadnego efektu.
Der Hochstapler
@dherman: Czy możesz podać przykład dekoratorów? Nie jestem pewien, jakie byłyby w tym kontekście.
Der Hochstapler
Zobacz github.com/gruntjs/grunt-contrib-uglify (jeśli używasz grunt). Wartością opcji powinno być „all”.
JB Nizet,

Odpowiedzi:

193

Nadal chciałbym wiedzieć, jak mogłem znaleźć miejsce w naszym kodzie źródłowym, które spowodowało ten problem, ale od tego czasu udało mi się znaleźć problem ręcznie.

W zasięgu globalnym została zadeklarowana funkcja kontrolera zamiast .controller()wywołania modułu aplikacji.

Więc było coś takiego:

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

Działa to dobrze dla AngularJS, ale aby działało dobrze z zniekształceniem, musiałem to zmienić na:

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

Po dalszych testach faktycznie znalazłem wystąpienia większej liczby kontrolerów, które również powodowały problemy. Oto jak ręcznie znalazłem źródło ich wszystkich :

Przede wszystkim uważam za dość ważne włączenie upiększania produkcji w opcjach uglify. W przypadku naszego podstawowego zadania oznaczało to:

options : {
    beautify : true,
    mangle   : true
}

Następnie otworzyłem witrynę projektu w przeglądarce Chrome z otwartymi narzędziami DevTools. Co powoduje rejestrowanie błędu takiego jak ten poniżej:

wprowadź opis obrazu tutaj

Metoda w śledzeniu wywołań, która nas interesuje, to ta, którą zaznaczyłem strzałką. To jest providerInjectorwinjector.js . Będziesz chciał umieścić punkt przerwania, w którym zgłosi wyjątek:

wprowadź opis obrazu tutaj

Kiedy teraz ponownie uruchomisz aplikację, punkt przerwania zostanie osiągnięty i możesz przeskoczyć w górę stosu wywołań. Pojawi się wywołanie z invokeininjector.js , które można rozpoznać po ciągu „Nieprawidłowy token wstrzyknięcia”:

wprowadź opis obrazu tutaj

localsParametr (zniekształcone, aby dw moim kodu) daje całkiem dobry pomysł, o który obiekt w źródle jest problem:

wprowadź opis obrazu tutaj

Szybkie przeglądanie grepnaszego źródła znajduje wiele przykładów modalInstance, ale stamtąd łatwo było znaleźć to miejsce w źródle:

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

Które należy zmienić na:

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

W przypadku, gdy zmienna nie zawiera przydatnych informacji, możesz również wskoczyć dalej na stos i powinieneś trafić call, do invokektórego powinno mieć dodatkowe podpowiedzi:

wprowadź opis obrazu tutaj

Zapobiegaj ponownemu wystąpieniu tego zdarzenia

Teraz, gdy masz nadzieję, że znalazłeś problem, czuję, że powinienem wspomnieć, jak najlepiej uniknąć powtórzenia się tego w przyszłości.

Oczywiście możesz po prostu użyć adnotacji tablicy wbudowanej wszędzie lub (w zależności od twoich preferencji) $injectadnotacji właściwości i po prostu spróbować nie zapomnieć o tym w przyszłości. Jeśli to zrobisz, upewnij się, że włączyłeś tryb ścisłego wstrzykiwania zależności , aby wcześnie wychwycić takie błędy.

Uważaj! Jeśli używasz Angular Batarang, StrictDI może nie działać dla Ciebie, ponieważ Angular Batarang wstrzykuje niezanotowany kod do twojego (zły Batarang!).

Możesz też pozwolić, by ng-annotate się tym zajął. Gorąco polecam, ponieważ usuwa wiele potencjalnych błędów w tym obszarze, takich jak:

  • Brak adnotacji DI
  • Niekompletna adnotacja DI
  • Adnotacja DI w złej kolejności

Utrzymywanie aktualności adnotacji to po prostu wrzód na dupie i nie powinieneś tego robić, jeśli można to zrobić automatycznie. ng-annotate robi dokładnie to.

Powinien ładnie zintegrować się z procesem kompilacji za pomocą grunt-ng-annotate i gulp-ng-annotate .

Der Hochstapler
źródło
12
To fantastyczny opis, napisany starannie. Właśnie natknąłem się na ten problem, wydaje się, że jest to problem gdzieś głęboko w ngmin. Twoje wskazówki pomogły mi wiedzieć, gdzie szukać. W końcu po prostu „zestawiłem” wszystkie moje parametry kątowe i problem zniknął. Wszystkie wcześniejsze kompilacje ng-zminifikowały się dobrze i nic znaczącego się nie zmieniło. Nie dodałem żadnych funkcji globalnych - po prostu przestał działać, tajemniczo, przez zniekształcenie jakiegoś kontrolera / dyrektywy / usługi / filtra?
zenocon
To było świetne źródło pomocy. Nie wiedziałem, że musisz używać składni array (inline) również do innych funkcji, takich jak rozwiązywanie routerów, .run, .config itp.
VDest,
4
W moim przypadku był to kontroler w dyrektywie. Jeśli w zmiennej 'd' zobaczysz $ attr, to prawdopodobnie ten sam problem. Parametry należy zawijać w nawiasy tablicowe dla wewnętrznego kontrolera dyrektywy. controller: ["$ scope", function ($ scope) {...}] zamiast controller: function ($ scope) {...}
alex naumov
Dziękuję bardzo za napisanie i rozwiązanie przy użyciu bezpiecznego iniekcji zależności / notacji tablicy dla odwołania do funkcji var. Ja też miałem ten błąd i dzięki twojemu rozwiązaniu mogłem iść naprzód. rządzisz!
Frankie Loscavio
1
Za każdym razem, gdy mam ten numer, czytam to ponownie i chcę ponownie zagłosować na to. Przy okazji, oto jak ustawić wersję uglify({ output : { beautify : true }})
łykową
30

Opis Olivera Salzburga był fantastyczny. Głosowano za.

Wskazówka dla każdego, kto może mieć ten błąd. Mój był po prostu spowodowany zapomnieniem o przekazaniu tablicy dla kontrolera dyrektywy:

ZŁY

return {
    restrict: "E",
    scope: {                
    },
    controller: ExampleDirectiveController,
    templateUrl: "template/url/here.html"
};

DOBRY

return {
    restrict: "E",
    scope: {                
    },
    controller: ["$scope", ExampleDirectiveController],
    templateUrl: "template/url/here.html"
};
Ash Clarke
źródło
2
To było takie bezczelne ... Uglify powodowało to u mnie dopiero po niedawnej aktualizacji!
SamMorrowDrums
Mój problem był ten sam, ale okazuje się, że to, co musiałem dodać, było /* @ngInject */przed funkcją. Wydaje się, że wykonuje skomplikowaną część wtrysku bez konieczności wpisywania każdego dołączonego modułu (używam Yeoman)
Nicholas Blasgen
25

użyj ng-strict-di z ng-app

Jeśli używasz kątowych 1.3 można zapisać się do świata bólu za pomocą ngStrictDi dyrektywę z ngApp:

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

Teraz - preminifikacja - wszystko, co nie używa adnotacji, wysadzi twoją konsolę i możesz zobaczyć imię pieprzonego człowieka bez szukania zniekształconych śladów stosu.

Zgodnie z dokumentacją:

aplikacja nie będzie w stanie wywołać funkcji, które nie używają jawnej adnotacji funkcji (a zatem nie nadają się do minifikacji)

Jedno zastrzeżenie , to tylko wykryje, że tam adnotacje, że adnotacje nie są kompletne.

Znaczenie:

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

Nie złapie, że ThingB nie jest częścią adnotacji.

Podziękowania za tę wskazówkę należą się osobom z adnotacjami ng , które są zalecane w stosunku do obecnie przestarzałego ngMin.

Mark Fox
źródło
To wymaga więcej głosów pozytywnych. Jest to świetne do debugowania aplikacji, która nigdy nie używała ngInject lub składni tablicy ciągów.
Michael Pearson,
11

Aby zminimalizować angular wszystko, co musisz zrobić, to zmienić swoją deklarację na tryb deklaracji tablicy, na przykład:

Z:

var demoApp= angular.module('demoApp', []);
demoApp.controller(function demoCtrl($scope) {
} );

Do

var demoApp= angular.module('demoApp', []);
demoApp.controller(["$scope",function demoCtrl($scope) {
}]);

Jak zadeklarować usługi fabryczne?

demoApp.factory('demoFactory', ['$q', '$http', function ($q, $http) {
    return {
          //some object
    };
}]);
Dalorzo
źródło
Wiem. Dlatego używamy ngmin. Podejrzewam, że ma problem z jakąś częścią naszego źródła lub jego zależnościami. Dlatego próbuję dotrzeć do źródła tego problemu.
Der Hochstapler
1
Moim zaleceniem jest tworzenie kodu w ten sposób. Możesz więc użyć dowolnego minifikatora
Dalorzo
3
I 'm tworzenia naszego kodu w ten sposób. Ale mamy zewnętrzne zależności, które tego nie robią. W przeszłości firma ngmin dobrze rozwiązała ten problem. Zakładam, że ostatnia zmiana spowodowała ten problem. Chciałbym teraz znaleźć źródło tego problemu, aby móc go odpowiednio naprawić w naszym kodzie, naszej zależności lub ewentualnie w samym ngmin.
Der Hochstapler
Ponieważ problem brzmi jak bardzo specyficzny dla konkretnego komponentu lub kodu, trudno jest udzielić wskazówek, przynajmniej z mojej strony
Dalorzo
ngmin nie wymaga używania trybu deklaracji tablicy, dodaje wiele bezużytecznych deklaracji.
Nanocom,
8

Po prostu miałem ten sam problem i rozwiązałem go, po prostu zastępując ngmin (obecnie przestarzałe) przez ng-annotate dla mojego zadania kompilacji gruntów.

Wygląda na to, że yeoman angular został również zaktualizowany, aby używać ng-annotate od tego zatwierdzenia: https://github.com/yeoman/generator-angular/commit/3eea4cbeb010eeaaf797c17604b4a3ab5371eccb

Jeśli jednak używasz starszej wersji yeoman angular jak ja, po prostu zamień ng-min na ng-annotate w swoim package.json:

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

uruchom npm install(następnie opcjonalnie npm prune) i postępuj zgodnie ze zmianami w zatwierdzeniu do edycji Gruntfile.js.

Xuwen
źródło
7

aby wiedzieć, jaka była oryginalna nazwa zmiennej, możesz zmienić sposób, w jaki zniekształcają zmienne:

../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;
    [...]
  }
};

a teraz błąd jest znacznie bardziej oczywisty

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)

EDYTOWAĆ

Teraz tak oczywiste ...

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++;
    [...]
  }
};

teraz każda zmienna jest zniekształcana do unikalnej wartości, która zawiera również oryginał ... po prostu otwórz zminimalizowany skrypt javascript i wyszukaj „a_orig_ $ stateProvider_91212” lub cokolwiek ... zobaczysz to w oryginalnym kontekście ...

nie może być prostsze ...

user3338098
źródło
4

Nie zapomnij również o resolvewłaściwościach trasy. Musi być również zdefiniowany jako tablica:

$routeProvider.when('/foo', {
    resolve: {
        bar: ['myService1', function(myService1) {
            return myService1.getThis();
        }],
        baz: ['myService2', function(myService2) {
            return myService2.getThat();
        }]
    }
});
Petr Felzmann
źródło
Zdarzyło mi się to, gdy dodałem kilka rozwiązań do moich tras. Potencjalnie zaoszczędziłeś mi wiele godzin bolesnego debugowania, dzięki.
Paul McClean
3

Z generatorem-łykiem-kątowym:

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

}

Napisz / ** @ngInject * / przed każdym kontrolerem, usługą, dyrektywą.

Maxim Danilov
źródło
2

Szybką i brudną poprawką, jeśli nie potrzebujesz Uglify do modyfikowania / skracania nazw zmiennych, jest ustawienie mangle = false w pliku Gruntfile

    uglify: {
        compile: {
            options: {
                mangle   : false,
                ...
            },
        }
    }
Parris Varney
źródło
Może to rozwiązać problem, ale wynikowy rozmiar kompilacji będzie większy, ponieważ maglowanie jest wyłączone.
NotABot
wciąż mniejszy niż wcale nie brzydki
mjwrazor