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 poście szczegółowo opisujemy, jakie zmiany w Angular i Narzędziach deweloperskich w Chrome były niezbędne, aby osiągnąć ten efekt. Chociaż niektóre z tych zmian pokazujemy w Angular, można je również zastosować na innych platformach. 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 listy

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.

W tym celu zespół Narzędzi deweloperskich wprowadził rozszerzenie map źródłowych o nazwie x_google_ignoreList. To rozszerzenie służy do identyfikowania źródeł zewnętrznych, takich jak kod frameworku lub kod wygenerowany przez pakiet. Gdy platforma korzysta z tego rozszerzenia, autorzy automatycznie unikają kodu, który nie chce zobaczyć ani przejść przez niego bez konieczności ręcznego konfigurowania tego rozszerzenia.

W praktyce Narzędzia deweloperskie w Chrome mogą automatycznie ukrywać kod zidentyfikowany na przykład w zrzutach stosu, w drzewie Źródła i oknie Szybkie otwieranie, a także usprawniają działanie kroków i wznawiania debugera.

Animowany GIF pokazujący DevTools przed i po. Zwróć uwagę, że na obrazie „Po” 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ódłowej 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 znajdziesz mapę źródeł dla wygenerowanego pliku out.js. Istnieją 2 pierwotne elementy sources, które miały udział w wygenerowaniu pliku wyjściowego: foo.js i lib.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;"
}

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

  • jako pliki w drzewie źródeł.
  • W oknie Szybkie otwieranie.
  • Jako zmapowane lokalizacje ramek wywołania w zrzutach stosu błędów podczas wstrzymania na punkcie przerwania i podczas przechodzenia.

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. Oznacza to, że regiony zmapowane na lib.js są w rzeczywistości kodem innej firmy, który powinien zostać automatycznie dodany do listy ignorowanych.

W bardziej skomplikowanym przykładzie pokazanym poniżej indeksy 2, 4 i 5 wskazują, że regiony mapowane na lib1.ts, lib2.coffeehmr.js to kody innych firm, które powinny zostać automatycznie dodane 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 narzędzia do tworzenia pakietów, 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ędziu Chrome DevTools.

x_google_ignoreList w Angular

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

Jest to możliwe dzięki zmianie w angular-cli polegającej na utworzeniu wtyczki, która łączy się z modułem Compiler pakietu internetowego.

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 przesł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 perspektywie dewelopera lub jego mentalnemu modelowi czasu wykonywania aplikacji. Jest to szczególnie istotne, gdy niektóre operacje zostaną zaplanowane asynchronicznie później: nadal interesujące może być poznanie „głównej przyczyny” lub planowania takich operacji, ale nie jest to częścią asynchronicznego zrzutu 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 czasie.

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:

Zrzut stosu pewnego wykonanego kodu asynchronicznego z informacjami o tym, kiedy został zaplanowany. Zwróć uwagę, że w przeciwieństwie do poprzedniego zrzutu stosu zawiera on elementy „businessLogic” i „schedule” (harmonogram).

Aby to zrobić, użyj nowej metody console o nazwie console.createTask(), którą udostępnia interfejs Async Stack Tagging API. 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 kolejności 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 do tagowania elementów w aplikacji 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. Powstała w ten sposób instancja Task jest przechowywana do dalszego użycia. Po wywołaniu zadania NgZone użyje do jego uruchomienia zapisanej instancji Task.

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 platformy często generują kod z różnych języków szablonów – na przykład szablonów Angular lub JSX, które zmieniają wygląd kodu HTML w zwykły kod JavaScript, który z czasem uruchamia się 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ą nawias w 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łań w Angular jest ciągłym zadaniem. 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 tworzone są też 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 minimalizacji, mapy źródłowe mogą teraz zawierać w polu „names” bardziej przyjazną nazwę tej funkcji, a mapowanie na początku zakresu funkcji może teraz odwoływać się do tej nazwy (czyli lewej nawiasu listy 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 platformy i przekażemy opinię na temat tych punktów rozszerzeń.

Jest więcej obszarów, które chcemy zbadać. W szczególności chodzi o to, jak ulepszyć profilowanie w Narzędziach deweloperskich.