Une note rapide sur la façon dont les Chrome DevTools ont migré du chargeur de module interne vers les modules JavaScript standard. Nous vous expliquerons comment et pourquoi la migration a été retardée, les coûts cachés de la migration et les conclusions de l'équipe DevTools une fois la migration terminée. Mais commençons par l'histoire des outils de développement Web.
introduction
Comme vous le savez probablement, Chrome DevTools est une application Web HTML, CSS et JavaScript. Au fil des ans, DevTools est devenu riche en fonctionnalités, intelligent et bien informé sur la plate-forme Web moderne. Bien que DevTools se soit développé, son architecture rappelle en grande partie l'original lorsque l'outil faisait partie de WebKit .
Nous raconterons l'histoire de DevTools, décrirons les avantages et les limites des solutions, et ce que nous avons fait pour atténuer ces limitations. Alors plongons plus profondément dans les systèmes modulaires, comment charger le code et comment nous avons finalement utilisé les modules JavaScript.
Au début il n'y avait rien
Le frontend dispose désormais de nombreux systèmes modulaires et de leurs outils, ainsi que d'un format de module JavaScript standardisé . Rien de tout cela n'était lorsque DevTools a démarré. L'outil est construit sur le code WebKit écrit il y a plus de 12 ans.
La première mention du système modulaire dans DevTools remonte à 2012: il s'agissait de l' introduction d'une liste de modules avec une liste de sources correspondante . Partie de l'infrastructure Python utilisée à l'époque pour compiler et construire DevTools. En 2013, les modules ont été extraits dans un fichier par
frontend_modules.json
ce commit , puis, en 2014, dans des fichiers séparés module.json
( ici ). Exemple module.json
:
{
"dependencies": [
"common"
],
"scripts": [
"StylePane.js",
"ElementsPanel.js"
]
}
Depuis 2014, il est
module.json
utilisé dans les outils de développement pour pointer vers des modules et des fichiers sources. Entre-temps, l'écosystème Web s'est développé rapidement et de nombreux formats de modules ont été créés: UMD, CommonJS et éventuellement des modules JavaScript standardisés. Cependant DevTools est bloqué module.json
. Le système modulaire unique et non standardisé présentait plusieurs inconvénients:
module.json
avait besoin de ses propres outils de construction.- Il n'y avait pas d'intégration IDE. Bien sûr, elle avait besoin d'outils spéciaux pour créer des fichiers qu'elle comprenait: (
jsconfig.json
pour VS Code ). - Les fonctions, classes et objets ont été placés dans la portée globale pour permettre le partage entre les modules.
- L'ordre dans lequel les fichiers étaient répertoriés était important. Il n'y avait aucune garantie que le code sur lequel vous comptez ait été téléversé autre que la vérification humaine.
En général, en évaluant l'état actuel du système modulaire DevTools et d'autres formats de modules plus largement utilisés, nous sommes arrivés à la conclusion qu'il
module.json
créait plus de problèmes qu'il n'en résolvait.
Avantages de la norme
Nous avons choisi des modules JavaScript. Lorsque cette décision a été prise, les modules dans le langage étaient toujours signalés dans Node.js et un grand nombre de packages NPM ne les supportaient pas. Quoi qu'il en soit, nous avons conclu que les modules JavaScript étaient la meilleure option.
Le principal avantage des modules est qu'ils sont un format standardisé dans la langue . Quand nous avons énuméré les inconvénients
module.json
, nous nous sommes rendu compte que presque tous étaient liés à l'utilisation d'un format de module unique et non standardisé. Choisir un format de module non standardisé signifie que nous devons investir du temps dans la construction d'intégrations à l'aide des outils de construction et des outils de nos collègues. Ces intégrations étaient souvent fragiles et manquaient de support pour les fonctionnalités, nécessitant un temps de maintenance supplémentaire et parfois des bogues délicats. Les bugs ont fini par frapper les utilisateurs.
Les modules JavaScript étant standard, cela signifiait que les IDE comme VS Code, les outils de vérification de type comme le compilateur Closure / TypeScript et les outils de création comme Rollup et les minificateurs seraient capables de comprendre le code source écrit. De plus, lorsqu'une nouvelle personne rejoint l'équipe DevTools, elle n'a pas à perdre de temps à apprendre le propriétaire
module.json
.
Bien sûr, lorsque DevTools a commencé, aucun des avantages ci-dessus n'existait. Il a fallu des années de travail dans les groupes de normalisation pour implémenter le runtime. Il a fallu du temps pour les commentaires des développeurs - utilisateurs des modules. Mais lorsque les modules sont apparus dans la langue, nous avions le choix: soit de continuer à supporter notre propre format, soit d'investir dans la transition vers le nouveau format.
Combien coûte l'éclat de la nouveauté?
Même si les modules JavaScript présentaient de nombreux avantages que nous voulions utiliser, nous sommes restés dans le monde
module.json
. Profiter des modules du langage signifiait que nous devions investir des efforts considérables dans la dette technique. En attendant, la migration pourrait interrompre les fonctions et introduire des bogues de régression.
Il ne s'agissait pas de savoir si nous utilisons des modules JavaScript. La question était de savoir à quel point la possibilité d'utiliser des modules JavaScript est coûteuse . Nous avons dû équilibrer le risque de gêner les utilisateurs avec des régressions, le temps nécessaire aux ingénieurs pour migrer et une période de détérioration de l'état du système dans lequel nous allions fonctionner.
Le dernier point s'est avéré très important. Même si nous pourrions théoriquement accéder aux modules JavaScript, lors de la migration, nous nous retrouverions avec du code qui prendrait en compte les deux types de modules . Non seulement cela est techniquement difficile, mais cela signifie également que tous les ingénieurs travaillant sur DevTools doivent savoir comment travailler dans un tel environnement. Ils devraient constamment se demander: "Qu'est-ce qui se passe dans ce code, est-ce
module.json
JS, et comment puis-je faire le changement?"
Le coût latent de la migration en termes de formation des collègues était plus élevé que prévu.Après avoir analysé les coûts, nous sommes arrivés à la conclusion qu'il vaut toujours la peine de passer aux modules dans la langue. Par conséquent, nos principaux objectifs étaient:
- Assurez-vous que les modules standard sont aussi utiles que possible.
- Assurez-vous que l'intégration avec les modules existants sur la base est
module.json
sûre et n'entraîne pas d'impacts négatifs sur l'utilisateur (erreurs de régression, frustration de l'utilisateur). - Fournir des guides de migration DevTools. Principalement grâce à des freins et contrepoids intégrés au processus pour éviter les erreurs accidentelles.
Tableur, conversions et dette technique
Le but était clair. Mais les limitations étaient
module.json
difficiles à contourner. Il a fallu plusieurs itérations, prototypes et modifications architecturales avant de proposer une solution utilisable. Nous avons fini par rédiger un document de projet avec une stratégie de migration. Ce document donne une première estimation du temps: 2-4 semaines.
La partie la plus intensive de la migration a duré 4 mois et 7 mois se sont écoulés du début à la fin!Le plan d'origine, cependant, a résisté à l'épreuve du temps: nous voulions apprendre au runtime DevTools à charger tous les fichiers à l'ancienne pour utiliser ceux répertoriés dans le tableau
scripts
module.json
, tandis que tous les fichiers répertoriés dans le tableau modules
devaient être chargés par importation dynamique de la langue . Tout fichier qui sera dans le tableau modules
peut fonctionner avec import
et export
depuis ES6.
De plus, nous voulions migrer en 2 phases. Finalement, nous avons divisé la dernière phase en 2 sous-phases: l'exportation et l'importation. Les modules et les phases ont été suivis dans une grande feuille de calcul:
Un extrait de la table de migration ici.
Phase d'exportation
La première étape a été d'ajouter des instructions d'exportation pour toutes les entités qui doivent être partagées entre les modules / fichiers. La transformation a été automatisée en exécutant un script pour chaque dossier . Disons
module.json
qu'il existe une telle entité:
Module.File1.exported = function() {
console.log('exported');
Module.File1.localFunctionInFile();
};
Module.File1.localFunctionInFile = function() {
console.log('Local');
};
Voici
Module
le nom du module. File1
- nom de fichier. Dans l'arborescence de code, il ressemble à ceci: front_end/module/file1.JS
.
Le code ci-dessus se traduit par ceci:
export function exported() {
console.log('exported');
Module.File1.localFunctionInFile();
}
export function localFunctionInFile() {
console.log('Local');
}
/** Legacy export object */
Module.File1 = {
exported,
localFunctionInFile,
};
Nous avions initialement prévu de réécrire l'importation en un seul fichier à ce stade. Par exemple, dans l'exemple ci-dessus, nous réécririons
Module.File1.localFunctionInFile
dans localFunctionInFile
. Cependant, nous avons réalisé qu'il serait plus facile d'automatiser et plus sûr de séparer les deux transformations. Ainsi, "transférer toutes les entités dans un seul fichier" deviendra la deuxième sous-phase d'importation.
Puisque l'ajout d'un mot
export
- clé transforme le fichier d'un "script" en un "module", une grande partie de l'infrastructure DevTools a dû être mise à jour en conséquence. Le framework comprenait un runtime d'importation dynamique ainsi que des outils tels que ESLint pour s'exécuter en mode module.
Un inconvénient était que nos tests étaient exécutés en mode "non strict". Les modules JavaScript impliquent que les fichiers s'exécutent en mode strict. Cela a affecté les tests. Il s'est avéré qu'un nombre non négligeable de tests reposait sur un mode non strict, y compris un test dans lequel l'opérateur était présent
with
.
Au final, la mise à jour du tout premier dossier (ajout de l'export) a pris environ une semaine et plusieurs tentatives de rechargement .
Phase d'importation
Une fois que toutes les entités ont été exportées à l'aide des instructions d'exportation, tout en restant dans la portée globale en raison de l'héritage, nous avons dû mettre à jour toutes les références d'entité, si elles sont dans plusieurs fichiers, pour utiliser les importations ES. Le but ultime est de supprimer toutes les exportations expirées en effaçant la portée mondiale. La transformation a été automatisée en exécutant un script pour chaque dossier .
Par exemple, les entités suivantes
module.json
:
Module.File1.exported();
AnotherModule.AnotherFile.alsoExported();
SameModule.AnotherFile.moduleScoped();
Converti en:
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();
Cependant, cette approche comporte des mises en garde:
- Toutes les entités n’ont pas été nommées par principe
Module.File.symbolName
. Certaines entités ont été nomméesModele.File
ou mêmeModule.CompletelyDifferentName
. La non-concordance signifiait que nous devions créer un mappage interne de l'ancien objet global vers le nouvel objet importé. -
moduleScoped
. ,Events
, ,Events
. , , ,import
Events
. - , . , . , , . , , ( DevTools). , , .
JavaScript
En février 2020, 6 mois après le début en septembre 2019, les derniers nettoyages ont été effectués dans le dossier ui /. C'est ainsi que la migration s'est terminée officieusement. Une fois la poussière retombée, nous avons officiellement marqué la migration comme terminée le 5 mars 2020 .
Désormais, les DevTools ne fonctionnent qu'avec des modules JavaScript. Nous mettons encore certaines entités dans le périmètre global (dans les fichiers hérités
module.js
) pour des tests hérités ou des intégrations avec d'autres parties des outils de l'architecte. Ils seront supprimés au fil du temps, mais nous ne les considérons pas comme un blocage du développement. Nous avons également un guide de style pour les modules JavaScript .
Statistiques
Des estimations prudentes du nombre de CL (changelist - un terme utilisé dans Gerrit, similaire à la pull request GitHub) impliquées dans cette migration est d'environ 250 CL, principalement effectuée par 2 ingénieurs . Nous n'avons pas de statistiques finales sur la taille des modifications apportées, mais une estimation prudente des lignes modifiées (la somme de la différence absolue entre les insertions et les suppressions pour chaque CL) est d'environ 30000 lignes, soit environ 20% de tout le code frontal DevTools .
Le premier fichier à exporter est pris en charge dans Chrome 79, qui a été publié dans la version stable en décembre 2019. Le dernier changement pour passer à l'importation vient dans Chrome 83, qui a été publié dans la version stable en mai 2020.
Nous avons connaissance d'une régression due à la migration vers Chrome stable. L'achèvement de l'extrait de code dans la barre de commandes a été interrompu en raison d'une exportation par défaut superflue . Il y a eu plusieurs autres régressions, mais nos scénarios de test automatisés et les utilisateurs de Chrome Canary les ont signalés. Nous avons corrigé des bogues avant qu'ils ne puissent entrer dans les versions stables de Chrome.
Vous pouvez voir toute l'histoire en vous inscrivant ici . Pas tous, mais la plupart des CL sont liés à cette erreur.
Qu'avons-nous appris?
- . , JavaScript ( ) , DevTools . , , , .
- — , . , , . , , , .
- (, ) . -. , Python Rollup.
- (~20% ), . , , . , .
- , . . , , , . , , — . , , .
, Level Up , - SkillFactory:
- Java- (18 )
- JavaScript (12 )
E
- Data Science (12 )
- - (8 )
- Machine Learning (12 )
- «Machine Learning Pro + Deep Learning» (20 )
- « Machine Learning Data Science» (20 )
- «Python -» (9 )
- DevOps (12 )
- (9 )
- UX- (9 )
- Web- (7 )