私はAngularJSアプリを作成しましたが、デバッグするのに少し悪夢があります。 Grunt + uglifyを使用して、アプリケーションコードを連結および縮小しています。また、縮小されたJSファイルと一緒にソースマップを作成します。
ファイルにJSエラーがあるが、AngularJSアプリケーションの外部にある場合、ソースマップは正しく機能しているようです。例えばいずれかのファイルの先頭にconsole.log('a.b');
を書き込むと、Chromeデバッガーに記録されたエラーは、縮小されたファイルではなく、元のファイルの行+ファイル情報を表示します。
この問題は、Angularがそれ自体を実行するコード(コントローラーコードなど)に問題がある場合に発生します。AngularからNiceスタックトレースを取得しますが、元のファイルではなく、縮小されたファイルの詳細のみを示します。
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)
私が見つけた唯一の解決策は、弾丸を噛み、ソースマップを自分で解析することです。これを行うコードを次に示します。まず、ページに 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でも問題なく機能します。欠点は、コードベースにかなり大きな依存関係を追加していることと、非常に高速な同期エラーレポートからかなり低速の非同期エラーレポートに移行することです。
Larrifaxの answer は良いですが、文書化された関数の改善されたバージョンがあります 同じ問題レポートに :
.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 注 のように、これにより2つのスタックトレースが生成されます。1つはAngularでフォーマットされ、もう1つはブラウザでフォーマットされます。 2番目のトレースはソースマップを適用します。他のAngularモジュールがあり、この後に委任を介して呼び出される可能性のある例外でも機能する可能性があるため、重複を無効にすることはおそらく良い考えではありません。
私はちょうど同じ問題を抱えていて、解決策を探していました-どうやらそれは一般的にスタックトレースのChrome問題であり、Angularエラーレポートでスタックトレースを使用します。参照:
次のプロジェクトを見てみましょう: https://github.com/novocaine/sourcemapped-stacktrace
@ jakub-hamplからの回答と本質的に同じことをしますが、役に立つかもしれません。
バグ 修正済み in Chrome(ただし、問題はAngularで解決されません))として、スタックトレースを2回出力しない回避策は次のようになります。
app.factory('$exceptionHandler', function() {
return function(exception, cause) {
console.error(exception.stack);
};
});
この問題 によると、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;
};
});
});