“Proveedor desconocido: aProvider <- a” ¿Cómo encuentro el proveedor original?

100

Cuando estoy cargando la versión minificada (a través de UglifyJS) de mi aplicación AngularJS, aparece el siguiente error en la consola:

Unknown provider: aProvider <- a

Ahora, me doy cuenta de que esto se debe a la alteración del nombre de las variables. La versión sin destrozar funciona bien. Sin embargo, yo no quiero hacer uso de renombrado de nombres variables, ya que reduce drásticamente el tamaño de nuestro archivo de salida JS.

Por esa razón, estamos usando ngmin en nuestro proceso de compilación, pero no parece resolver este problema, aunque nos sirvió bien en el pasado.

Entonces, para depurar este problema, habilité los mapas de origen en nuestra tarea uglify grunt. Se generan muy bien y Chrome hace cargar los mapas desde el servidor. Sin embargo, sigo recibiendo el mismo mensaje de error inútil, aunque tenía la impresión de que ahora debería ver el nombre original del proveedor.

¿Cómo consigo que Chrome use los mapas de origen para decirme qué proveedor es el problema aquí o, alternativamente, cómo puedo encontrar el proveedor de otra manera?

Der Hochstapler
fuente
Puede intentar agregar comentarios distintos a cada archivo fuente JS (si no es el caso) y usar la opción preserveComments de UglifyJS: eso le daría una idea de qué archivo contiene el código incorrecto.
JB Nizet
¿Estás usando decoradores? Descubrí que ngmin no parece reescribir los decoradores correctamente cuando lo he usado en el pasado, lo que genera errores como el tuyo.
dherman
@JBNizet: Me gusta la idea, pero agregar esa directiva a las opciones no parece tener ningún efecto.
Der Hochstapler
@dherman: ¿Podría darme un ejemplo de decoradores? No estoy seguro de cuáles serían en este contexto.
Der Hochstapler
Consulta github.com/gruntjs/grunt-contrib-uglify (si usas grunt). El valor de la opción debe ser "todos".
JB Nizet

Respuestas:

193

Todavía me encantaría saber cómo pude haber encontrado el lugar en nuestro código fuente que causó este problema, pero desde entonces he podido encontrar el problema manualmente.

Había una función de controlador declarada en el ámbito global, en lugar de usar una .controller()llamada en el módulo de la aplicación.

Entonces había algo como esto:

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

Esto funciona bien para AngularJS, pero para que funcione correctamente con la manipulación, tuve que cambiarlo a:

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

Después de más pruebas, encontré casos de más controladores que también causaron problemas. Así es como encontré la fuente de todos ellos manualmente :

En primer lugar, considero bastante importante habilitar el embellecimiento de salida en las opciones de uglify. Para nuestra dura tarea eso significaba:

options : {
    beautify : true,
    mangle   : true
}

Luego abrí el sitio web del proyecto en Chrome, con DevTools abierto. Lo que da como resultado que se registre un error como el siguiente:

ingrese la descripción de la imagen aquí

El método en el seguimiento de llamadas que nos interesa es el que marqué con una flecha. Esto está providerInjectoreninjector.js . Querrá colocar un punto de interrupción donde arroje una excepción:

ingrese la descripción de la imagen aquí

Cuando vuelva a ejecutar la aplicación, se alcanzará el punto de interrupción y podrá saltar la pila de llamadas. Habrá una llamada desde invokeadentroinjector.js , reconocible desde la cadena "Token de inyección incorrecto":

ingrese la descripción de la imagen aquí

El localsparámetro (alterado den mi código) da una idea bastante buena sobre qué objeto en su fuente es el problema:

ingrese la descripción de la imagen aquí

Un rápido grepvistazo a nuestra fuente encuentra muchas instancias de modalInstance, pero a partir de ahí, fue fácil encontrar este lugar en la fuente:

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

Que debe cambiarse a:

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

En caso de que la variable no contenga información útil, también puede saltar más en la pila y debe hacer una llamada a la invokeque debe tener sugerencias adicionales:

ingrese la descripción de la imagen aquí

Evita que esto vuelva a suceder

Ahora que, con suerte, ha encontrado el problema, creo que debería mencionar la mejor forma de evitar que esto vuelva a suceder en el futuro.

Obviamente, puede usar la anotación de matriz en línea en todas partes, o la $injectanotación de propiedad (según su preferencia) y simplemente tratar de no olvidarse de ella en el futuro. Si lo hace, asegúrese de habilitar el modo de inyección de dependencia estricta para detectar errores como este antes de tiempo.

¡Cuidado! En caso de que esté utilizando Angular Batarang, StrictDI podría no funcionar para usted, ya que Angular Batarang inyecta código no anotado en el suyo (¡Bad Batarang!).

O puede dejar que ng-annotate se encargue de ello. Recomiendo encarecidamente hacerlo, ya que elimina una gran cantidad de posibles errores en esta área, como:

  • Falta la anotación DI
  • Anotación DI incompleta
  • Anotación DI en orden incorrecto

Mantener las anotaciones actualizadas es simplemente un dolor de cabeza y no debería tener que hacerlo si se puede hacer automáticamente. ng-annotate hace exactamente eso.

Debería integrarse muy bien en su proceso de compilación con grunt-ng-annotate y gulp-ng-annotate .

Der Hochstapler
fuente
12
Este es un artículo fantástico, escrito con cuidado. Me acabo de encontrar con este problema, parece ser un problema profundo en ngmin en alguna parte. Tus consejos me ayudaron a saber dónde buscar. Al final, simplemente "ordené" todos mis parámetros angulares y el problema desapareció. Todas las compilaciones anteriores ng-minificaron muy bien, y no hubo cambios considerables. No agregué ninguna función global, ¿simplemente dejó de funcionar, misteriosamente, al alterar algún controlador / directiva / servicio / filtro?
Zenocón
Esta fue una gran fuente de ayuda. No sabía que tenía que usar la sintaxis de matriz (en línea) también para otras funciones, como resolución de enrutador, .run, .config, etc.
VDest
4
En mi caso fue controlador en directiva. Si en la variable 'd' verá $ attr, probablemente sea el mismo problema. Debe envolver los parámetros entre corchetes de matriz para el controlador de directiva interno. controlador: ["$ alcance", función ($ alcance) {...}] en lugar de controlador: función ($ alcance) {...}
alex naumov
Muchas gracias por su redacción y solución utilizando la notación de matriz / inyección de dependencia segura para la referencia de la función var. Yo también tuve este error y gracias a su solución pude seguir avanzando. ¡tú Molas!
Frankie Loscavio
1
Cada vez que tengo este problema, lo leo de nuevo y quiero votar a favor de nuevo. Por cierto, aquí se explica cómo configurar la versión de gulpuglify({ output : { beautify : true }})
Eugene Gluhotorenko
30

El artículo de Oliver Salzburg fue fantástico. Voto a favor.

Sugerencia para cualquiera que pueda tener este error. El mío simplemente fue causado por olvidar pasar una matriz para un controlador de directiva:

MALO

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

BUENO

return {
    restrict: "E",
    scope: {                
    },
    controller: ["$scope", ExampleDirectiveController],
    templateUrl: "template/url/here.html"
};
Ash Clarke
fuente
2
Este fue tan descarado ... ¡Uglify no me estaba causando esto hasta una actualización reciente!
SamMorrowDrums
Mi problema era el mismo, pero resulta que lo que necesitaba agregar era /* @ngInject */antes de la función. Parece hacer la parte de inyección complicada sin necesidad de escribir cada módulo incluido (estoy usando Yeoman)
Nicholas Blasgen
25

use ng-strict-di con ng-app

Si está utilizando angular 1.3 usted puede ahorrarse un mundo de dolor mediante el uso de ngStrictDi Directiva con ngApp:

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

Ahora, pre-minificación, cualquier cosa que no use anotaciones hará explotar tu consola y podrás ver el maldito nombre sin buscar entre los rastros de pila destrozados.

Según los documentos:

la aplicación no podrá invocar funciones que no utilicen anotaciones de función explícitas (y por lo tanto no son adecuadas para la minificación)

Una advertencia , sólo se detecta que no son anotaciones, no es que las anotaciones se han completado.

Sentido:

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

No captaré que ThingB no es parte de la anotación.

El crédito por este consejo es para la gente de ng-annotate , que se recomienda sobre el ahora obsoleto ngMin.

Mark Fox
fuente
Esto necesita más votos a favor. Esto es excelente para depurar una aplicación que nunca usó ngInject o la sintaxis de matriz de cadenas.
Michael Pearson
11

Para minimizar el angular, todo lo que necesita es cambiar su declaración al modo de declaración de "matriz", por ejemplo:

De:

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

A

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

¿Cómo declarar los servicios de fábrica?

demoApp.factory('demoFactory', ['$q', '$http', function ($q, $http) {
    return {
          //some object
    };
}]);
Dalorzo
fuente
Lo sé. Por eso usamos ngmin. Sospecho que tiene un problema con alguna parte de nuestra fuente o sus dependencias. Por eso estoy tratando de llegar a la raíz de este problema.
Der Hochstapler
1
Mi recomendación es que cree su código de esta manera. Para que puedas usar cualquier
minificador
3
Yo estoy creando nuestro código de esta manera. Pero tenemos dependencias externas que no. ngmin nos ha resuelto bien este problema en el pasado. Supongo que un cambio reciente creó este problema. Ahora me gustaría encontrar la fuente de este problema para poder solucionarlo correctamente en nuestro código, nuestra dependencia o posiblemente en ngmin.
Der Hochstapler
Dado que el problema parece muy específico para un componente o código en particular, es difícil proporcionar orientación, al menos desde mi punto de vista
Dalorzo
ngmin no requiere que use el modo de declaración de matriz, agrega muchas declaraciones inútiles.
Nanocom
8

Acabo de tener el mismo problema y lo resolví simplemente reemplazando ngmin (ahora en desuso) con ng-annotate para mi tarea de compilación grunt.

Parece que yeoman angular también se ha actualizado para usar ng-annotate a partir de este compromiso: https://github.com/yeoman/generator-angular/commit/3eea4cbeb010eeaaf797c17604b4a3ab5371eccb

Sin embargo, si está utilizando una versión anterior de yeoman angular como yo, simplemente reemplace ng-min con ng-annotate en su package.json:

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

ejecutar npm install(luego opcionalmente npm prune) y seguir los cambios en la confirmación para editar Gruntfile.js.

Xuwen
fuente
7

para saber cuál era el nombre de la variable original, puede cambiar cómo uglify manipula las 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;
    [...]
  }
};

y ahora el error es mucho más obvio

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)

EDITAR

Tan obvio ahora ...

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

ahora cada variable está alterada a un valor único que también contiene el original ... simplemente abra el javascript minificado y busque "a_orig_ $ stateProvider_91212" o lo que sea ... lo verá en su contexto original ...

no podría ser más fácil ...

usuario3338098
fuente
4

Además no olvide la resolvepropiedad de la ruta. También debe definirse como la matriz:

$routeProvider.when('/foo', {
    resolve: {
        bar: ['myService1', function(myService1) {
            return myService1.getThis();
        }],
        baz: ['myService2', function(myService2) {
            return myService2.getThat();
        }]
    }
});
Petr Felzmann
fuente
Esto me sucedió cuando agregué un montón de resoluciones a mis rutas. Potencialmente me ha ahorrado horas de dolorosa depuración, gracias.
Paul McClean
3

Con generador-gulp-angular:

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

}

Escriba / ** @ngInject * / antes de cada controlador, servicio, directiva.

Maxim Danilov
fuente
2

Una solución rápida y sucia para esto si no necesita que Uglify mangle / acorte los nombres de sus variables es establecer mangle = false en su Gruntfile

    uglify: {
        compile: {
            options: {
                mangle   : false,
                ...
            },
        }
    }
Parris Varney
fuente
Esto puede resolver el problema, pero el tamaño de compilación resultante será mayor ya que mangle está deshabilitado.
NotABot
aún más pequeño que no feo en absoluto
mjwrazor