Практический пример: улучшение угловой отладки с помощью DevTools

Улучшенный опыт отладки

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

В этом посте мы заглянем под капот и увидим, какие изменения в Angular и Chrome DevTools потребовались для достижения этой цели. Несмотря на то, что некоторые из этих изменений демонстрируются в Angular, их можно применить и к другим платформам. Команда Chrome DevTools призывает другие платформы использовать новые консольные API и точки расширения карты исходного кода, чтобы они также могли предложить своим пользователям более удобные возможности отладки.

Игнорировать код листинга

При отладке приложений с помощью Chrome DevTools авторы обычно хотят видеть только свой код , а не код лежащей под ним платформы или какую-то зависимость, спрятанную в папке node_modules .

Чтобы добиться этого, команда DevTools представила расширение исходных карт под названием x_google_ignoreList . Это расширение используется для идентификации сторонних источников, таких как код платформы или код, созданный сборщиком. Когда фреймворк использует это расширение, авторы теперь автоматически избегают кода, который они не хотят видеть или выполнять, без необходимости предварительно настраивать его вручную .

На практике Chrome DevTools может автоматически скрывать код, идентифицированный как таковой в трассировках стека, дереве источников, диалоговом окне быстрого открытия, а также улучшать пошаговое поведение и возобновление работы в отладчике.

Анимированный GIF-файл, показывающий DevTools до и после. Обратите внимание, как на изображении ниже DevTools отображает авторский код в дереве, больше не предлагает никаких файлов платформы в меню «Быстрое открытие» и показывает гораздо более чистую трассировку стека справа.

Расширение исходной карты x_google_ignoreList

В картах источников новое поле x_google_ignoreList ссылается на массив sources и перечисляет индексы всех известных сторонних источников в этой карте источников. При анализе карты исходного кода Chrome DevTools будет использовать ее, чтобы определить, какие разделы кода следует исключить из списка игнорируемых.

Ниже приведена исходная карта сгенерированного файла out.js Существует два исходных sources , которые способствовали созданию выходного файла: foo.js и lib.js Первое — это то, что написал разработчик веб-сайта, а второе — это фреймворк, который они использовали.

{
  "version" : 3,
  "file": "out.js",
  "sourceRoot": "",
  "sources": ["foo.js", "lib.js"],
  "sourcesContent": ["...", "..."],
  "names": ["src", "maps", "are", "fun"],
  "mappings": "A,AAAB;;ABCDE;"
}

sourcesContent включен в оба этих исходных источника, и Chrome DevTools по умолчанию отображает эти файлы в отладчике:

  • Как файлы в дереве источников.
  • Как результат в диалоговом окне быстрого открытия.
  • В качестве сопоставленных местоположений кадров вызова в трассировке стека ошибок во время паузы в точке останова и при пошаговом выполнении.

Есть еще одна дополнительная информация, которую теперь можно включить в карты исходного кода, чтобы определить, какой из этих источников является исходным кодом или сторонним:

{
  ...
  "sources": ["foo.js", "lib.js"],
  "x_google_ignoreList": [1],
  ...
}

Новое поле x_google_ignoreList содержит единственный индекс, ссылающийся на массив sources : 1. Он указывает, что регионы, сопоставленные с lib.js на самом деле являются сторонним кодом, который должен автоматически добавляться в список игнорирования.

В более сложном примере, показанном ниже, индексы 2, 4 и 5 указывают, что регионы, сопоставленные с lib1.ts , lib2.coffee и hmr.js являются сторонним кодом, который должен быть автоматически добавлен в список игнорирования.

{
  ...
  "sources": ["foo.html", "bar.css", "lib1.ts", "baz.js", "lib2.coffee", "hmr.js"],
  "x_google_ignoreList": [2, 4, 5],
  ...
}

Если вы разработчик фреймворка или сборщика, убедитесь, что карты исходного кода, созданные в процессе сборки, включают это поле, чтобы использовать эти новые возможности в Chrome DevTools.

x_google_ignoreList в Angular

Начиная с Angular v14.1.0 , содержимое папок node_modules и webpack помечено как «игнорировать» .

Это было достигнуто за счет изменения angular-cli путем создания плагина, который подключается к модулю Compiler веб-пакета.

Плагин веб-пакета , созданный нашими инженерами, подключается к этапу PROCESS_ASSETS_STAGE_DEV_TOOLING и заполняет поле x_google_ignoreList в исходных картах для конечных ресурсов, которые генерирует веб-пакет и загружает браузер.

const map = JSON.parse(mapContent) as SourceMap;
const ignoreList = [];

for (const [index, path] of map.sources.entries()) {
  if (path.includes('/node_modules/') || path.startsWith('webpack/')) {
    ignoreList.push(index);
  }
}

map[`x_google_ignoreList`] = ignoreList;
compilation.updateAsset(name, new RawSource(JSON.stringify(map)));

Связанные трассировки стека

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

В V8 имеется внутренний механизм отслеживания таких асинхронных задач, когда используются стандартные примитивы планирования браузера, такие как setTimeout . В таких случаях это делается по умолчанию, поэтому разработчики уже могут это проверить! Но в более сложных проектах все не так просто, особенно при использовании платформы с более продвинутыми механизмами планирования — например, той, которая выполняет отслеживание зон, настраиваемую постановку задач в очередь или которая разбивает обновления на несколько выполняемых единиц работы. время.

Чтобы решить эту проблему, DevTools предоставляет в объекте console механизм под названием «Async Stack Tagged API», который позволяет разработчикам платформы указывать как места, где запланированы операции, так и места их выполнения.

API тегирования асинхронного стека

Без асинхронной маркировки стека трассировки стека для кода, который асинхронно выполняется платформами сложными способами, отображаются без связи с кодом, в котором он был запланирован.

Трассировка стека некоторого асинхронно выполняемого кода без информации о том, когда он был запланирован. Он показывает только трассировку стека, начиная с requestAnimationFrame, но не содержит никакой информации о том, когда она была запланирована.

С помощью асинхронной маркировки стека можно предоставить этот контекст, и трассировка стека выглядит следующим образом:

Трассировка стека некоторого асинхронно выполняемого кода с информацией о том, когда он был запланирован. Обратите внимание, что, в отличие от предыдущего, он включает в трассировку стека `businessLogic` и `schedule`.

Для этого используйте новый console метод с именем console.createTask() , который предоставляет API-интерфейс Async Stack Tagged. Его подпись следующая:

interface Console {
  createTask(name: string): Task;
}

interface Task {
  run<T>(f: () => T): T;
}

Вызов console.createTask() возвращает экземпляр Task , который позже можно использовать для запуска асинхронного кода.

// Task Creation
const task = console.createTask(name);

// Task Execution
task.run(f);

Асинхронные операции также могут быть вложенными, и «основные причины» будут последовательно отображаться в трассировке стека.

Задачи можно запускать любое количество раз, и полезная нагрузка может различаться при каждом запуске. Стек вызовов на сайте планирования будет запоминаться до тех пор, пока объект задачи не будет очищен от мусора.

API тегирования асинхронного стека в Angular

В Angular изменения были внесены в NgZone — контекст выполнения Angular, который сохраняется во всех асинхронных задачах.

При планировании задачи используется console.createTask() если она доступна. Полученный экземпляр Task сохраняется для дальнейшего использования. При вызове задачи NgZone будет использовать сохраненный экземпляр Task для ее запуска.

Эти изменения появились в NgZone 0.11.8 Angular через запросы на включение #46693 и #46958 .

Фреймы дружеского вызова

При создании проекта фреймворки часто генерируют код из всех видов языков шаблонов, например шаблонов Angular или JSX, которые превращают код, похожий на HTML, в простой JavaScript, который в конечном итоге запускается в браузере. Иногда таким типам сгенерированных функций присваиваются не очень дружелюбные имена — либо однобуквенные имена после их минимизации, либо какие-то неясные или незнакомые имена, даже если это не так.

В Angular нередко можно увидеть в трассировках стека фреймы вызовов с такими именами, как AppComponent_Template_app_button_handleClick_1_listener .

Снимок экрана трассировки стека с автоматически созданным именем функции.

Чтобы решить эту проблему, Chrome DevTools теперь поддерживает переименование этих функций через исходные карты. Если в исходной карте есть запись имени для начала области функции (то есть левая скобка списка параметров), кадр вызова должен отображать это имя в трассировке стека.

Фреймы дружеских вызовов в Angular

Переименование фреймов вызовов в Angular — это постоянная работа. Мы ожидаем, что эти улучшения будут происходить постепенно с течением времени.

При анализе HTML-шаблонов, написанных авторами, компилятор Angular генерирует код TypeScript, который в конечном итоге преобразуется в код JavaScript, который загружает и запускает браузер.

В рамках этого процесса генерации кода также создаются исходные карты. В настоящее время мы изучаем способы включения имен функций в поле «имена» исходных карт и ссылки на эти имена в сопоставлениях между сгенерированным кодом и исходным кодом.

Например, если сгенерирована функция для прослушивателя событий и ее имя либо недружественно, либо удалено во время минимизации, исходные карты теперь могут включать более понятное имя для этой функции в поле «имена» и сопоставление для начала функции. область теперь может ссылаться на это имя (то есть на левую скобку списка параметров). Chrome DevTools затем будет использовать эти имена для переименования кадров вызовов в трассировках стека.

Заглядывая в будущее

Использование Angular в качестве пилотного тестирования для проверки нашей работы было замечательным опытом. Мы хотели бы услышать мнение разработчиков фреймворка и предоставить нам отзывы об этих точках расширения .

Есть и другие области, которые мы хотели бы изучить. В частности, как улучшить работу с профилированием в DevTools.