Comme vous le savez peut-être, les Outils pour les développeurs Chrome sont une application Web écrite en HTML, CSS et JavaScript. Au fil des ans, les outils de développement sont devenus plus riches en fonctionnalités, plus intelligents et plus informés sur la plate-forme Web dans son ensemble. Bien que les outils de développement aient évolué au fil des ans, leur architecture ressemble en grande partie à celle d'origine lorsqu'ils faisaient encore partie de WebKit.
Cet article fait partie d'une série de posts de blog décrivant les modifications que nous apportons à l'architecture de DevTools et à sa création. Nous allons vous expliquer comment DevTools fonctionnait auparavant, quels étaient ses avantages et ses limites, et ce que nous avons fait pour atténuer ces limites. Examinons donc en détail les systèmes de modules, comment charger du code et comment nous sommes arrivés à utiliser des modules JavaScript.
Au commencement, il n'y avait rien
Bien que le paysage du frontend actuel comporte une variété de systèmes de modules avec des outils construits autour d'eux, ainsi que le format de modules JavaScript désormais standardisé, aucun de ces éléments n'existait lorsque DevTools a été créé pour la première fois. DevTools est basé sur du code initialement publié dans WebKit il y a plus de 12 ans.
La première mention d'un système de modules dans DevTools remonte à 2012: introduction d'une liste de modules avec une liste de sources associée.
Il faisait partie de l'infrastructure Python utilisée à l'époque pour compiler et créer DevTools.
Une modification ultérieure a extrait tous les modules dans un fichier frontend_modules.json
distinct (commit) en 2013, puis dans des fichiers module.json
distincts (commit) en 2014.
Exemple de fichier module.json
:
{
"dependencies": [
"common"
],
"scripts": [
"StylePane.js",
"ElementsPanel.js"
]
}
Depuis 2014, le modèle module.json
est utilisé dans DevTools pour spécifier ses modules et ses fichiers sources.
Pendant ce temps, l'écosystème Web a évolué rapidement et plusieurs formats de modules ont été créés, y compris UMD, CommonJS et les modules JavaScript finalement standardisés.
Toutefois, les outils de développement ont conservé le format module.json
.
Bien que les outils pour les développeurs continuent de fonctionner, l'utilisation d'un système de modules unique et non standardisé présente quelques inconvénients:
- Le format
module.json
nécessitait des outils de compilation personnalisés, semblables aux outils de regroupement modernes. - Il n'y avait pas d'intégration d'IDE, ce qui nécessitait des outils personnalisés pour générer des fichiers que les IDE modernes pouvaient comprendre (script d'origine pour générer des fichiers jsconfig.json pour VS Code).
- Les fonctions, les classes et les objets ont tous été placés dans le champ d'application global pour permettre le partage entre les modules.
- Les fichiers étaient dépendants de l'ordre, ce qui signifie que l'ordre dans lequel les
sources
étaient listés était important. Il n'était pas garanti que le code sur lequel vous vous appuyez soit chargé, sauf si un humain l'avait vérifié.
Dans l'ensemble, lorsque nous avons évalué l'état actuel du système de modules dans DevTools et les autres formats de modules (plus couramment utilisés), nous avons conclu que le modèle module.json
créait plus de problèmes qu'il n'en résolvait et qu'il était temps de l'abandonner.
Avantages des normes
Parmi les systèmes de modules existants, nous avons choisi les modules JavaScript comme système de migration. Au moment de cette décision, les modules JavaScript étaient toujours distribués derrière un indicateur dans Node.js, et un grand nombre de paquets disponibles sur NPM ne disposaient pas d'un lot de modules JavaScript que nous pouvions utiliser. Malgré cela, nous avons conclu que les modules JavaScript étaient la meilleure option.
L'avantage principal des modules JavaScript est qu'il s'agit du format de module standardisé pour JavaScript.
Lorsque nous avons listé les inconvénients de module.json
(voir ci-dessus), nous avons réalisé que presque tous étaient liés à l'utilisation d'un format de module unique et non standardisé.
Le choix d'un format de module non standardisé signifie que nous devons investir du temps dans la création d'intégrations avec les outils de compilation et les outils utilisés par nos mainteneurs.
Ces intégrations étaient souvent fragiles et ne prenaient pas en charge certaines fonctionnalités, ce qui nécessitait un temps de maintenance supplémentaire et entraînait parfois des bugs subtils qui étaient finalement envoyés aux utilisateurs.
Étant donné que les modules JavaScript étaient la norme, cela signifiait que les IDE tels que VS Code, les outils de vérification des types tels que Closure Compiler/TypeScript et les outils de compilation tels que Rollup/les outils de minification pouvaient comprendre le code source que nous écrivions.
De plus, lorsqu'un nouveau mainteneur rejoindra l'équipe DevTools, il n'aura pas à passer du temps à apprendre un format module.json
propriétaire, alors qu'il connaîtra (probablement) déjà les modules JavaScript.
Bien sûr, lorsque les outils pour les développeurs ont été créés pour la première fois, aucun de ces avantages n'existait. Il a fallu des années de travail dans des groupes de normalisation, des implémentations d'environnements d'exécution et des développeurs utilisant des modules JavaScript pour arriver à ce stade. Toutefois, lorsque les modules JavaScript sont devenus disponibles, nous avons dû faire un choix: continuer à gérer notre propre format ou investir dans la migration vers le nouveau.
Coût du nouveau
Même si les modules JavaScript présentaient de nombreux avantages que nous aurions aimé exploiter, nous sommes restés dans le monde non standard de module.json
.
Pour profiter des avantages des modules JavaScript, nous avons dû investir massivement dans le nettoyage de la dette technique, en effectuant une migration qui pouvait potentiellement endommager des fonctionnalités et entraîner des bugs de régression.
À ce stade, la question n'était pas de savoir si nous souhaitions utiliser des modules JavaScript, mais de savoir à quel point il est coûteux d'utiliser des modules JavaScript. Ici, nous avons dû trouver un équilibre entre le risque de perturber nos utilisateurs avec des régressions, le coût de la migration (qui prend beaucoup de temps) et l'état temporaire plus mauvais dans lequel nous allions travailler.
Ce dernier point s'est avéré très important. Même si nous pouvions en théorie accéder aux modules JavaScript, lors d'une migration, nous aurions un code qui devrait prendre en compte à la fois les modules module.json
et JavaScript.
Cela était non seulement techniquement difficile à réaliser, mais cela signifiait également que tous les ingénieurs travaillant sur DevTools devaient savoir travailler dans cet environnement.
Il devrait se demander en permanence : "Pour cette partie du codebase, s'agit-il de modules module.json
ou JavaScript, et comment apporter des modifications ?".
Aperçu: le coût caché de la migration de nos collègues a été plus important que prévu.
Après l'analyse des coûts, nous avons conclu qu'il était toujours intéressant de migrer vers les modules JavaScript. Nos principaux objectifs étaient donc les suivants:
- Assurez-vous que l'utilisation des modules JavaScript profite au maximum de leurs avantages.
- Assurez-vous que l'intégration au système existant basé sur
module.json
est sécurisée et n'a pas d'impact négatif sur l'expérience utilisateur (bugs de régression, frustration des utilisateurs). - Guider tous les responsables de la maintenance de DevTools tout au long de la migration, en particulier grâce à des contrôles intégrés pour éviter les erreurs accidentelles.
Feuilles de calcul, transformations et dette technique
Bien que l'objectif soit clair, les limites imposées par le format module.json
se sont révélées difficiles à contourner.
Il nous a fallu plusieurs itérations, prototypes et modifications architecturales avant de développer une solution qui nous convenait.
Nous avons rédigé un document de conception avec la stratégie de migration que nous avons finalement adoptée.
Le document de conception indique également notre estimation initiale du temps: deux à quatre semaines.
Attention: la partie la plus intensive de la migration a pris quatre mois, et la migration complète a duré sept mois.
Le plan initial a cependant résisté à l'épreuve du temps: nous avons appris au runtime DevTools à charger tous les fichiers listés dans le tableau scripts
du fichier module.json
à l'ancienne, tandis que tous les fichiers listés dans le tableau modules
sont chargés avec l'importation dynamique des modules JavaScript.
Tout fichier situé dans le tableau modules
peut utiliser les importations/exportations ES.
De plus, nous allons effectuer la migration en deux phases (nous allons finalement diviser la dernière phase en deux sous-phases, voir ci-dessous): les phases export
et import
.
L'état de chaque module dans chaque phase était suivi dans une grande feuille de calcul:
Un extrait de la feuille de progression est disponible publiquement sur cette page.
export
-phase
La première phase consiste à ajouter des instructions export
pour tous les symboles censés être partagés entre les modules/fichiers.
La transformation est automatisée en exécutant un script par dossier.
Étant donné que le symbole suivant existe dans le monde module.json
:
Module.File1.exported = function() {
console.log('exported');
Module.File1.localFunctionInFile();
};
Module.File1.localFunctionInFile = function() {
console.log('Local');
};
(Ici, Module
est le nom du module et File1
le nom du fichier. Dans notre sourcetree, il s'agit de front_end/module/file1.js
.)
Elle sera transformée comme suit:
export function exported() {
console.log('exported');
Module.File1.localFunctionInFile();
}
export function localFunctionInFile() {
console.log('Local');
}
/** Legacy export object */
Module.File1 = {
exported,
localFunctionInFile,
};
Initialement, nous avions prévu de réécrire les importations de même fichier au cours de cette phase.
Par exemple, dans l'exemple ci-dessus, nous remplacerions Module.File1.localFunctionInFile
par localFunctionInFile
.
Toutefois, nous avons réalisé qu'il serait plus facile d'automatiser et d'appliquer ces deux transformations si nous les séparions.
Par conséquent, la sous-phase "Migrer tous les symboles dans le même fichier" deviendra la deuxième sous-phase de la phase import
.
Étant donné que l'ajout du mot clé export
dans un fichier transforme le fichier d'un "script" en "module", une grande partie de l'infrastructure DevTools a dû être mise à jour en conséquence.
Cela incluait l'environnement d'exécution (avec importation dynamique), mais aussi des outils tels que ESLint
pour s'exécuter en mode module.
En travaillant sur ces problèmes, nous avons découvert que nos tests étaient exécutés en mode "sloppy".
Étant donné que les modules JavaScript impliquent que les fichiers s'exécutent en mode "use strict"
, cela affecterait également nos tests.
Il s'est avéré qu'un nombre non négligeable de tests reposaient sur cette négligence, y compris un test qui utilisait une instruction with
😱.
Au final, la mise à jour du tout premier dossier pour inclure des instructions export
a pris environ une semaine et plusieurs essais avec des relands.
import
-phase
Une fois que tous les symboles ont été exportés à l'aide d'instructions export
et sont restés dans le champ d'application global (ancien), nous avons dû mettre à jour toutes les références aux symboles interfichiers pour utiliser les importations ES.
L'objectif final est de supprimer tous les "anciens objets d'exportation", ce qui permet de nettoyer la portée globale.
La transformation est automatisée en exécutant un script par dossier.
Par exemple, pour les symboles suivants qui existent dans le monde module.json
:
Module.File1.exported();
AnotherModule.AnotherFile.alsoExported();
SameModule.AnotherFile.moduleScoped();
Elles seront transformées comme suit:
import * as Module from '../module/Module.js';
import * as AnotherModule from '../another_module/AnotherModule.js';
import {moduleScoped} from './AnotherFile.js';
Module.File1.exported();
AnotherModule.AnotherFile.alsoExported();
moduleScoped();
Toutefois, cette approche présente quelques inconvénients:
- Tous les symboles n'ont pas été nommés
Module.File.symbolName
. Certains symboles étaient nommés uniquementModule.File
ou mêmeModule.CompletelyDifferentName
. Cette incohérence nous a obligés à créer un mappage interne de l'ancien objet global vers le nouvel objet importé. - Il peut arriver que des conflits surviennent entre des noms de portée module.
Plus précisément, nous avons utilisé un modèle de déclaration de certains types de
Events
, où chaque symbole était simplement nomméEvents
. Cela signifie que si vous écoutiez plusieurs types d'événements déclarés dans différents fichiers, un conflit de nom se produirait dans l'instructionimport
pour cesEvents
. - Il s'est avéré qu'il y avait des dépendances circulaires entre les fichiers.
Cela n'était pas un problème dans un contexte de portée globale, car l'utilisation du symbole était effectuée après le chargement de tout le code.
Toutefois, si vous avez besoin d'un
import
, la dépendance circulaire sera rendue explicite. Ce n'est pas un problème immédiat, sauf si votre code de portée globale contient des appels de fonction avec effet secondaire, comme DevTools. Au final, il a fallu effectuer quelques opérations chirurgicales et refactoriser le code pour que la transformation soit sûre.
Un nouveau monde avec les modules JavaScript
En février 2020, six mois après le début des opérations en septembre 2019, les derniers nettoyages ont été effectués dans le dossier ui/
.
Cela a marqué la fin officieuse de la migration.
Après avoir laissé les choses se tasser, nous avons officiellement marqué la migration comme terminée le 5 mars 2020. 🎉
Désormais, tous les modules de DevTools utilisent des modules JavaScript pour partager du code.
Nous plaçons toujours certains symboles dans la portée globale (dans les fichiers module-legacy.js
) pour nos anciens tests ou pour les intégrer à d'autres parties de l'architecture DevTools.
Elles seront supprimées au fil du temps, mais nous ne les considérons pas comme un obstacle au développement futur.
Nous disposons également d'un guide de style pour notre utilisation des modules JavaScript.
Statistiques
Les estimations les plus prudentes du nombre de listes de modifications (abréviation de "changelist", terme utilisé dans Gerrit pour désigner une modification, semblable à une demande d'extraction GitHub) impliquées dans cette migration sont d'environ 250 listes de modifications, principalement effectuées par deux ingénieurs. Nous ne disposons pas de statistiques définitives sur l'ampleur des modifications apportées, mais une estimation prudente du nombre de lignes modifiées (calculée comme la somme de la différence absolue entre les insertions et les suppressions pour chaque CL) est d'environ 30 000 (environ 20% de l'ensemble du code front-end de DevTools).
Le premier fichier utilisant export
a été publié dans Chrome 79, qui est passé en version stable en décembre 2019.
La dernière modification de migration vers import
a été publiée dans Chrome 83, qui est disponible en version stable depuis mai 2020.
Nous avons connaissance d'une régression qui a été introduite dans la version stable de Chrome lors de cette migration.
La saisie semi-automatique des extraits dans le menu de commande ne fonctionne plus en raison d'une exportation default
externe.
Nous avons constaté plusieurs autres régressions, mais nos suites de tests automatisés et les utilisateurs de Chrome Canary nous en ont informés. Nous les avons corrigées avant qu'elles n'atteignent les utilisateurs de Chrome stable.
Vous pouvez consulter l'intégralité du parcours (pas toutes les CL ne sont associées à ce bug, mais la plupart le sont) sur crbug.com/1006759.
Les enseignements
- Les décisions prises par le passé peuvent avoir un impact durable sur votre projet. Même si les modules JavaScript (et d'autres formats de modules) étaient disponibles depuis un certain temps, DevTools n'était pas en mesure de justifier la migration. Décider quand migrer et quand ne pas le faire est difficile et repose sur des suppositions éclairées.
- Nos estimations initiales étaient exprimées en semaines plutôt qu'en mois. Cela est dû en grande partie au fait que nous avons rencontré plus de problèmes inattendus que prévu lors de notre analyse des coûts initiale. Même si le plan de migration était solide, la dette technique était (plus souvent que nous l'aurions souhaité) un obstacle.
- La migration des modules JavaScript a inclus un grand nombre de nettoyages de dette technique (apparemment sans rapport). La migration vers un format de module standardisé moderne nous a permis de réaligner nos bonnes pratiques de codage sur le développement Web moderne. Par exemple, nous avons pu remplacer notre bundleur Python personnalisé par une configuration de rollup minimale.
- Malgré l'impact important sur notre codebase (environ 20% du code a été modifié), très peu de régressions ont été signalées. Nous avons rencontré de nombreux problèmes lors de la migration des deux premiers fichiers, mais au bout d'un certain temps, nous avons obtenu un workflow solide, partiellement automatisé. Cela signifie que l'impact négatif sur les utilisateurs stables a été minimal pour cette migration.
- Enseigner les subtilités d'une migration particulière à d'autres mainteneurs est difficile, voire impossible. Les migrations de cette ampleur sont difficiles à suivre et nécessitent de solides connaissances du domaine. Le transfert de ces connaissances sur le domaine à d'autres personnes travaillant sur le même codebase n'est pas souhaitable en soi pour la tâche qu'elles effectuent. Savoir ce que vous devez partager et ce que vous ne devez pas partager est un art, mais un art nécessaire. Il est donc essentiel de réduire le nombre de migrations importantes, ou du moins de ne pas les effectuer en même temps.
Télécharger les canaux de prévisualisation
Envisagez d'utiliser Chrome Canary, Dev ou Bêta comme navigateur de développement par défaut. Ces canaux de prévisualisation vous donnent accès aux dernières fonctionnalités de DevTools, vous permettent de tester les API de plate-forme Web de pointe et vous aident à détecter les problèmes sur votre site avant vos utilisateurs.
Contacter l'équipe des outils pour les développeurs Chrome
Utilisez les options suivantes pour discuter des nouvelles fonctionnalités, des mises à jour ou de tout autre élément lié aux outils pour les développeurs.
- Envoyez-nous vos commentaires et vos demandes de fonctionnalités sur crbug.com.
- Signalez un problème dans les outils de développement à l'aide de l'icône Plus d'options > Aide > Signaler un problème dans les outils de développement dans les outils de développement.
- Envoyez un tweet à @ChromeDevTools.
- Laissez des commentaires sur les vidéos YouTube sur les nouveautés des outils pour les développeurs ou sur les vidéos YouTube sur les conseils concernant les outils pour les développeurs.