Error de minificación del módulo angular

82

Pasar el tiempo más divertido tratando de descubrir por qué la minificación no funciona.

He inyectado a través de un objeto de matriz a mis proveedores antes de la función según numerosas sugerencias en la web y, sin embargo, todavía "Proveedor desconocido: aProvider <- a"

Regular:

var app = angular.module('bpwApp', ['ui.bootstrap', 'ui', 'myTabs'])
    .config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider){
    $routeProvider.
        when('/', {templateUrl: 'partials/home.jade', controller: HomeCtrl});

    $locationProvider.html5Mode(true);
    }])

Minificado:

var app = angular.module('bpwApp', ['ui.bootstrap', 'ui', 'myTabs'])
    .config(['$routeProvider', '$locationProvider', function(a, b){
    a.
        when('/', {templateUrl: 'partials/home.jade', controller: HomeCtrl});

    b.html5Mode(true);
    }])

¡Cualquier sugerencia sería muy agradecida!

BPWDesarrollo
fuente
1
¿Qué usas para minimizar tu código? uglifyJS? Consulte también: github.com/btford/ngmin ;)
AndreM96
Usé ngmin, todo lo que hizo fue alinear el código en un formato de espacio en blanco diferente. Intenté usar express-uglify como middleware pero no funcionaba, así que intenté usar manualmente un uglifier en línea. De cualquier manera, el código terminó igual.
BPWDevelopment
Además, ¿no falta una ]? (antes del cierre ))
AndreM96
Los había, los olvidé en este fragmento en particular. No cambia el hecho de que "proveedor desconocido a" todavía ocurre :(
BPWDevelopment
2
Ok, bueno, ¿qué minificador en línea usaste? Esto funciona bien con su código: marijnhaverbeke.nl/uglifyjs
AndreM96

Respuestas:

139

Me encontré con este problema antes con el complemento Grunt.js Uglify .

Una de las opciones es mangle

uglify: {
  options: {
    mangle: false
  },

Lo que creo que ejecuta funciones de expresiones regulares en "cadenas similares" y las minimiza.

Por ejemplo:

angular.module("imgur", ["imgur.global","imgur.album"]);

Se convertiría:

angular.module("a", ["a.global","a.album"]);

Desactívelo: esta función no funciona bien con Angular.

Editar:

Para ser más precisos, como explica @JoshDavidMiller:

Uglify manglesolo modifica variables similares, que es lo que realmente causa el problema de AngularJS. Es decir, el problema está en la inyección y no en la definición.

function MyCtrl($scope, myService)se estropearía function MyCtrl(a, b), pero la definición de servicio dentro de una cadena nunca debería modificarse.

  • Ejecutar ng-minantes de ejecutar uglifyresuelve este problema.
Dan Kanze
fuente
3
Actualizó su código. Su problema no era que "$ locationProvider" se convirtiera en "b" ni nada por el estilo. Simplemente no funcionó. Sin embargo, +1 para esta respuesta :)
AndreM96
1
Gracias estaba buscando con dificultad para encontrar esa opción.
reen
Me encontré con exactamente lo mismo cuando usé angular bootstrap + yeoman. Usando el generador angular yeoman produjo una distcompilación que tendría el error de dependencia mencionado "Proveedor desconocido". La configuración mangle: falseresolvió el problema. (nota: el problema fue solo un problema en la compilación gruntconstruida, distno en la appcompilación amigable para el desarrollador)
craigb
6
¿ mangle: true Realmente destroza "como cuerdas"? Estoy bastante seguro de que solo se estropea como variables , que es lo que realmente causa el problema de AngularJS. Es decir, el problema está en la inyección y no en la definición. function MyCtrl($scope, myService)se estropearía function MyCtrl(a, b), pero la definición de servicio dentro de una cadena nunca debería modificarse. Ejecutar ng-minantes de ejecutar uglifyresuelve este problema, ¿no?
Josh David Miller
1
ng-minahora está en desuso a favor deng-annotate
Atav32
51

Problema

Desde AngularJS: The Bad Parts :

Angular tiene un inyector de dependencia incorporado que pasará los objetos apropiados a su función en función de los nombres de sus parámetros:

function MyController($scope, $window) {
    // ...
}

Aquí, los nombres de los parámetros $scopey$window se compararán con una lista de nombres conocidos, y los objetos correspondientes se instancian y pasan a la función. Angular obtiene los nombres de los parámetros llamando toString()a la función y luego analizando la definición de la función.

El problema con esto, por supuesto, es que deja de funcionar en el momento en que minimizas tu código . Dado que le importa la experiencia del usuario, estará minimizando su código, por lo que el uso de este mecanismo DI romperá su aplicación. De hecho, una metodología de desarrollo común es utilizar código no minificado en el desarrollo para facilitar la depuración y luego minificar el código al pasar a producción o puesta en escena. En ese caso, este problema no levantará su fea cabeza hasta que esté en el punto donde más duele.

(...)

Dado que este mecanismo de inyección de dependencia no funciona en el caso general, Angular también proporciona un mecanismo que sí lo hace. Sin duda, proporciona dos. Puede pasar una matriz como esta:

module.controller('MyController', ['$scope', '$window', MyController]);

O puede establecer la $injectpropiedad en su constructor:

MyController.$inject = ['$scope', '$window'];

Solución

Puede usar ng-annotatepara agregar automáticamente las anotaciones necesarias para minificar:

ng-annotateagrega y elimina anotaciones de inyección de dependencia de AngularJS. No es intrusivo, por lo que su código fuente permanece exactamente igual. Sin comentarios perdidos ni líneas movidas.

ng-annotatees más rápido y estable que ngmin(que ahora está en desuso) y tiene complementos para muchas herramientas:


A partir de AngularJS 1.3 también hay un nuevo parámetro ngAppllamado ngStrictDi:

si este atributo está presente en el elemento de la aplicación, el inyector se creará en modo "estricto-di". Esto significa que 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), como se describe en la guía de inyección de dependencia , y la información de depuración útil ayudará a rastrear la raíz de estos errores.

Paolo Moretti
fuente
1
+1 Simplemente cambiar a grunt-ng-annotate solucionó este problema y ngmin ahora está obsoleto de todos modos, así que esa es otra razón para cambiar.
Pier-Luc Gendreau
¡Esa fue la solución que estaba buscando durante días!
jack.the.ripper
Estoy enfrentando el mismo problema al crear un código minificado con browserify, angular y gulp-minify. Eliminé gulp minify y lo reemplacé por gulp-ng-annotate, el código aún está minificado pero aún no funciona.
Dimitri Kopriwa
@BigDong, si está utilizando browserify, la mejor manera es probablemente habilitar ngStrictDi(algo así como <div ng-app="myApp" ng-strict-di />) y usar gulp-ng-annotateincluso en su entorno de desarrollo para que pueda rastrear fácilmente estos errores de minificación.
Paolo Moretti
@PaoloMoretti Lo intenté con ngStrictDi y gulp-ng-annotate, browserify se puede agrupar pero el código no está reducido, ¿no se supone que es un trabajo ng-annotate?
Dimitri Kopriwa
22

Tengo el mismo error. Sin embargo, para mí, el problema es la declaración del controlador de las directivas. Deberías hacer esto en su lugar.

myModule.directive('directiveName', function factory(injectables) {
    var directiveDefinitionObject = {
      templateUrl: 'directive.html',
      replace: false,
      restrict: 'A',
      controller: ["$scope", "$element", "$attrs", "$transclude", "otherInjectables",
        function($scope, $element, $attrs, $transclude, otherInjectables) { ... }]
    };
    return directiveDefinitionObject;
  });

https://github.com/angular/angular.js/pull/3125

angelokh
fuente
1
¡Gracias @angelokh! Tenía exactamente este problema. Estaba usando la controller: function ($scope) {}notación.
jbasko
2
Esto se parece más a la solución al problema real de lo que se mangle: falsesugiere en otras respuestas, porque todavía queremos poder modificar los nombres.
jbasko
9

Tuve un problema similar al usar grunt, ngmin y uglify.

Ejecuté el proceso en este orden: concat, ngmin, uglify

Seguí obteniendo el error $ injector de angular hasta que agregué las opciones de uglify mangle: false, luego todo se solucionó.

También intenté agregar las excepciones para uglify así:

 options: {
  mangle: {
     except: ['jQuery', 'angular']
  }
}

Pero fue en vano...

Aquí está mi gruntFile.js para mayor aclaración:

module.exports = function(grunt) {
'use strict';
// Configuration goes here
grunt.initConfig({
    pkg: require('./package.json'),

    watch: {
        files: ['scripts/**/*.js', 'test/**/*spec.js', 'GruntFile.js'],
        tasks: ['test', 'ngmin']
    },

    jasmine : {
        // Your project's source files
        src : ['bower_components/angular/angular.js', 'bower_components/angular-mocks/angular-mocks.js', 'scripts/app.js', 'scripts/**/*.js' ],
        // Your Jasmine spec files

        options : {
            specs : 'test/**/*spec.js',
            helpers: 'test/lib/*.js'
        }
    },

    concat: {
      dist : {
          src: ['scripts/app.js', 'scripts/**/*.js'],
          dest: 'production/js/concat.js'
      }
    },

    ngmin: {
        angular: {
            src : ['production/js/concat.js'],
            dest : 'production/js/ngmin.js'
        }

    },

    uglify : {
        options: {
            report: 'min',
            mangle: false
        },
        my_target : {
            files : {
                'production/app/app.min.js' : ['production/js/ngmin.js']
            }
        }
    },

  docular : {
      groups: [],
      showDocularDocs: false,
      showAngularDocs: false
  }

});

// Load plugins here
grunt.loadNpmTasks('grunt-ngmin');
grunt.loadNpmTasks('grunt-docular');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-jasmine');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-connect');

// Define your tasks here
grunt.registerTask('test', ['jasmine']);
grunt.registerTask('build', ['concat', 'ngmin', 'uglify']);
grunt.registerTask('default', ['test', 'build', 'watch']);

};

Sten Muchow
fuente
¡AH GRACIAS, GRACIAS! eso me ha ahorrado tanto tiempo.
mylescc
5

La sugerencia de AndrewM96 ng-mines correcta.

La alineación y el espacio en blanco son importantes para Uglify y Angular.

BPWDesarrollo
fuente
10
ng-min parece simplemente procesar los archivos angulares para que sean amigables uglify. En nuestro proceso de compilación usamos ambos ( ng-minantes uglify) y todavía teníamos un problema con el js feo.
craigb
4
¿Por qué está marcado esto como la respuesta? (Además, AndrewM96 debería ser AndreM96)
Jay
aunque en docs ng-min suena prometedor que no resuelve el problema
special0ne
@craigb tiene el mismo problema. Quizás sea una combinación de cosas. Yo también uso RequireJS. Básicamente, hago todas las funciones que ng-min debería hacer y aún ejecuto ng-min, luego ejecuto require build y luego ejecuto uglify con mangle true. Este proceso parece funcionar la mayoría de las veces.
escapedcat
3

Tuve un problema similar. Y lo resolvió de la siguiente manera. Necesitamos ejecutar un módulo Gulp llamado gulp-ng-annotate antes de ejecutar uglify. Entonces instalamos ese módulo

npm install gulp-ng-annotate --save-dev

Luego haga el requerimiento en Gulpfile.js

ngannotate = require(‘gulp-ng-annotate’)

Y en tu tarea usemin haz algo como esto

js: [ngannotate(), uglify(),rev()] 

Eso me resolvió.

[EDITAR: errores tipográficos corregidos]

Paulo Borralho Martins
fuente
gulp-MG-annotate debería ser gulp-NG-annotate?
hally9k
Sí, perdón por el error. Donde se lee mg-annotatesiempreng-annotate
Paulo Borralho Martins
2

Esto es muy difícil de depurar porque muchos servicios tienen el mismo nombre (principalmente e o a). Esto no resolverá el error, pero le proporcionará el nombre del servicio no resuelto que le permite rastrear, en la salida desagradable, la ubicación en el código y finalmente le permite resolver el problema:

Vaya a lib/scope.jsUglify2 ( node_modules/grunt-contrib-uglify/node_modules/uglify-js/lib/scope.js) y reemplace la línea

this.mangled_name = this.scope.next_mangled(options);

con

this.mangled_name = this.name + "__debugging_" + counter++
Bas
fuente