J'ai écrit une application AngularJS mais cela s'avère être un cauchemar pour le débogage. J'utilise Grunt + uglify pour concaténer et minimiser le code de mon application. Il crée également une carte source à côté du fichier JS minifié.
La carte source semble fonctionner correctement en cas d'erreur JS dans le fichier, mais en dehors de l'application AngularJS. par exemple. Si j'écris console.log('a.b');
en haut de l'un des fichiers, l'erreur consignée dans le débogueur de Chrome affiche les informations de ligne et de fichier correspondant au fichier d'origine, et non le fichier modifié.
Le problème se produit lorsqu'il existe un problème avec le code que Angular s'exécute lui-même (par exemple, dans le code du contrôleur). Je reçois une trace de pile Nice de Angular, mais elle ne détaille que le fichier minifié et non l’original.
Puis-je faire quelque chose pour que Angular reconnaisse la carte source?
Exemple d'erreur ci-dessous:
TypeError: Cannot call method 'getElement' of undefined
at Object.addMapControls (http://my-site/wp-content/plugins/my-maps/assets/js/app.min.js:1:2848)
at Object.g [as init] (http://my-site/wp-content/plugins/my-maps/assets/js/app.min.js:1:344)
at new a (http://my-site/wp-content/plugins/my-maps/assets/js/app.min.js:1:591)
at d (http://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.2/angular.min.js:29:495)
at Object.instantiate (http://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.2/angular.min.js:30:123)
La seule solution que j'ai pu trouver est de mordre la balle et d'analyser vous-même les cartes source. Voici un code qui fera cela. Vous devez d’abord ajouter source-map à votre page. Puis ajoutez ce code:
angular.module('Shared').factory('$exceptionHandler',
function($log, $window, $injector) {
var getSourceMappedStackTrace = function(exception) {
var $q = $injector.get('$q'),
$http = $injector.get('$http'),
SMConsumer = window.sourceMap.SourceMapConsumer,
cache = {};
// Retrieve a SourceMap object for a minified script URL
var getMapForScript = function(url) {
if (cache[url]) {
return cache[url];
} else {
var promise = $http.get(url).then(function(response) {
var m = response.data.match(/\/\/# sourceMappingURL=(.+\.map)/);
if (m) {
var path = url.match(/^(.+)\/[^/]+$/);
path = path && path[1];
return $http.get(path + '/' + m[1]).then(function(response) {
return new SMConsumer(response.data);
});
} else {
return $q.reject();
}
});
cache[url] = promise;
return promise;
}
};
if (exception.stack) { // not all browsers support stack traces
return $q.all(_.map(exception.stack.split(/\n/), function(stackLine) {
var match = stackLine.match(/^(.+)(http.+):(\d+):(\d+)/);
if (match) {
var prefix = match[1], url = match[2], line = match[3], col = match[4];
return getMapForScript(url).then(function(map) {
var pos = map.originalPositionFor({
line: parseInt(line, 10),
column: parseInt(col, 10)
});
var mangledName = prefix.match(/\s*(at)?\s*(.*?)\s*(\(|@)/);
mangledName = (mangledName && mangledName[2]) || '';
return ' at ' + (pos.name ? pos.name : mangledName) + ' ' +
$window.location.Origin + pos.source + ':' + pos.line + ':' +
pos.column;
}, function() {
return stackLine;
});
} else {
return $q.when(stackLine);
}
})).then(function (lines) {
return lines.join('\n');
});
} else {
return $q.when('');
}
};
return function(exception) {
getSourceMappedStackTrace(exception).then($log.error);
};
});
Ce code va télécharger la source, puis télécharger les cartes sources, les analyser et enfin essayer de remplacer les emplacements dans la pile, suivre les emplacements mappés. Cela fonctionne parfaitement dans Chrome et de manière tout à fait acceptable dans Firefox. L'inconvénient est que vous ajoutez une dépendance assez importante à votre base de code et que vous passez d'un rapport d'erreur synchrone très rapide à un rapport d'erreur asynchrone assez lent.
La réponse de Larrifax answer est bonne, mais une version améliorée de la fonction documentée figure dans le même rapport de problème :
.config(function($provide) {
// Fix sourcemaps
// @url https://github.com/angular/angular.js/issues/5217#issuecomment-50993513
$provide.decorator('$exceptionHandler', function($delegate) {
return function(exception, cause) {
$delegate(exception, cause);
setTimeout(function() {
throw exception;
});
};
});
})
Cela générera deux traces de pile, comme Andrew Magee remarqua : une formatée par Angular, puis une seconde par le navigateur. La deuxième trace appliquera des cartes source. Ce n'est probablement pas une bonne idée de désactiver les doublons, car vous pouvez avoir d'autres modules Angular fonctionnant également avec des exceptions pouvant ensuite être appelées via la délégation.
Je viens d’avoir le même problème et je cherche une solution. Apparemment, c’est un problème de Chrome avec les traces de pile en général et s’applique s’appliquant à Angular car il utilise des traces de pile dans les rapports d’erreur. Voir:
Le mappage de source dans Google Chrome sera-t-il envoyé à Error.stack
Je voudrais jeter un oeil sur le projet suivant: https://github.com/novocaine/sourcemapped-stacktrace
Il fait essentiellement la même chose que la réponse de @ jakub-hampl mais pourrait être utile.
Comme le bogue a été corrigé dans Chrome (mais que le problème persiste en angulaire), une solution de contournement qui n’imprime pas deux fois la trace de pile est la suivante:
app.factory('$exceptionHandler', function() {
return function(exception, cause) {
console.error(exception.stack);
};
});
Selon ce problème , il semblerait que le $logProvider
d’Anngular rompt la cartographie des sources. Une solution de contournement comme celle-ci est suggérée dans le numéro:
var module = angular.module('source-map-exception-handler', [])
module.config(function($provide) {
$provide.decorator('$exceptionHandler', function($delegate) {
return function(exception, cause) {
$delegate(exception, cause);
throw exception;
};
});
});