Studium przypadku: Better Angular Debugging with DevTools

ulepszone debugowanie;

W ciągu ostatnich kilku miesięcy zespół Narzędzi deweloperskich w Chrome współpracował z zespołem Angulara nad ulepszeniami debugowania w Narzędziach deweloperskich w Chrome. Pracownicy obu zespołów współpracowali ze sobą i podejmowali działania, aby umożliwić deweloperom debugowanie i profilowanie aplikacji internetowych z perspektywy autora: w języku źródłowym i strukturze projektu, z dostępem do informacji, które są im znane i przydatne.

W tym artykule przyjrzymy się bliżej temu, jakie zmiany w Angular i Narzędziach deweloperskich Chrome były potrzebne do osiągnięcia tego celu. Chociaż niektóre z tych zmian są demonstrowane na przykładzie Angulara, można je zastosować również w innych ramach. Zespół Chrome DevTools zachęca inne frameworki do korzystania z nowych interfejsów API konsoli i punktów rozszerzenia mapy źródłowej, aby również one mogły oferować użytkownikom lepsze możliwości debugowania.

Kod ignorowania wizytówki

Podczas debugowania aplikacji za pomocą Narzędzi deweloperskich w Chrome autorzy zwykle chcą widzieć tylko swój kod, a nie frameworku lub zależności ukrytej w folderze node_modules.

Aby to osiągnąć, zespół Narzędzi deweloperskich wprowadził rozszerzenie map źródeł o nazwę x_google_ignoreList. To rozszerzenie służy do identyfikowania źródeł zewnętrznych, takich jak kod frameworku lub kod wygenerowany przez pakiet. Gdy framework używa tego rozszerzenia, autorzy mogą automatycznie unikać kodu, którego nie chcą widzieć lub którego nie chcą sprawdzać, bez konieczności ręcznej konfiguracji.

W praktyce Narzędzia deweloperskie Chrome mogą automatycznie ukrywać kod zidentyfikowany w śladach stosu, drzewie źródeł i oknie Szybkie otwieranie, a także poprawiać działanie krokowania i wznawiania w debugerze.

Animowany GIF pokazujący DevTools przed i po. Zwróć uwagę, że na obrazie po zmianach w Narzędziach deweloperskich widać w drzewie kod autorski, nie ma już sugestii dotyczących plików frameworku w menu „Otwórz szybko” i po prawej stronie jest znacznie czystszy ślad stosu.

Rozszerzenie mapy źródeł x_google_ignoreList

W mapach źródeł nowe pole x_google_ignoreList odwołuje się do tablicy sources i wypisuje indeksy wszystkich znanych źródeł zewnętrznych w tej mapie źródeł. Podczas analizowania mapy źródłowej Narzędzia deweloperskie w Chrome użyją tego parametru, aby ustalić, które sekcje kodu należy pominąć.

Poniżej znajduje się mapa źródłowa wygenerowanego pliku out.js. Do wygenerowania pliku wyjściowego przyczyniły się 2 pierwotne sources: foo.jslib.js. Pierwsza to część napisana przez dewelopera witryny, a druga to framework, którego użył.

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

sourcesContent jest uwzględniony w przypadku obu tych pierwotnych źródeł, a Narzędzia deweloperskie w Chrome wyświetlają te pliki domyślnie w Debugerze:

  • jako pliki w drzewie źródeł.
  • W oknie Szybkie otwieranie.
  • Jako mapowane lokalizacje wywołań w ramkach błędów w śladach stosu podczas wstrzymywania na punkcie przerwy i przechodzenia krok po kroku.

W mapach źródeł można teraz uwzględnić 1 dodatkowy element informacji, który pozwala określić, które z nich są kodem własnym, a które kodem zewnętrznym:

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

Nowe pole x_google_ignoreList zawiera pojedynczy indeks odwołujący się do tablicy sources: 1. Określa to, że regiony mapowane na lib.js są w istocie kodem zewnętrznym, który powinien być automatycznie dodawany do listy ignorowanych.

W bardziej złożonym przykładzie pokazanym poniżej indeksy 2, 4 i 5 wskazują, że regiony mapowane na lib1.ts, lib2.coffeehmr.js to kod innych firm, który powinien zostać automatycznie dodany do listy ignorowanych.

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

Jeśli jesteś twórcą frameworka lub pakietu, upewnij się, że mapy źródeł wygenerowane podczas procesu kompilacji zawierają to pole, aby można było korzystać z tych nowych funkcji w narzędziach programistycznych Chrome.

x_google_ignoreList w Angular

Od wersji Angular 14.1.0 zawartość folderów node_moduleswebpack została oznaczona jako „do zignorowania”.

Osiągnięto to przez zmianę w angular-cli, tworząc wtyczkę, która łączy się z modułem Compiler w webpacku.

Wtyczka webpack, którą nasi inżynierowie stworzyli na etapie PROCESS_ASSETS_STAGE_DEV_TOOLING, wypełnia pole x_google_ignoreList w mapach źródeł dla końcowych zasobów wygenerowanych przez webpack i przesyłanych do przeglądarki.

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

Połączone zrzuty stosu

Ścieżki stosu odpowiadają na pytanie „jak się tu znalazłem”, ale często jest to widok z perspektywy maszyny i niekoniecznie odpowiada on postrzeganiu dewelopera lub jego mentalnemu modelowi czasu wykonywania aplikacji. Jest to szczególnie ważne, gdy niektóre operacje są zaplanowane do wykonania asynchronicznie w późniejszym czasie: nadal może być interesujące poznanie „przyczyny źródłowej” lub strony planowania takich operacji, ale nie będzie to widoczne w asynchronicznym śladzie stosu.

V8 ma wewnętrzny mechanizm śledzenia takich zadań asynchronicznych, gdy używane są standardowe prymitywy harmonogramowania w przeglądarce, takie jak setTimeout. W takich przypadkach jest to domyślne, więc deweloperzy mogą już je sprawdzić. W przypadku bardziej złożonych projektów nie jest to jednak takie proste, zwłaszcza gdy używasz platformy z bardziej zaawansowanymi mechanizmami harmonogramowania, np. takiej, która wykonuje śledzenie stref, kolejkowanie niestandardowych zadań lub dzieli aktualizacje na kilka jednostek pracy, które są wykonywane w ciągu czasu.

Aby rozwiązać ten problem, DevTools udostępnia mechanizm o nazwie „Interfejs API do oznaczania zadań asynchronicznych” w obiekcie console, który umożliwia deweloperom frameworków wskazywanie lokalizacji, w których zaplanowano operacje, oraz lokalizacji, w których te operacje są wykonywane.

Async Stack Tagging API

Bez oznaczenia kodu asynchronicznego zrzuty kodu w przypadku kodu, który jest wykonywany asynchronicznie w skomplikowany sposób przez frameworki, nie są powiązane z kodem, w którym zostały zaplanowane.

ślad stosu dla niektórych asynchronicznie wykonywanych fragmentów kodu bez informacji o tym, kiedy zostały zaplanowane; Pokazuje tylko ślad stosu od metody requestAnimationFrame, ale nie zawiera informacji o czasie jego zaplanowania.

Dzięki asynchronicznemu tagowaniu stosu można podać ten kontekst, a zrzut stosu będzie wyglądał tak:

ślad stosu z niektórego asynchronicznie wykonanego kodu z informacjami o jego zaplanowaniu. Zwróć uwagę, że w odróżnieniu od poprzedniego śladu stosu zawiera on metody „businessLogic” i „schedule”.

Aby to zrobić, użyj nowej metody console o nazwie console.createTask(), którą udostępnia interfejs API tagowania za pomocą asynkronicznej warstwy. Jego podpis wygląda tak:

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

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

Wywołanie console.createTask() zwraca instancję Task, której można później użyć do uruchomienia kodu asynchronicznego.

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

// Task Execution
task.run(f);

Operacje asynchroniczne mogą być również zagnieżdżone, a „przyczyny źródłowe” będą wyświetlane w kolei w wyświetleniu ścieżki wywołań.

Zadania można uruchamiać dowolną liczbę razy, a ładunek roboczy może się różnić w zależności od uruchomienia. Stos wywołań w miejscu planowania będzie zapamięty, dopóki obiekt zadania nie zostanie usunięty.

Interfejs API otagowania elementów w warstwie asynchronicznej w Angular

W Angularze wprowadziliśmy zmiany w NgZone – kontekście wykonania w Angular, który jest trwały w przypadku zadań asynchronicznych.

Podczas planowania zadania używa console.createTask(), jeśli jest dostępny. Wynikowa instancja Task jest przechowywana do dalszego użycia. Po wywołaniu zadania NgZone użyje przechowywanej instancji Task do jego wykonania.

Te zmiany zostały wprowadzone w wersji 0.11.8 interfejsu NgZone w ramach pull request #46693#46958.

Ramki przyjaznych rozmów

Podczas tworzenia projektu frameworki często generują kod z różnych języków szablonów, takich jak Angular czy szablony JSX, które przekształcają kod podobny do kodu HTML w zwykły kod JavaScript, który ostatecznie działa w przeglądarce. Czasami tego typu wygenerowane funkcje mają nazwy, które nie są zbyt przyjazne – albo nazwy jednoliterowe po ich zminimalizowaniu, albo niejasne lub nieznane nazwy, nawet jeśli nie są one zminimalizowane.

W przypadku Angulara w śladach stosu często występują ramki wywołania o nazwach takich jak AppComponent_Template_app_button_handleClick_1_listener.

Zrzut ekranu ścieżki wywołania z automatycznie wygenerowaną nazwą funkcji

Aby rozwiązać ten problem, narzędzia deweloperskie w Chrome obsługują teraz zmianę nazwy tych funkcji za pomocą map źródłowych. Jeśli mapa źródłowa zawiera wpis z nazwą początku zakresu działania funkcji (czyli lewą nawiasem na liście parametrów), ramka wywołania powinna wyświetlać tę nazwę w wyświetleniu stosu.

Ramki wywołania przyjazne dla użytkownika w Angular

Zmiana nazw ramek wywołania w Angular to ciągły proces. Spodziewamy się, że te ulepszenia będą wprowadzane stopniowo.

Podczas analizowania szablonów HTML napisanych przez autorów kompilator Angular generuje kod TypeScript, który jest następnie przekształcany w kod JavaScript, który przeglądarka wczytuje i uruchamia.

W ramach tego procesu generowania kodu są też tworzone mapy źródeł. Obecnie szukamy sposobów na uwzględnianie nazw funkcji w polu „names” map źródłowych oraz na odwoływanie się do tych nazw w mapowaniu między wygenerowanym kodem a oryginalnym kodem.

Jeśli na przykład wygenerowana zostanie funkcja dla odbiornika zdarzeń, a jej nazwa zostanie usunięta lub zmieniona na nieprzyjazną podczas kompresji, mapy źródłowe mogą teraz zawierać w polu „names” bardziej przyjazną nazwę tej funkcji, a mapowanie na początku zakresu funkcji może się teraz do niej odwoływać (czyli do lewej nawiasu w liście parametrów). Narzędzia deweloperskie w Chrome będą używać tych nazw do zmiany nazw ramek wywołania w śladach stosu.

Perspektywy

Korzystanie z Angular w ramach programu pilotażowego, aby sprawdzić naszą pracę, było wspaniałym doświadczeniem. Chętnie poznamy opinie deweloperów frameworków na temat tych punktów rozszerzeń. Prześlij nam swoją opinię.

Jest jeszcze wiele obszarów, które chcielibyśmy zbadać. W szczególności chodzi o to, jak ulepszyć profilowanie w Narzędziach deweloperskich.