“Provedor desconhecido: aProvider <- a” Como encontro o provedor original?

100

Quando estou carregando a versão reduzida (por meio de UglifyJS) do meu aplicativo AngularJS, recebo o seguinte erro no console:

Unknown provider: aProvider <- a

Agora, percebo que isso se deve à mutilação do nome da variável. A versão não fragmentada funciona muito bem. No entanto, eu não quero fazer uso de desconfiguração do nome variável, uma vez que reduz drasticamente o tamanho do nosso arquivo de saída JS.

Por esse motivo, estamos usando o ngmin em nosso processo de construção, mas ele não parece resolver esse problema, embora tenha nos servido bem no passado.

Portanto, para depurar esse problema, habilitei os mapas de origem em nossa tarefa uglify grunt. Eles são gerados apenas multa e Chrome faz transferir os mapas do servidor. Ainda assim, continuo recebendo a mesma mensagem de erro inútil, embora tenha a impressão de que agora deveria ver o nome original do provedor.

Como faço para que o Chrome use os mapas de origem para me dizer qual provedor é o problema aqui ou, alternativamente, como posso descobrir o provedor de outra maneira?

Der Hochstapler
fonte
Você pode tentar adicionar comentários distintos a cada arquivo de origem JS (se ainda não for o caso) e usar a opção preserveComments de UglifyJS: isso lhe daria uma ideia de qual arquivo contém o código incorreto.
JB Nizet
Você usa decoradores? Descobri que o ngmin não parece reescrever decoradores adequadamente quando o usei no passado, o que resulta em erros como os seus.
dherman de
@JBNizet: Eu gosto da ideia, mas adicionar essa diretiva às opções não parece ter nenhum efeito.
Der Hochstapler
@dherman: Você poderia me dar um exemplo de decoradores? Não tenho certeza do que seriam neste contexto.
Der Hochstapler
Consulte github.com/gruntjs/grunt-contrib-uglify (se você usar o grunt). O valor da opção deve ser "all".
JB Nizet

Respostas:

193

Ainda adoraria saber como poderia ter encontrado o local em nosso código-fonte que causou esse problema, mas desde então consegui encontrar o problema manualmente.

Havia uma função de controlador declarada no escopo global, em vez de usar uma .controller()chamada no módulo de aplicativo.

Então, havia algo assim:

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

Isso funciona bem para o AngularJS, mas para que funcione bem com mutilação, eu tive que alterá-lo para:

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

Depois de mais testes, eu realmente encontrei instâncias de mais controladores que também causaram problemas. Foi assim que encontrei a fonte de todos eles manualmente :

Em primeiro lugar, considero bastante importante habilitar o embelezamento da saída nas opções do uglify. Para nossa tarefa árdua, isso significava:

options : {
    beautify : true,
    mangle   : true
}

Em seguida, abri o site do projeto no Chrome, com as DevTools abertas. O que resulta em um erro como o seguinte sendo registrado:

insira a descrição da imagem aqui

O método no rastreamento de chamada em que estamos interessados ​​é aquele que marquei com uma seta. Isso está providerInjectordentroinjector.js . Você vai querer colocar um ponto de interrupção onde ele lança uma exceção:

insira a descrição da imagem aqui

Quando você executar novamente o aplicativo, o ponto de interrupção será atingido e você poderá saltar para cima na pilha de chamadas. Haverá uma chamada de invokeeminjector.js , reconhecível na string "Token de injeção incorreto":

insira a descrição da imagem aqui

O localsparâmetro (mutilado dem meu código) dá uma boa ideia sobre qual objeto em sua fonte está o problema:

insira a descrição da imagem aqui

Uma rápida olhada grepem nossa fonte encontra muitas instâncias de modalInstance, mas, a partir daí, foi fácil encontrar este ponto na fonte:

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

Que deve ser alterado para:

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

Caso a variável não contenha informações úteis, você também pode pular para cima na pilha e deve acertar uma chamada para a invokequal deve ter dicas adicionais:

insira a descrição da imagem aqui

Evite que isso aconteça novamente

Agora que espero que você tenha encontrado o problema, sinto que devo mencionar a melhor forma de evitar que isso aconteça novamente no futuro.

Obviamente, você poderia apenas usar a anotação de array embutido em qualquer lugar ou (dependendo de sua preferência) $injectanotação de propriedade e simplesmente tentar não se esquecer disso no futuro. Se você fizer isso, certifique-se de habilitar o modo de injeção de dependência estrita para detectar erros como este antecipadamente.

Cuidado! Caso você esteja usando o Angular Batarang, o StrictDI pode não funcionar para você, pois o Angular Batarang injeta código não anotado no seu (Batarang ruim!).

Ou você pode deixar o ng-annotate cuidar disso. Eu recomendo fortemente fazer isso, pois remove muito potencial de erros nesta área, como:

  • Anotação DI ausente
  • Anotação DI incompleta
  • Anotação DI na ordem errada

Manter as anotações atualizadas é simplesmente um pé no saco e você não deveria ter que fazer isso se puder ser feito automaticamente. ng-annotate faz exatamente isso.

Ele deve se integrar perfeitamente ao seu processo de construção com grunt-ng-annotate e gulp-ng-annotate .

Der Hochstapler
fonte
12
Este é um artigo fantástico, escrito com cuidado. Acabei de encontrar esse problema, parece ser um problema profundo em algum lugar. Suas dicas me ajudaram a saber onde procurar. No final, apenas "agrupei" todos os meus parâmetros angulares e o problema foi embora. Todas as compilações anteriores foram minimizadas e nada mudou muito. Eu não adicionei nenhuma função global - ele apenas parou de funcionar, misteriosamente, mutilando algum controlador / diretiva / serviço / filtro?
zenocon
Esta foi uma grande fonte de ajuda. Não sabia que você tinha que usar a sintaxe de array (inline) também para outras funções, como resolução de roteador, .run, .config, etc.
VDest
4
No meu caso, foi controlador em diretiva. Se na variável 'd' você verá $ attr, provavelmente é o mesmo problema. Você deve envolver os parâmetros em colchetes de array para o controlador de diretiva interna. controlador: ["$ escopo", função ($ escopo) {...}] em vez de controlador: função ($ escopo) {...}
alex naumov
Muito obrigado por seu artigo e solução usando injeção de dependência segura / notação de matriz para a referência de função var. Eu também tive esse erro e por causa da sua solução pude seguir em frente. você é demais!
Frankie Loscavio
1
Sempre que tenho esse problema, leio isso novamente e quero votar a favor novamente. A propósito, aqui está como configurar a versão gulpuglify({ output : { beautify : true }})
Eugene Gluhotorenko
30

O artigo de Oliver Salzburg foi fantástico. Votado.

Dica para quem pode ter esse erro. O meu foi simplesmente causado por esquecer de passar uma matriz para um controlador de diretiva:

RUIM

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

BOA

return {
    restrict: "E",
    scope: {                
    },
    controller: ["$scope", ExampleDirectiveController],
    templateUrl: "template/url/here.html"
};
Ash Clarke
fonte
2
Este foi tão atrevido ... Uglify não estava causando isso para mim até uma atualização recente!
SamMorrowDrums
Meu problema era o mesmo, mas o que eu precisava adicionar era /* @ngInject */antes da função. Parece fazer a parte complicada da injeção sem precisar digitar cada módulo incluído (estou usando Yeoman)
Nicholas Blasgen
25

usar ng-strict-di com ng-app

Se você estiver usando o Angular 1.3, você pode evitar muitos danos usando a diretiva ngStrictDi com ngApp:

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

Agora - pré-minificação - qualquer coisa que não use anotações explodirá seu console e você poderá ver o maldito nome sem procurar por rastros de pilha mutilados.

De acordo com os documentos:

o aplicativo falhará ao invocar funções que não usam anotação de função explícita (e, portanto, são inadequadas para minificação)

Uma ressalva , ele só detecta que não são anotações, não que as anotações estão completos.

Significado:

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

Não vai perceber que ThingB não faz parte da anotação.

O crédito por esta dica vai para o pessoal do ng-annotate , que é recomendado em vez do agora obsoleto ngMin.

Mark Fox
fonte
Isso precisa de mais votos positivos. Isso é ótimo para depurar um aplicativo que nunca usou ngInject ou a sintaxe de array de string.
Michael Pearson
11

Para minimizar o angular, tudo o que você precisa fazer é alterar sua declaração para o modo "declaração de array", por exemplo:

De:

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

Para

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

Como declarar serviços de fábrica?

demoApp.factory('demoFactory', ['$q', '$http', function ($q, $http) {
    return {
          //some object
    };
}]);
Dalorzo
fonte
Eu sei. É por isso que usamos o ngmin. Suspeito que haja um problema com alguma parte de nossa fonte ou suas dependências. É por isso que estou tentando chegar à raiz deste problema.
Der Hochstapler
1
Minha recomendação é que você crie seu código dessa forma. Então você pode usar qualquer
minificador
3
Eu estou criando nosso código desta forma. Mas temos dependências externas que não o fazem. O ngmin já resolveu bem esse problema para nós no passado. Presumo que uma mudança recente tenha criado esse problema. Agora, gostaria de encontrar a origem deste problema para que possa corrigi-lo adequadamente em nosso código, nossa dependência ou possivelmente no próprio ngmin.
Der Hochstapler
Como o problema parece muito específico para um determinado componente ou código, é difícil fornecer orientação, pelo menos do meu lado
Dalorzo
O ngmin não requer que você use o modo de declaração de array, ele adiciona muitas declarações inúteis.
Nanocom de
8

Eu simplesmente tive o mesmo problema e resolvi-o simplesmente substituindo ngmin (agora obsoleto) por ng-annotate para minha tarefa de compilação grunt.

Parece que yeoman angular também foi atualizado para usar ng-annotate a partir deste commit: https://github.com/yeoman/generator-angular/commit/3eea4cbeb010eeaaf797c17604b4a3ab5371eccb

No entanto, se você estiver usando uma versão mais antiga do yeoman angular como eu, apenas substitua ng-min por ng-annotate em seu package.json:

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

execute npm install(opcionalmente npm prune) e siga as mudanças no commit para editar Gruntfile.js.

Xuwen
fonte
7

para saber qual era o nome da variável original, você pode alterar como o uglify altera as variáveis:

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

e agora o erro é muito mais óbvio

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

Tão óbvio agora ...

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

agora cada variável é mutilada para um valor único que também contém o original ... basta abrir o javascript minificado e pesquisar por "a_orig_ $ stateProvider_91212" ou qualquer outro ... você o verá em seu contexto original ...

não poderia ser mais fácil ...

user3338098
fonte
4

Também não se esqueça da resolvepropriedade da rota. Ele também deve ser definido como a matriz:

$routeProvider.when('/foo', {
    resolve: {
        bar: ['myService1', function(myService1) {
            return myService1.getThis();
        }],
        baz: ['myService2', function(myService2) {
            return myService2.getThat();
        }]
    }
});
Petr Felzmann
fonte
Isso aconteceu comigo quando adicionei um monte de resoluções às minhas rotas. Você potencialmente me salvou horas de depuração dolorosa, obrigado.
Paul McClean,
3

Com gerador-gulp-angular:

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

}

Escreva / ** @ngInject * / antes de cada controlador, serviço, diretiva.

Maxim Danilov
fonte
2

Uma solução rápida e suja para isso se você não precisar que o Uglify mangle / encurte seus nomes de variáveis ​​é definir mangle = false em seu Gruntfile

    uglify: {
        compile: {
            options: {
                mangle   : false,
                ...
            },
        }
    }
Parris Varney
fonte
Isso pode resolver o problema, mas o tamanho da compilação resultante será maior, pois o mangle está desativado.
NotABot de
ainda menor do que nada feio
mjwrazor