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 Angular, aby wprowadzić ulepszenia w zakresie debugowania w Narzędziach deweloperskich w Chrome. Pracownicy obu zespołów pracowali razem, aby umożliwić deweloperom debugowanie i profilowanie aplikacji internetowych z perspektywy tworzenia: pod względem języka źródłowego i struktury projektu oraz dostępu do znanych i istotnych informacji.

W tym poście przyjrzymy się, jakie zmiany w Angular i Chrome DevTools były niezbędne, aby można było je osiągnąć. Mimo że niektóre z tych zmian są odzwierciedlane w Angular, można je również stosować w innych środowiskach. Zespół Narzędzi deweloperskich w Chrome zachęca inne platformy do wdrażania nowych interfejsów API konsoli i punktów rozszerzeń map źródłowych, aby ułatwić użytkownikom debugowanie.

Kod do zignorowania

Podczas debugowania aplikacji przy użyciu Narzędzi deweloperskich w Chrome autorzy zwykle chcą widzieć tylko tylko swój kod, a nie platformę lub pewną zależność ukrytą 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 platformy lub kod wygenerowany przez usługę pakietową. Gdy platforma korzysta z tego rozszerzenia, autorzy automatycznie unikają kodu, którego nie chcą oglądać ani krok po kroku bez konieczności ręcznego konfigurowania tego rozszerzenia.

W praktyce Narzędzia deweloperskie w Chrome mogą automatycznie ukrywać kod zidentyfikowany jako taki w zrzucie stosu, drzewie Źródła czy w oknie Szybkie otwieranie, a także usprawniać proces przechodzenia i wznawiania działania debugera.

Animowany GIF przedstawiający Narzędzia deweloperskie przed i po. Zwróć uwagę, że na obrazie poniżej w Narzędziach deweloperskich widoczny jest kod Authored Code w drzewie, nie sugerujemy już żadnych plików platformy w menu „Szybkie otwieranie”, a po prawej stronie widoczny jest znacznie bardziej przejrzysty zrzut stosu.

Rozszerzenie mapy źródeł x_google_ignoreList

W mapach źródłowych nowe pole x_google_ignoreList odnosi się do tablicy sources i zawiera listę indeksów wszystkich znanych źródeł zewnętrznych w tej mapie źródłowej. Podczas analizowania mapy źródłowej Narzędzia deweloperskie w Chrome będą korzystać z tej informacji, aby określić, które fragmenty kodu powinny zostać zignorowane.

Poniżej znajduje się mapa źródła wygenerowanego pliku out.js. Istnieją 2 pierwotne elementy typu sources, które przyczyniły się do wygenerowania pliku wyjściowego: foo.js i lib.js. Pierwszy to coś, co napisał programista strony internetowej, a drugi – platforma, której używa.

{
  "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 pierwotnych źródłach, a Narzędzia deweloperskie w Chrome domyślnie wyświetlają te pliki w debugerze:

  • Jako pliki w drzewie Źródła.
  • W rezultacie w oknie Szybkie otwieranie.
  • Jako zmapowane lokalizacje ramek wywołania w zrzutach stosu błędów podczas wstrzymania w punkcie przerwania i podczas wykonywania kroków.

W mapach źródłowych można teraz uwzględnić dodatkową informację, która pozwala zidentyfikować źródła (własne, a które zewnętrzne):

{
  ...
  "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 on, że regiony zmapowane na lib.js są w rzeczywistości kodem innej firmy, które należy automatycznie dodać do listy ignorowanych.

W bardziej złożonym przykładzie, które widać poniżej, indeksy 2, 4 i 5 wskazują, że regiony zmapowane na lib1.ts, lib2.coffee i hmr.js to wszystkie kody firm zewnętrznych, które należy automatycznie dodać 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ś deweloperem platformy lub usługi tworzenia pakietów, upewnij się, że mapy źródeł wygenerowane podczas kompilacji zawierają to pole, aby poznać te nowe funkcje w Narzędziach deweloperskich w Chrome.

x_google_ignoreList w Angular

Od Angular w wersji 14.1.0 zawartość folderów node_modules i webpack jest oznaczona jako „do ignorowania”.

W tym celu wprowadziliśmy zmianę w usłudze angular-cli, która utworzyła wtyczkę, która inicjuje połączenie z modułem Compiler pakietu webpack.

Opracowana przez naszych inżynierów wtyczka pakietu internetowego dodaje się do etapu PROCESS_ASSETS_STAGE_DEV_TOOLING i wypełnia pole x_google_ignoreList w mapach źródeł dla końcowych zasobów wygenerowanych przez pakiet internetowy i wczytywanych przez przeglądarkę.

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

Zrzuty stosu odpowiadają na pytanie „jak to znalazłem”, ale często dzieje się to z perspektywy maszyny i niekoniecznie musi być zgodne z perspektywą dewelopera lub jego myślowym modelem środowiska wykonawczego aplikacji. Dzieje się tak szczególnie wtedy, gdy niektóre operacje mają być wykonywane asynchronicznie później: być może nadal warto poznać „główną przyczynę” lub harmonogram takich operacji, ale nie będzie to częścią asynchronicznego zrzutu stosu.

Wersja 8 posiada wewnętrznie mechanizm do śledzenia takich asynchronicznych zadań w przypadku korzystania ze standardowych podstawowych elementów planowania przeglądarki, takich jak setTimeout. W takich przypadkach dzieje się to domyślnie, więc deweloperzy mogą już to sprawdzić. Jednak w bardziej złożonych projektach nie jest to tak proste, zwłaszcza gdy korzystasz z platformy z bardziej zaawansowanymi mechanizmami planowania, takich jak śledzenie stref, niestandardowe kolejkowanie zadań lub podział aktualizacji na kilka jednostek pracy wykonywanych w czasie.

Aby rozwiązać ten problem, Narzędzia deweloperskie udostępniają mechanizm o nazwie „Async Stack Tagging API” w obiekcie console, który umożliwia deweloperom platformy wskazywanie zarówno lokalizacji, w których zaplanowane są operacje, jak i miejsc, w których są one wykonywane.

Async Stack Tagging API

Bez asynchronicznego tagowania stosowego zrzuty stosu kodu, który jest wykonywany asynchronicznie przez platformy w złożony sposób, nie są połączone z kodem, w którym zaplanowano działanie.

Zrzut stosu z wykonanym asynchronicznym kodem bez informacji o czasie jego zaplanowania. Pokazuje tylko zrzut stosu rozpoczynający się od „requestAnimationFrame”, ale nie zawiera żadnych informacji od zaplanowanego terminu.

Asynchroniczne tagowanie stosu umożliwia podanie tego kontekstu, a zrzut stosu wygląda tak:

Zrzut stosu z wykonanym asynchronicznym kodem z informacjami o czasie jego zaplanowania. Zwróć uwagę, jak w przeciwieństwie do wcześniejszych instrukcji umieszcza w zrzucie stosu parametry „businessLogic” i „schedule”.

W tym celu użyj nowej metody console o nazwie console.createTask() dostępnej w interfejsie Async Stack Tagging API. Podpis:

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

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

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

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

// Task Execution
task.run(f);

Operacje asynchroniczne mogą być zagnieżdżone, a „przyczyny główne” będą w kolejności wyświetlane w zrzucie stosu.

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 witrynie do planowania będzie zapamiętany do czasu zebrania obiektu zadania do kosza.

Async Stack Tagging API w Angular

W Angular wprowadzono zmiany w NgZone – kontekście wykonywania Angular, który występuje w zadaniach asynchronicznych.

Podczas planowania zadania używany jest element console.createTask(), jeśli jest dostępny. Powstała w ten sposób instancja Task jest przechowywana do dalszego użytku. Po wywołaniu zadania NgZone użyje zapisanej instancji Task do uruchomienia.

Te zmiany trafiły do systemu Angular NgZone 0.11.8 w ramach żądań pull #46693 i #46958.

Przyjazne ramki połączeń

Platformy często generują kod przy użyciu różnego rodzaju języków szablonów, na przykład Angular lub JSX, które zmieniają kod przypominający język HTML w zwykły kod JavaScript, który następnie działa w przeglądarce. Czasami tego rodzaju generowane funkcje mają nazwy, które nie są zbyt przyjazne – mogą to być nazwy jednoliterowe po zmniejszeniu albo nazwy mało znane lub nieznane, nawet jeśli takie nie są.

W Angular rzadko można zobaczyć w zrzucie stosu ramki wywołań o nazwach takich jak AppComponent_Template_app_button_handleClick_1_listener.

Zrzut ekranu zrzutu stosu z automatycznie wygenerowaną nazwą funkcji.

Aby rozwiązać ten problem, w Narzędziach deweloperskich w Chrome możesz teraz zmieniać nazwy tych funkcji za pomocą map źródeł. Jeśli mapa źródeł zawiera wpis nazwy dla początku zakresu funkcji (czyli lewy nawias na liście parametrów), ramka wywołania powinna wyświetlać tę nazwę w zrzucie stosu.

Przyjazne ramki połączeń w Angular

Cały czas pracujemy nad zmianą nazw ramek wywołań w Angular. Spodziewamy się, że te ulepszenia będą wprowadzane stopniowo.

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

W ramach tego procesu generowania kodu tworzone są również mapy źródeł. Analizujemy obecnie sposoby umieszczania nazw funkcji w polu „names” (nazwy) w mapach źródłowych oraz odwoływania się do tych nazw w mapowaniu między wygenerowanym kodem a kodem oryginalnym.

Jeśli na przykład funkcja detektora zdarzeń zostanie wygenerowana, a jej nazwa będzie nieprzyjazna lub usunięta podczas minifikacji, mapy źródeł mogą teraz zawierać w polu „names” prostszą nazwę, a mapowanie początku zakresu funkcji może się teraz odwoływać do tej nazwy (czyli do lewego przedrostka listy parametrów). Narzędzia deweloperskie w Chrome będą następnie używać tych nazw do zmiany nazw ramek wywołań w zrzutach stosu.

Perspektywy

Wykorzystanie Angular w wersji pilotażowej w celu weryfikacji wyników jest bardzo przydatne. Chętnie poznamy Twoją opinię deweloperów platform i przekaż nam swoją opinię na temat tych punktów rozszerzeń.

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