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 l'auteur: en termes de langue source et de structure de projet, avec un accès à des informations familières et pertinentes pour eux.
Cet article passe en revue les détails techniques pour comprendre quelles modifications ont été nécessaires dans Angular et les outils pour les développeurs Chrome. Même si certaines de ces modifications sont illustrées par Angular, elles peuvent également être appliquées à d'autres frameworks. L'équipe Chrome DevTools encourage les autres frameworks à adopter les nouvelles API de la console et les points d'extension de la carte source afin qu'ils puissent également offrir une meilleure expérience de débogage à leurs utilisateurs.
Code d'ignorement de la fiche
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 des 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 le code qu'ils ne souhaitent pas voir ou exécuter sans avoir à le configurer manuellement 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 des sources et la boîte de dialogue d'ouverture rapide, et 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 du mappage source, Chrome DevTools utilise cette information pour déterminer quelles sections du code doivent être ajoutées à la liste d'éléments à ignorer.
Vous trouverez ci-dessous un mappage source pour un fichier généré out.js
. Deux sources
d'origine ont contribué à générer le fichier de sortie: foo.js
et lib.js
. Le premier est un code écrit par un développeur de site Web, et le second est un framework 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".
- dans la boîte de dialogue "Ouvrir rapidement".
- 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 indice faisant référence au tableau sources
: 1. Cela spécifie que les régions mappées sur lib.js
sont en fait du code tiers qui doit être automatiquement ajouté à la liste d'é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 lors du processus de compilation incluent ce champ afin de pouvoir exploiter ces nouvelles fonctionnalités dans les outils de développement Chrome.
x_google_ignoreList
dans Angular
À partir de la version 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 créé par nos ingénieurs s'intègre à l'étape PROCESS_ASSETS_STAGE_DEV_TOOLING
et renseigne le champ x_google_ignoreList
dans les mappages sources pour les éléments finaux que webpack génère 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 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 plus tard: il peut toujours être intéressant de connaître la "cause racine" ou le côté planification de ces opérations, mais c'est exactement ce qui ne figurera pas dans une trace de 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 ! Toutefois, dans les projets plus complexes, ce n'est pas aussi simple, en particulier lorsque vous utilisez un framework avec des mécanismes de planification plus avancés, par exemple un framework qui effectue le suivi des zones, la mise en file d'attente de tâches personnalisées 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 du 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, vous pouvez fournir ce contexte. La trace de la pile se présente comme suit :
Pour ce faire, utilisez une nouvelle méthode console
appelé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 pourrez 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 profondes" s'affichent dans la trace de la pile de manière séquentielle.
Les tâches peuvent être exécutées un nombre illimité de fois, et la charge utile de travail peut varier à chaque exécution. La pile d'appels sur le site de planification est conservée jusqu'à ce que l'objet de tâche soit collecté.
API Async Stack Tagging dans Angular
Dans Angular, des modifications ont été apportées à NgZone, le contexte d'exécution d'Angular qui persiste entre les tâches asynchrones.
Lors de la planification d'une tâche, console.createTask()
est utilisé si disponible. L'instance Task
obtenue est stockée pour être utilisée par la suite. Lors de l'appel de la tâche, NgZone utilisera l'instance Task
stockée pour l'exécuter.
Ces modifications ont été apportées à NgZone 0.11.8 d'Angular via les requêtes pull 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 ressemblant à du code HTML en code JavaScript pur qui s'exécute finalement dans le navigateur. Parfois, ces types de fonctions générées sont associées à des noms peu pratiques : des noms à une seule lettre après avoir été minifiés, ou des noms obscurs ou inconnus, même lorsqu'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 une carte source contient 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 bloc 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 seront déployées progressivement au fil du temps.
Lors de l'analyse des modèles HTML écrits 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 fonction dans le champ "names" des maps sources et de les référencer dans les mappages entre le code généré et le code d'origine.
Par exemple, si une fonction pour un écouteur d'événement est générée et que son nom n'est pas convivial ou est supprimé lors de la minification, les mappages de source peuvent désormais inclure le nom plus convivial de cette fonction dans le champ "names", et le mappage du début du champ d'application de la fonction peut désormais faire référence à ce nom (c'est-à-dire la parenthèse gauche de la liste des paramètres). Chrome DevTools utilisera ensuite ces noms pour renommer les cadres d'appel dans les traces de pile.
Perspectives
L'utilisation d'Angular en tant que pilote de test pour vérifier notre travail a été une expérience formidable. Nous aimerions connaître l'avis des développeurs de framework et recevoir leurs commentaires sur ces points d'extension.
Il y a d'autres domaines que nous aimerions explorer. En particulier, comment améliorer l'expérience de profilage dans DevTools.