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 plus large. Bien que les outils de développement se soient développés au fil des années, leur architecture ressemble en grande partie à celle d'origine lorsqu'ils faisaient encore partie de WebKit.
Cet article fait partie d'une série d'articles de blog décrivant les modifications que nous apportons à l'architecture des outils de développement et leur conception. Nous allons 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 divers systèmes de modules avec des outils développés 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 restaient au 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, lors de l'évaluation de l'état actuel du système de modules dans les outils de développement et des autres formats de module (les plus utilisés), nous avons conclu que le modèle module.json
créait plus de problèmes qu'il ne résolvait, et qu'il était temps de planifier notre abandon.
Avantages des normes
Parmi les systèmes de modules existants, nous avons choisi les modules JavaScript pour la 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 bundle 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.
Comme les modules JavaScript étaient la norme, cela signifiait que les IDE comme VS Code, les vérificateurs de type comme Closure Compiler/TypeScript et les outils de compilation tels que Rollup/Minifiers pouvaient comprendre le code source que nous avions écrit.
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 les groupes de normalisation, les implémentations d'environnements d'exécution et les 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 nouvel appareil
Même si les modules JavaScript présentent de nombreux avantages que nous aimerions 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, il ne s'agissait pas de se demander si l'utilisation de modules JavaScript était nécessaire, mais plutôt de se demander à quel point l'utilisation de modules JavaScript est-elle coûteuse ?. 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). - Guidez tous les responsables des outils de développement tout au long de la migration, principalement en intégrant des contrôles et des contrepoids 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 qui se trouverait dans le tableau modules
pourrait 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
.
Le statut du module et de la phase suivie 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 grâce à l'exécution d'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,
};
Au départ, nous avions prévu de réécrire les importations sur un même fichier au cours de cette phase également.
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 les instructions export
a pris environ une semaine et plusieurs tentatives avec 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 des 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 important encore, nous avons utilisé un modèle de déclaration de certains types de
Events
, où chaque symbole n'était nommé queEvents
. 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 ne convenait pas dans un contexte de champ d'application global, car le symbole était utilisé 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 non officielle de la migration.
Après avoir laissé la poussière s'installer, nous avons officiellement marqué la migration comme terminée le 5 mars 2020. 🎉
Désormais, tous les modules des outils de développement utilisent des modules JavaScript pour partager du code.
Nous ajoutons encore des symboles au champ d'application global (dans les fichiers module-legacy.js
) pour nos anciens tests ou pour les intégrer à d'autres parties de l'architecture des outils de développement.
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. Toutefois, une estimation prudente du nombre de lignes modifiées (calculée en additionnant 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 de l'interface des outils de développement).
Le premier fichier utilisant export
a été publié dans Chrome 79, qui est sorti 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. Nous nous appuyons sur des suppositions fondées sur des suppositions pour déterminer quand et quand migrer.
- 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 ne l'aurions dû) le blocage.
- 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 mis en place 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. Transmettre ces connaissances du domaine à d'autres personnes travaillant dans le même codebase n'est pas en soi souhaitable pour leur travail. 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
Vous pouvez utiliser la version Canary, Dev ou Bêta de Chrome 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 plates-formes 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.