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)

person Tom Seldon    schedule 17.10.2013    source источник


Ответы (6)


ответ Ларифакса хорош, но есть улучшенная версия задокументированной функции в том же отчете о проблеме:

.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;
      });
    };
  });
})

Это приведет к созданию двух трассировок стека, как отметил Эндрю Маги: одна отформатированная от Angular, затем второй, отформатированный браузером. Вторая трассировка применит исходные карты. Вероятно, не стоит отключать дубликаты, потому что у вас могут быть другие модули Angular, которые также работают с исключениями, которые могут быть вызваны после этого через делегирование.

person Marc Durdin    schedule 30.11.2015
comment
SetTimeout здесь имеет решающее значение. Я пробовал исходный хак Larrifax, но получал другие не связанные с этим ошибки (предположительно, потому что выброс ошибки прерывает angular). - person tandrewnichols; 29.07.2016
comment
Отлично! Это работает для меня, тайм-аут выводит его из-под контроля Angular, а Chrome делает все остальное. - person Angry Dan; 24.11.2016

Единственное решение, которое я смог найти, - это укусить пулю и самостоятельно проанализировать исходные карты. Вот код, который это сделает. Сначала вам нужно добавить на свою страницу карту источников. Затем добавьте этот код:

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. Недостатком является то, что вы добавляете довольно большую зависимость к своей базе кода и переходите от очень быстрого синхронного сообщения об ошибках к довольно медленному асинхронному сообщению об ошибках.

person Jakub Hampl    schedule 01.08.2014
comment
Это сработало для меня в Chrome. Я изменил _.map на $.map, чтобы он использовал jQuery вместо underscore.js. У меня уже была зависимость от jQuery, и я не хотел добавлять underscore.js. - person jcoffland; 29.12.2014
comment
У меня работает в Firefox, фантастика, трассировки стека angular давно меня беспокоят. - person jazmit; 29.10.2015
comment
Если вы не возражаете, чтобы исключение регистрировалось дважды в консоли, вы можете добавить меньший и более легкий фрагмент кода. - person Marc Durdin; 30.11.2015
comment
Чтобы минификаторы кода не разрушали ваше приложение angular, вы должны использовать синтаксис массива для определения контроллеров. stackoverflow.com/a/20266527/1087768 - person coni2k; 25.01.2016

У меня была такая же проблема, и я искал решение - по-видимому, это проблема Chrome с трассировкой стека в целом и, как правило, применима к Angular, потому что он использует трассировку стека в отчетах об ошибках. Видеть:

Будет ли исходное сопоставление в Google Chrome выдавать ошибку .stack

person jraede    schedule 09.11.2013
comment
И теперь ошибка исправлена ​​в Chrome 42 :) Cf code.google. ru / p / chromium / issues / detail? id = 357958. - person Pascal Pixel Rigaux; 10.11.2015


Согласно этой проблеме, похоже, что $logProvider Angular нарушает отображение источников. В проблеме предлагается обходной путь, подобный этому:

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;
    };
  });
});
person Larrifax    schedule 03.09.2014
comment
У меня отлично работает в angular 1.3.8 cl.ly/image/3y0m0J3w3p1J/20150110-190523 .png - person Scymex; 10.01.2015
comment
Вроде сработало для меня. Как есть, я увижу и неприятную трассировку стека (иногда несколько раз), и хорошую трассировку стека. Если я закомментирую $delegate(exception, cause), я просто получу хорошую трассировку стека. - person Andrew Magee; 07.03.2015
comment
Хотя это работает в Angular 1.3.8, см. Мой ответ ниже, чтобы получить более четкий ответ, также из той же проблемы. - person Marc Durdin; 30.11.2015

Поскольку ошибка была исправлена ​​ в Chrome (но проблема сохраняется в Angular), обходной путь, при котором трассировка стека не выводится дважды, будет следующим:

app.factory('$exceptionHandler', function() {
    return function(exception, cause) {
        console.error(exception.stack);
    };
});
person cdauth    schedule 30.10.2016