Подтвердить что ты не робот

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)
4b9b3361

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

Ответ 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;
    };
  });
});