Amélioration de l'expérience de débogage
Au cours des derniers mois, l'équipe des outils pour les développeurs Chrome a collaboré avec l'équipe Angular pour améliorer l'expérience de débogage dans les outils pour les développeurs Chrome. Les membres des deux équipes ont travaillé ensemble et ont pris des mesures pour permettre aux développeurs de déboguer et de profiler des applications Web du point de vue de la création, en termes de langage source et de structure de projet, tout en ayant accès à des informations qui leur sont familières et pertinentes.
Cet article examine les détails techniques afin de déterminer quelles modifications ont été nécessaires dans Angular et les outils pour les développeurs Chrome pour y parvenir. Même si certaines de ces modifications sont illustrées par Angular, elles peuvent également être appliquées à d'autres frameworks. L'équipe des outils pour les développeurs Chrome encourage les autres frameworks à adopter les nouvelles API de console et les points d'extension de mappage source afin qu'ils puissent eux aussi offrir une meilleure expérience de débogage à leurs utilisateurs.
Code de l'élément à ignorer
Lors du débogage d'applications à l'aide des outils pour les développeurs Chrome, les auteurs ne veulent généralement voir que leur code, et non celui du framework situé en dessous ou une dépendance cachée dans le dossier node_modules
.
Pour ce faire, l'équipe DevTools a introduit une extension de cartes sources, appelée x_google_ignoreList
. Cette extension permet d'identifier les sources tierces telles que le code de framework ou le code généré par bundler. Lorsqu'un framework utilise cette extension, les auteurs évitent désormais automatiquement du code qu'ils ne souhaitent pas voir ni parcourir, sans avoir à configurer manuellement cela au préalable.
En pratique, les outils pour les développeurs Chrome peuvent masquer automatiquement le code identifié comme tel dans les traces de la pile, l'arborescence "Sources" et la boîte de dialogue d'ouverture rapide. Ils peuvent également améliorer le comportement d'exécution des étapes et de la reprise dans le débogueur.
Extension de carte source x_google_ignoreList
Dans les mappages sources, le nouveau champ x_google_ignoreList
fait référence au tableau sources
et répertorie les index de toutes les sources tierces connues dans ce mappage source. Lors de l'analyse de la carte source, les outils pour les développeurs Chrome s'en serviront pour identifier les sections du code qui doivent être ajoutées à la liste des éléments à ignorer.
Vous trouverez ci-dessous un mappage source pour un fichier généré out.js
. Deux sources
d'origine ont contribué à la génération du fichier de sortie: foo.js
et lib.js
. Le premier est quelque chose qu'un développeur de sites Web a écrit et le second est un cadre qu'il a utilisé.
{
"version" : 3,
"file": "out.js",
"sourceRoot": "",
"sources": ["foo.js", "lib.js"],
"sourcesContent": ["...", "..."],
"names": ["src", "maps", "are", "fun"],
"mappings": "A,AAAB;;ABCDE;"
}
Le fichier sourcesContent
est inclus pour ces deux sources d'origine, et les outils pour les développeurs Chrome affichent ces fichiers par défaut dans le débogueur:
- En tant que fichiers dans l'arborescence "Sources".
- Comme résultats dans la boîte de dialogue d'ouverture rapide.
- Les emplacements des frames d'appel mappés dans les traces de la pile d'erreurs sont mis en pause au niveau d'un point d'arrêt et lors d'étapes.
Une information supplémentaire peut désormais être incluse dans les mappages sources afin d'identifier la source de code propriétaire ou tiers:
{
...
"sources": ["foo.js", "lib.js"],
"x_google_ignoreList": [1],
...
}
Le nouveau champ x_google_ignoreList
contient un seul index faisant référence au tableau sources
: 1. Cela indique que les régions mappées à lib.js
sont en fait du code tiers qui doit être automatiquement ajouté à la liste des éléments à ignorer.
Dans un exemple plus complexe, illustré ci-dessous, les index 2, 4 et 5 indiquent que les régions mappées à lib1.ts
, lib2.coffee
et hmr.js
sont toutes du code tiers qui doit être automatiquement ajouté à la liste des éléments à ignorer.
{
...
"sources": ["foo.html", "bar.css", "lib1.ts", "baz.js", "lib2.coffee", "hmr.js"],
"x_google_ignoreList": [2, 4, 5],
...
}
Si vous êtes développeur de framework ou de bundler, assurez-vous que les mappages sources générés pendant le processus de compilation incluent ce champ afin d'intégrer ces nouvelles fonctionnalités dans les outils pour les développeurs Chrome.
x_google_ignoreList
dans Angular
Depuis Angular v14.1.0, le contenu des dossiers node_modules
et webpack
a été marqué comme à ignorer.
Pour cela, nous avons modifié angular-cli
en créant un plug-in qui se connecte au module Compiler
de webpack.
Le plug-in webpack que nos ingénieurs ont créé des hooks dans l'étape PROCESS_ASSETS_STAGE_DEV_TOOLING
et qui renseigne le champ x_google_ignoreList
dans les mappages sources pour les éléments finaux générés par Webpack et que le navigateur charge.
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)));
Traces de la pile associées
Les traces de pile répondent à la question "Comment s'est déroulée l'arrivée", mais le plus souvent, c'est du point de vue de la machine, et pas nécessairement du point de vue du développeur ou de son modèle mental de l'environnement d'exécution de l'application. Cela est particulièrement vrai lorsque certaines opérations sont planifiées pour se produire de manière asynchrone par la suite: il peut toujours être intéressant de connaître la "cause racine" ou le côté planification de ces opérations, mais cela ne fera pas partie de la trace de la pile asynchrone.
V8 dispose en interne d'un mécanisme permettant de suivre ces tâches asynchrones lorsque des primitives de planification de navigateur standards sont utilisées, comme setTimeout
. Cette opération est effectuée par défaut dans ces cas de figure. Les développeurs peuvent donc déjà les inspecter ! Ce n'est toutefois pas aussi simple dans les projets plus complexes, en particulier lorsque vous utilisez un framework doté de mécanismes de planification plus avancés (par exemple, un système qui effectue un suivi des zones, une mise en file d'attente personnalisée des tâches ou qui divise les mises à jour en plusieurs unités de travail exécutées au fil du temps).
Pour résoudre ce problème, les outils de développement proposent un mécanisme appelé "API Async Stack Tagging" au niveau de l'objet console
. Il permet aux développeurs de framework d'indiquer les emplacements où les opérations sont planifiées et celles où elles sont exécutées.
API Async Stack Tagging
Sans le taggage de pile asynchrone, les traces de pile pour le code exécuté de manière asynchrone de manière complexe par les frameworks s'affichent sans connexion au code où il a été planifié.
Avec le taggage de pile asynchrone, il est possible de fournir ce contexte. La trace de la pile se présente comme suit:
Pour ce faire, utilisez une nouvelle méthode console
nommée console.createTask()
fournie par l'API Async Stack Tagging. Sa signature est la suivante:
interface Console {
createTask(name: string): Task;
}
interface Task {
run<T>(f: () => T): T;
}
L'appel de console.createTask()
renvoie une instance Task
que vous pouvez utiliser ultérieurement pour exécuter le code asynchrone.
// Task Creation
const task = console.createTask(name);
// Task Execution
task.run(f);
Les opérations asynchrones peuvent également être imbriquées, et les "causes racines" sont affichées dans l'ordre dans la trace de la pile.
Les tâches peuvent être exécutées autant de fois que nécessaire, et la charge utile peut différer entre chaque exécution. La pile d'appel du site de planification est mémorisée jusqu'à ce que l'objet de tâche soit récupéré.
API Async Stack Tagging dans Angular
Dans Angular, des modifications ont été apportées à NgZone, le contexte d'exécution d'Angular qui persiste lors des tâches asynchrones.
Lors de la planification d'une tâche, la fonction console.createTask()
est utilisée lorsqu'elle est disponible. L'instance Task
résultante est stockée pour être utilisée par la suite. Après avoir appelé la tâche, NgZone utilisera l'instance Task
stockée pour l'exécuter.
Ces modifications ont été intégrées dans la version NgZone 0.11.8 d'Angular via les demandes d'extraction #46693 et #46958.
Cadres d'appel conviviaux
Les frameworks génèrent souvent du code à partir de toutes sortes de langages de création de modèles lors de la création d'un projet, tels que des modèles Angular ou JSX qui transforment du code HTML en JavaScript simple qui s'exécute à terme dans le navigateur. Parfois, ces types de fonctions générées reçoivent des noms peu conviviaux : soit des noms à une seule lettre après minimisation, soit des noms obscurs ou inconnus, même s'ils ne le sont pas.
Dans Angular, il n'est pas rare de voir des cadres d'appel avec des noms tels que AppComponent_Template_app_button_handleClick_1_listener
dans les traces de pile.
Pour résoudre ce problème, les outils pour les développeurs Chrome permettent désormais de renommer ces fonctions via des mappages sources. Si un mappage source comporte une entrée de nom pour le début d'un champ d'application de fonction (c'est-à-dire la parenthèse gauche de la liste des paramètres), le cadre d'appel doit afficher ce nom dans la trace de la pile.
Frames d'appel conviviaux dans Angular
Le changement de nom des frames d'appel dans Angular est un effort continu. Ces améliorations devraient se déployer progressivement au fil du temps.
Lors de l'analyse des modèles HTML rédigés par les auteurs, le compilateur Angular génère du code TypeScript, qui est finalement transpilé en code JavaScript que le navigateur charge et exécute.
Ce processus de génération de code implique également la création de mappages sources. Nous étudions actuellement des moyens d'inclure les noms de fonctions dans le champ "Noms" des mappages sources et de référencer ces noms dans les mappages entre le code généré et le code d'origine.
Par exemple, si une fonction pour un écouteur d'événements est générée et que son nom n'est pas adapté ou a été supprimé lors de la minimisation, les mappages sources peuvent désormais inclure le nom plus convivial de cette fonction dans le champ "names" et faire référence au début du champ d'application de la fonction (c'est-à-dire à la parenthèse gauche de la liste des paramètres). Les outils pour les développeurs Chrome utiliseront ensuite ces noms pour renommer les cadres d'appel dans les traces de la pile.
Perspectives d'avenir
Utiliser Angular comme pilote d'essai pour vérifier notre travail a été une expérience merveilleuse. N'hésitez pas à nous faire part de vos commentaires sur les développeurs de frameworks et à nous faire part de vos commentaires sur ces points d'extension.
Il y a d'autres domaines que nous aimerions explorer. en particulier la manière d'améliorer l'expérience de profilage dans les outils de développement.