AngularJS - Трассировка стека, игнорирующая исходную карту
Я написал приложение AngularJS, но это доказывает, что кошмар отлаживается. Я использую Grunt + uglify для конкатенации и минимизации кода приложения. Он также создает исходную карту рядом с мини-JS файлом.
Исходная карта, похоже, работает правильно, когда в файле есть ошибка JS, но вне приложения AngularJS. например Если я пишу console.log('a.b');
в верхней части одного из файлов, ошибка, зарегистрированная в отладчике Chrome, отображает строку + информацию о файле для исходного файла, а не миниатюрный.
Проблема возникает, когда возникает проблема с кодом, который выполняется Angular (например, в коде контроллера). Я получаю красивую трассировку стека от Angular, но она только детализирует мини файл, а не оригинал.
Есть ли что-нибудь, что я могу сделать, чтобы получить Angular, чтобы подтвердить исходную карту?
Пример ошибки ниже:
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)
Ответы
Ответ 1
Единственное решение, которое я мог найти, - это укусить пулю и проанализировать исходные карты. Вот какой код, который это сделает. Сначала вам нужно добавить source-map на свою страницу. Затем добавьте этот код:
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);
};
});
Этот код загрузит источник, затем загрузит исходные карты, проанализирует их и, наконец, попытается заменить места в трассе стека отображаемыми местоположениями. Это отлично работает в Chrome, и вполне приемлемо в Firefox. Недостаток заключается в том, что вы добавляете довольно большую зависимость от вашей базы кода и что вы переходите от очень быстрой синхронной отчетности об ошибках к довольно медленным отчетам об ошибках async.
Ответ 2
Larrifax ответ хорош, но есть улучшенная версия функции, зарегистрированной в том же выпуск отчета:
.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;
});
};
});
})
Это создаст две трассировки стека, так как Andrew Magee отметил: один отформатирован Angular, а затем второй, отформатированный браузером. Вторая трассировка будет применяться к исходным картам. Не исключено, что вы отключите дубликаты, потому что у вас могут быть другие модули Angular, которые также работают с исключениями, которые могут быть вызваны после этого с помощью делегирования.
Ответ 3
У меня была одна и та же проблема, и я искал решение - видимо, это проблема Chrome со стековыми трассировками в целом и применяется к Angular, потому что в отчетах об ошибках используется трассировка стека. См:
Будет ли исходное сопоставление в Google Chrome нажать на Error.stack
Ответ 4
Я бы посмотрел на следующий проект: https://github.com/novocaine/sourcemapped-stacktrace
Это по сути то же самое, что и ответ от jakub-hampl, но может быть полезным.
Ответ 5
В соответствии с эта проблема кажется, что Angular $logProvider
прерывает сопоставление источников. Обходной путь, подобный этому, предлагается в этом выпуске:
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;
};
});
});
Ответ 6
Так как исправлена ошибка в Chrome (но проблема сохраняется в Angular), обходной путь, который не распечатывает трассировку стека дважды это будет:
app.factory('$exceptionHandler', function() {
return function(exception, cause) {
console.error(exception.stack);
};
});