«Fournisseur inconnu: aProvider <- a» Comment trouver le fournisseur d'origine?

100

Lorsque je charge la version minifiée (via UglifyJS) de mon application AngularJS, j'obtiens l'erreur suivante dans la console:

Unknown provider: aProvider <- a

Maintenant, je me rends compte que cela est dû à un changement de nom variable. La version démêlée fonctionne très bien. Cependant, je ne veux utiliser mangling nom de la variable, car elle réduit considérablement la taille de notre fichier de sortie JS.

Pour cette raison, nous utilisons ngmin dans notre processus de construction, mais cela ne semble pas résoudre ce problème, même s'il nous a bien servi dans le passé.

Donc, pour déboguer ce problème, j'ai activé les cartes sources dans notre tâche uglify grunt. Ils sont générés très bien et Chrome ne charge les cartes du serveur. Pourtant, j'obtiens toujours le même message d'erreur inutile, même si j'avais l'impression que je devrais maintenant voir le nom d'origine du fournisseur.

Comment puis-je faire en sorte que Chrome utilise les cartes sources pour me dire quel fournisseur est le problème ici, ou, alternativement, comment puis-je trouver le fournisseur d'une autre manière?

Der Hochstapler
la source
Vous pouvez essayer d'ajouter des commentaires distincts à chaque fichier source JS (si ce n'est déjà le cas) et utiliser l'option preserveComments de UglifyJS: cela vous donnerait une idée du fichier contenant le code incorrect.
JB Nizet
Utilisez-vous des décorateurs? J'ai constaté que ngmin ne semble pas réécrire correctement les décorateurs lorsque je l'ai utilisé dans le passé, ce qui entraîne des erreurs comme la vôtre.
dherman
@JBNizet: J'aime l'idée, mais l'ajout de cette directive aux options ne semble pas avoir d'effet.
Der Hochstapler
@dherman: Pouvez-vous me donner un exemple de décorateurs? Je ne sais pas ce qu'ils seraient dans ce contexte.
Der Hochstapler
Voir github.com/gruntjs/grunt-contrib-uglify (si vous utilisez grunt). La valeur de l'option doit être "tous".
JB Nizet

Réponses:

193

J'adorerais toujours savoir comment j'aurais pu trouver l'emplacement dans notre code source qui a causé ce problème, mais j'ai depuis pu trouver le problème manuellement.

Il y avait une fonction de contrôleur déclarée sur la portée globale, au lieu d'utiliser un .controller()appel sur le module d'application.

Il y avait donc quelque chose comme ça:

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

Cela fonctionne très bien pour AngularJS, mais pour que cela fonctionne correctement avec le mangling, j'ai dû le changer en:

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

Après d'autres tests, j'ai trouvé des instances de plusieurs contrôleurs qui ont également causé des problèmes. Voici comment j'ai trouvé la source de chacun d'eux manuellement :

Tout d'abord, je considère qu'il est assez important d'activer l'embellissement de la sortie dans les options uglify. Pour notre tâche difficile, cela signifiait:

options : {
    beautify : true,
    mangle   : true
}

J'ai ensuite ouvert le site Web du projet dans Chrome, avec les DevTools ouverts. Ce qui entraîne une erreur comme celle ci-dessous enregistrée:

entrez la description de l'image ici

La méthode de la trace d'appel qui nous intéresse est celle que j'ai marquée d'une flèche. C'est providerInjectordansinjector.js . Vous allez vouloir placer un point d'arrêt où il lève une exception:

entrez la description de l'image ici

Lorsque vous réexécutez maintenant l'application, le point d'arrêt est atteint et vous pouvez sauter dans la pile d'appels. Il y aura un appel de invokeininjector.js , reconnaissable à partir de la chaîne "Jeton d'injection incorrect":

entrez la description de l'image ici

Le localsparamètre (mutilé ddans mon code) donne une assez bonne idée de quel objet dans votre source est le problème:

entrez la description de l'image ici

Un rapide grepsur notre source trouve de nombreuses instances de modalInstance, mais à partir de là, il était facile de trouver cet endroit dans la source:

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

Qui doit être changé en:

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

Dans le cas où la variable ne contient pas d'informations utiles, vous pouvez également sauter plus haut dans la pile et vous devriez frapper un appel invokequi devrait avoir des indices supplémentaires:

entrez la description de l'image ici

Empêcher que cela ne se reproduise

Maintenant que vous avez trouvé le problème avec un peu de chance, je pense que je devrais mentionner la meilleure façon d'éviter que cela ne se reproduise à l'avenir.

De toute évidence, vous pouvez simplement utiliser l' annotation de tableau en ligne partout, ou l' $injectannotation de propriété (selon vos préférences) et essayer simplement de ne pas l'oublier à l'avenir. Si vous le faites, assurez-vous d'activer le mode d'injection de dépendances strict , pour détecter les erreurs comme celle-ci tôt.

Fais attention! Si vous utilisez Angular Batarang, StrictDI pourrait ne pas fonctionner pour vous, car Angular Batarang injecte du code non annoté dans le vôtre (mauvais Batarang!).

Ou vous pouvez laisser ng-annotate s'en occuper. Je recommande vivement de le faire, car cela supprime beaucoup de risques d'erreurs dans ce domaine, comme:

  • Annotation DI manquante
  • Annotation DI incomplète
  • Annotation DI dans le mauvais ordre

Garder les annotations à jour est tout simplement une douleur dans le cul et vous ne devriez pas avoir à le faire si cela peut être fait automatiquement. ng-annotate fait exactement cela.

Il devrait s'intégrer parfaitement dans votre processus de construction avec grunt-ng-annotate et gulp-ng-annotate .

Der Hochstapler
la source
12
C'est un article fantastique, écrit avec soin. Je viens de rencontrer ce problème, semble être un problème au fond de ngmin quelque part. Vos conseils m'ont aidé à savoir où chercher. En fin de compte, je viens de "array-ified" tous mes paramètres angulaires, et le problème a disparu. Toutes les versions précédentes ng-minified très bien, et rien de considérable n'a changé. Je n'ai pas ajouté de fonctions globales - il a juste cessé de fonctionner, mystérieusement, en manipulant un contrôleur / une directive / un service / un filtre?
zenocon
C'était une grande source d'aide. Je ne savais pas que vous deviez utiliser la syntaxe de tableau (en ligne) également pour d'autres fonctions, comme la résolution de routeur, .run, .config, etc.
VDest
4
Dans mon cas, c'était contrôleur en directive. Si dans la variable 'd' vous verrez $ attr, c'est probablement le même problème. Vous devez mettre les paramètres entre crochets pour le contrôleur de directive interne. controller: ["$ scope", function ($ scope) {...}] au lieu de controller: function ($ scope) {...}
alex naumov
Merci beaucoup pour votre rédaction et votre solution utilisant l'injection de dépendances sécurisée / la notation de tableau pour la référence de la fonction var. J'ai moi aussi eu cette erreur et grâce à votre solution, j'ai pu continuer à avancer. tu gères!
Frankie Loscavio
1
Chaque fois que j'ai ce numéro, je le relis et je veux voter à nouveau pour cela. Btw, voici comment configurer la version uglify({ output : { beautify : true }})
gulp
30

La rédaction d'Oliver Salzburg était fantastique. J'ai voté pour.

Astuce pour tous ceux qui pourraient avoir cette erreur. Le mien a simplement été causé par l'oubli de passer dans un tableau pour un contrôleur de directive:

MAUVAIS

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

BIEN

return {
    restrict: "E",
    scope: {                
    },
    controller: ["$scope", ExampleDirectiveController],
    templateUrl: "template/url/here.html"
};
Ash Clarke
la source
2
C'était tellement effronté ... Uglify ne me causait pas cela jusqu'à une récente mise à jour!
SamMorrowDrums
Mon problème était le même, mais il s'avère que ce que j'avais besoin d'ajouter était /* @ngInject */avant la fonction. Il semble faire la partie d'injection compliquée sans avoir besoin de taper chaque module inclus (j'utilise Yeoman)
Nicholas Blasgen
25

utiliser ng-strict-di avec ng-app

Si vous utilisez angulaire 1.3 vous pouvez vous sauver un monde de mal en utilisant ngStrictDi directive avec ngApp:

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

Maintenant - pré-minification - tout ce qui n'utilise pas d' annotations fera exploser votre console et vous pouvez voir le nom de friggin 'sans chercher à travers les traces de pile mutilées.

Selon les documents:

l'application échouera à invoquer des fonctions qui n'utilisent pas d'annotation de fonction explicite (et ne sont donc pas adaptées à la minification)

Une mise en garde , il détecte seulement qu'il ya des annotations, pas que les annotations sont terminées.

Sens:

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

Ne comprendra pas que ThingB ne fait pas partie de l'annotation.

Le mérite de cette astuce va aux gens de ng-annotate , ce qui est recommandé par rapport au ngMin désormais obsolète.

Mark Fox
la source
Cela nécessite plus de votes positifs. C'est idéal pour déboguer une application qui n'a jamais utilisé ngInject ou la syntaxe du tableau de chaînes.
Michael Pearson
11

Pour minimiser angulaire, tout ce que vous avez à faire est de changer votre déclaration en "mode" déclaration "tableau" par exemple:

De:

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

À

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

Comment déclarer les services d'usine?

demoApp.factory('demoFactory', ['$q', '$http', function ($q, $http) {
    return {
          //some object
    };
}]);
Dalorzo
la source
Je connais. C'est pourquoi nous utilisons ngmin. Je soupçonne qu'il a un problème avec une partie de notre source ou ses dépendances. C'est pourquoi j'essaie d'aller à la racine de ce problème.
Der Hochstapler
1
Ma recommandation est que vous créez votre code de cette manière. Vous pouvez donc utiliser n'importe quel minificateur
Dalorzo
3
Je suis en train de créer notre code de cette façon. Mais nous avons des dépendances externes qui n'en ont pas. ngmin a bien résolu ce problème pour nous dans le passé. Je suppose qu'un changement récent a créé ce problème. Je voudrais maintenant trouver la source de ce problème afin de pouvoir le corriger correctement dans notre code, notre dépendance ou éventuellement dans ngmin lui-même.
Der Hochstapler
Étant donné que le problème semble très spécifique à un composant ou un code particulier, il est difficile de fournir des conseils, du moins de ma fin
Dalorzo
ngmin ne vous oblige pas à utiliser le mode de déclaration de tableau, il ajoute beaucoup de déclarations inutiles.
Nanocom
8

J'ai juste eu le même problème et je l'ai résolu en remplaçant simplement ngmin (maintenant obsolète) par ng-annotate pour ma tâche de construction grunt.

Il semble que yeoman angular a également été mis à jour pour utiliser ng-annotate à partir de ce commit: https://github.com/yeoman/generator-angular/commit/3eea4cbeb010eeaaf797c17604b4a3ab5371eccb

Cependant, si vous utilisez une ancienne version de yeoman angular comme moi, remplacez simplement ng-min par ng-annotate dans votre package.json:

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

run npm install(puis éventuellement npm prune), et suivez les changements dans le commit pour éditer Gruntfile.js.

Xuwen
la source
7

afin de savoir quel était le nom d'origine de la variable, vous pouvez modifier la façon dont uglify modifie les variables:

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

et maintenant l'erreur est beaucoup plus évidente

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)

ÉDITER

Si évident maintenant ...

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

maintenant chaque variable est transformée en une valeur unique qui contient également l'original ... ouvrez simplement le javascript minifié et recherchez "a_orig_ $ stateProvider_91212" ou autre ... vous le verrez dans son contexte d'origine ...

ne pourrait pas être plus simple ...

user3338098
la source
4

N'oubliez pas non plus la resolvepropriété de l'itinéraire. Il doit également être défini comme le tableau:

$routeProvider.when('/foo', {
    resolve: {
        bar: ['myService1', function(myService1) {
            return myService1.getThis();
        }],
        baz: ['myService2', function(myService2) {
            return myService2.getThat();
        }]
    }
});
Petr Felzmann
la source
Cela m'est arrivé lorsque j'ai ajouté un tas de résolutions à mes itinéraires. Vous m'avez potentiellement sauvé des heures de débogage douloureux, merci.
Paul McClean
3

Avec générateur-gulp-angular:

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

}

Écrivez / ** @ngInject * / avant chaque contrôleur, service, directive.

Maxim Danilov
la source
2

Une solution rapide et sale pour cela si vous n'avez pas besoin d'Uglify pour modifier / raccourcir vos noms de variables est de définir mangle = false dans votre Gruntfile

    uglify: {
        compile: {
            options: {
                mangle   : false,
                ...
            },
        }
    }
Parris Varney
la source
Cela peut résoudre le problème, mais la taille de construction résultante sera plus grande car mangle est désactivé.
NotABot le
encore plus petit que pas du tout
laid