Actualisation de l'architecture des outils de développement: migration des outils de développement vers TypeScript

Tim van der Lippe
Tim van der Lippe

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.

Suite à la migration vers les modules JavaScript et à la migration vers les composants Web, nous poursuivons aujourd'hui notre série d'articles de blog sur les modifications que nous apportons à l'architecture des outils de développement et à leur conception. (Si vous ne l'avez pas déjà vue, nous avons publié une vidéo sur notre travail de mise à niveau de l'architecture des outils de développement vers le Web moderne, avec 14 conseils pour améliorer vos projets Web.)

Dans cet article, nous décrirons notre parcours en 13 mois avant l'abandon du vérificateur de type de compilateur Closure à TypeScript.

Introduction

Compte tenu de la taille du codebase DevTools et de la nécessité de pouvoir s'assurer que les ingénieurs qui y travaillent, l'utilisation d'un vérificateur de type est nécessaire. C'est pourquoi DevTools a adopté le compilateur Closure Compiler en 2013. L'adoption de Closure a permis aux ingénieurs de développement d'apporter des modifications en toute confiance. Le compilateur Closure effectuait des vérifications de type pour s'assurer que toutes les intégrations système étaient correctement typées.

Cependant, au fil du temps, les vérificateurs de type alternatifs sont devenus populaires dans le développement Web moderne. TypeScript et Flow en sont deux exemples notables. De plus, TypeScript est devenu un langage de programmation officiel chez Google. Malgré la popularité grandissante de ces nouveaux vérificateurs de type, nous avons également remarqué que nous étions en train de créer des régressions de livraison qui auraient dû être détectées par un vérificateur de type. Nous avons donc décidé de réévaluer notre choix de vérificateur de type et de définir les prochaines étapes de développement sur les outils de développement.

Évaluer les vérificateurs de type

Étant donné que les outils de développement utilisaient déjà un vérificateur de type, la question à laquelle nous devons répondre était la suivante:

Devons-nous continuer à utiliser Closure Compiler ou migrer vers un nouveau vérificateur de type ?

Pour répondre à cette question, nous avons dû évaluer les vérificateurs de type sur plusieurs caractéristiques. Étant donné que notre utilisation d'un vérificateur de type est axée sur la confiance des ingénieurs, l'aspect le plus important pour nous est l'exactitude du type. En d'autres termes: Dans quelle mesure un vérificateur de type est-il fiable pour détecter des problèmes réels ?

Notre évaluation s'est concentrée sur les régressions que nous avions envoyées et a permis d'en déterminer la cause. L'hypothèse est que, puisque nous utilisions déjà Closure Compiler, Closure n'aurait pas détecté ces problèmes. Il faudrait donc déterminer si un autre vérificateur de type aurait pu le faire.

Exactitude du typage dans TypeScript

TypeScript étant un langage de programmation officiellement pris en charge par Google et dont la popularité a rapidement grandi, nous avons décidé de commencer par évaluer TypeScript. TypeScript s'est révélé être un choix intéressant, car l'équipe TypeScript elle-même utilise les outils de développement comme l'un de ses projets de test afin de suivre leur compatibilité avec la vérification du type JavaScript. La résultat du test de référence de référence a montré que TypeScript capturait une grande quantité de problèmes de type, des problèmes que le compilateur Closure ne détectait pas nécessairement. Bon nombre de ces problèmes étaient probablement la cause première des régressions que nous avions envoyées. Cela nous a ensuite fait croire que TypeScript pouvait être une option viable pour les outils de développement.

Lors de notre migration vers les modules JavaScript, nous avions déjà découvert que Closure Compiler avait détecté plus de problèmes qu'auparavant. Le passage à un format de module standard a permis à Closure de mieux comprendre notre codebase et ainsi d'accroître l'efficacité des vérificateurs de type. Cependant, l'équipe TypeScript utilisait une version de référence des outils de développement qui était antérieure à la migration des modules JavaScript. Nous devions donc déterminer si la migration vers les modules JavaScript avait également réduit le nombre d'erreurs que le compilateur TypeScript détecterait.

Évaluer TypeScript

Les outils de développement existent depuis plus de dix ans, au cours desquels ils s'est développé pour devenir une application Web de taille considérable et riche en fonctionnalités. Au moment de la rédaction de cet article, les outils de développement contiennent environ 150 000 lignes de code JavaScript propriétaire. Lorsque nous avons exécuté le compilateur TypeScript sur notre code source, le volume considérable d'erreurs était écrasant. Nous avons découvert que même si le compilateur TypeScript émettait moins d'erreurs liées à la résolution du code (environ 2 000 erreurs), il y avait encore 6 000 erreurs supplémentaires dans notre codebase concernant la compatibilité des types.

Cela a montré que, bien que TypeScript comprenne comment résoudre les types, il a détecté un grand nombre d'incompatibilités de types dans notre codebase. Une analyse manuelle de ces erreurs a montré que TypeScript était (la plupart du temps) correct. TypeScript a pu les détecter, contrairement à Closure, car le compilateur Closure déduisait souvent un type comme étant un Any, alors que TypeScript effectuait une inférence de type basée sur les attributions et en déduit un type plus précis. Ainsi, TypeScript était en effet plus efficace pour comprendre la structure de nos objets et a découvert des utilisations problématiques.

Remarque importante : l'utilisation du compilateur Closure dans les outils de développement inclut l'utilisation fréquente de @unrestricted. L'annotation d'une classe avec @unrestricted désactive effectivement les vérifications strictes de propriété du compilateur Closure pour cette classe spécifique. Cela signifie qu'un développeur peut enrichir une définition de classe à sa guise, sans sûreté du typage. Nous n'avons trouvé aucun contexte historique expliquant pourquoi l'utilisation de @unrestricted était répandue dans le codebase des outils de développement, mais cela avait entraîné l'exécution du compilateur Closure dans un mode de fonctionnement moins sûr sur de grandes parties du codebase.

Une analyse croisée de nos régressions avec les erreurs de type découvertes par TypeScript a également montré un chevauchement, ce qui nous a conduit à croire que TypeScript aurait pu éviter ces problèmes (à condition que les types eux-mêmes étaient corrects).

Passage d'un appel any...

À ce stade, nous avons dû décider entre améliorer notre utilisation de Closure Compiler ou migrer vers TypeScript. Comme Flow n'était pas compatible avec Google ni dans Chromium, nous avons dû renoncer à cette option. Suite aux discussions avec les ingénieurs Google travaillant sur les outils JavaScript/TypeScript et aux recommandations de ces derniers, nous avons choisi de choisir le compilateur TypeScript. (Nous avons également récemment publié un article de blog sur la migration de Puppeteer vers TypeScript.)

Les principales raisons du compilateur TypeScript étaient l'amélioration de l'exactitude du type, tandis que d'autres avantages comprenaient la prise en charge des équipes TypeScript en interne chez Google et les fonctionnalités du langage TypeScript, telles que interfaces (par opposition à typedefs dans JSDoc).

En choisissant le compilateur TypeScript, nous avons dû investir massivement dans le codebase des outils de développement et son architecture interne. Ainsi, nous avons estimé qu'il nous fallait au moins un an pour migrer vers TypeScript (prévu pour le troisième trimestre 2020).

Procéder à la migration

La principale question qui reste restée: comment migrerons-nous vers TypeScript ? Nous avons 150 000 lignes de code et nous ne pouvons pas les migrer en une seule fois. Nous savions également que l'exécution de TypeScript sur notre codebase nous permettrait de détecter des milliers d'erreurs.

Nous avons évalué plusieurs options:

  1. Obtenez toutes les erreurs TypeScript et comparez-les à une sortie "golden". Cette approche serait similaire à celle dont dispose l'équipe TypeScript. Le principal inconvénient de cette approche est la fréquence à laquelle les conflits de fusion se produisent, car des dizaines d'ingénieurs travaillent dans le même codebase.
  2. Définissez tous les types problématiques sur any. Cela obligerait TypeScript à supprimer les erreurs. Nous n'avons pas choisi cette option, car l'objectif de cette migration était d'utiliser l'exactitude du type de données, ce qui compromettait la suppression.
  3. Corrigez toutes les erreurs TypeScript manuellement. Cela impliquerait de corriger des milliers d'erreurs, ce qui prend du temps.

Malgré les efforts considérables attendus, nous avons choisi l'option 3. Nous avons choisi cette option pour d'autres raisons: par exemple, elle nous a permis d'auditer tout le code et d'examiner chaque décennie toutes les fonctionnalités, y compris leur implémentation. D'un point de vue commercial, nous n'apportions pas de valeur ajoutée, mais plutôt le statu quo. Il était donc plus difficile de justifier l'option 3 comme étant la bonne réponse.

Cependant, en adoptant TypeScript, nous étions convaincus que nous pourrions prévenir de futurs problèmes, en particulier concernant les régressions. L'argument était donc moins "nous ajoutons de la valeur pour l'entreprise", mais plus "nous veillons à ne pas perdre de valeur commerciale".

Prise en charge de JavaScript par le compilateur TypeScript

Après avoir obtenu l'adhésion et développé un plan visant à exécuter à la fois les compilateurs Closure et TypeScript sur le même code JavaScript, nous avons commencé par quelques petits fichiers. Notre approche était principalement ascendante: commençons par le code principal, puis progresse vers le haut de l'architecture jusqu'à atteindre les panneaux de haut niveau.

Nous avons pu exécuter notre travail en parallèle en ajoutant préventivement @ts-nocheck à chaque fichier dans les outils de développement. Le processus de "correction de TypeScript" consisterait à supprimer l'annotation @ts-nocheck et à résoudre toutes les erreurs que TypeScript trouverait. Ainsi, nous étions sûrs que chaque fichier avait été vérifié et que le plus grand nombre possible de problèmes avaient été résolus.

En général, cette approche a fonctionné avec peu de problèmes. Nous avons rencontré plusieurs bugs dans le compilateur TypeScript, mais la plupart d'entre eux étaient obscurs:

  1. Un paramètre facultatif dont le type de fonction renvoie any est considéré comme obligatoire: #38551
  2. Une attribution de propriété à une méthode statique d'une classe rompt la déclaration: #38553
  3. La déclaration d'une sous-classe avec un constructeur sans argument et une super-classe avec un constructeur d'arguments omet le constructeur enfant: #41397

Ces bugs montrent que, dans les cas de 99 %, le compilateur TypeScript constitue une base solide sur laquelle s'appuyer. Oui, ces bugs obscurs pouvaient parfois poser problème dans les outils de développement, mais la plupart du temps, ils étaient suffisamment obscurs pour que nous puissions facilement les contourner.

Le seul problème qui a pu semer la confusion était la sortie non déterministe des fichiers .tsbuildinfo: #37156. Dans Chromium, nous exigeons que deux versions du même commit Chromium génèrent exactement le même résultat. Malheureusement, nos ingénieurs de compilation Chromium ont découvert que la sortie .tsbuildinfo était non déterministe: crbug.com/1054494. Pour contourner ce problème, nous avons dû corriger le fichier .tsbuildinfo (qui contient essentiellement JSON) et le post-traiter pour renvoyer une sortie déterministe: https://crrev.com/c/2091448. Heureusement, l'équipe TypeScript a résolu le problème en amont, et nous avons rapidement pu supprimer notre solution de contournement. Merci à l'équipe TypeScript pour sa réceptivité aux rapports de bugs et la résolution rapide de ces problèmes !

Dans l'ensemble, nous sommes satisfaits de l'exactitude (type) du compilateur TypeScript. En tant que grand projet JavaScript Open Source, nous espérons que les outils de développement ont contribué à renforcer la compatibilité de JavaScript avec TypeScript.

Analyser les conséquences

Nous avons pu progresser dans la résolution de ces erreurs de type et augmenter progressivement la quantité de code vérifié par TypeScript. Toutefois, en août 2020 (neuf mois après le début de cette migration), nous avons fait un bilan et avons découvert que nous ne respecterions pas notre échéance à notre rythme actuel. L'un de nos ingénieurs a créé un graphique d'analyse pour montrer la progression de "TypeScriptification" (le nom que nous avons donné à cette migration).

Progression de la migration TypeScript

Progression de la migration avec TypeScript : lignes de code restantes à migrer

Les estimations correspondant à zéro ligne restante étaient comprises entre juillet 2021 et décembre 2021, soit près d'un an après notre échéance. Après avoir discuté avec la direction et d'autres ingénieurs, nous avons accepté d'augmenter le nombre d'ingénieurs travaillant sur la migration vers la prise en charge du compilateur TypeScript. C'est possible lorsque nous avons conçu la migration de sorte qu'elle soit parallélisable, de sorte que plusieurs ingénieurs travaillant sur plusieurs fichiers différents n'aient pas de conflits entre eux.

À ce stade, le processus TypeScriptification est devenu un effort de l'équipe. Grâce à ces aides supplémentaires, nous avons pu terminer la migration à la fin du mois de novembre 2020, 13 mois après le début, et plus d'un an avant la prévision initiale de notre estimation.

Au total, 18 ingénieurs ont envoyé 771 listes de modifications (semblable à une demande d'extraction). Notre bug de suivi (https://crbug.com/1011811) compte plus de 1 200 commentaires (la plupart d'entre eux sont publiés automatiquement à partir des listes de modifications). Notre feuille de suivi comportait plus de 500 lignes pour tous les fichiers à taper, la personne responsable et la liste de modifications dans laquelle ils avaient été "typés".

Limiter l'impact des performances du compilateur TypeScript

Le plus gros problème que nous traitons aujourd'hui est la lenteur des performances du compilateur TypeScript. Compte tenu du nombre d'ingénieurs qui conçoivent Chromium et les outils de développement, ce goulot d'étranglement est coûteux. Malheureusement, nous n'avons pas pu identifier ce risque avant la migration. C'est seulement lorsque nous avons migré la majorité des fichiers vers TypeScript que nous avons constaté une augmentation notable du temps passé sur les versions de Chromium: https://crbug.com/1139220.

Nous avons signalé ce problème en amont à l'équipe de compilation Microsoft TypeScript, mais celle-ci a malheureusement déterminé que ce comportement était intentionnel. Nous espérons qu'ils reviendront sur ce problème, mais en attendant, nous nous efforçons de réduire au maximum l'impact de la lenteur des performances dans Chromium.

Malheureusement, les solutions dont nous disposons aujourd'hui ne conviennent pas toujours aux contributeurs autres que Google. Les contributions Open Source à Chromium étant très importantes (en particulier celles de l'équipe Microsoft Edge), nous recherchons activement des alternatives qui fonctionneront pour tous les contributeurs. Toutefois, nous n'avons pas trouvé de solution alternative pour le moment.

État actuel de TypeScript dans les outils de développement

Pour le moment, nous avons supprimé le vérificateur de type du compilateur Closure de notre codebase et nous utilisons uniquement le compilateur TypeScript. Nous sommes en mesure d'écrire des fichiers créés par TypeScript et d'utiliser les fonctionnalités propres à TypeScript (comme les interfaces, les génériques, etc.), ce qui nous aide au quotidien. Nous avons davantage confiance dans le fait que le compilateur TypeScript détectera les erreurs et les régressions de type, ce qui est ce que nous espérions lorsque nous avons commencé à travailler sur cette migration. Cette migration, comme beaucoup d'entre elles, a été lente, nuancée et souvent difficile, mais au fur et à mesure que nous en tirons les bénéfices, nous pensons qu'elle en valait la peine.

Télécharger les canaux de prévisualisation

Nous vous conseillons d'utiliser Chrome Canary, Dev ou Beta comme navigateur de développement par défaut. Ces versions preview vous permettent d'accéder aux dernières fonctionnalités des outils de développement, de tester des API de pointe de plates-formes Web et de détecter les problèmes sur votre site avant qu'ils ne le fassent.

Contacter l'équipe des outils pour les développeurs Chrome

Utilisez les options suivantes pour discuter des nouvelles fonctionnalités et des modifications dans l'article, ou de toute autre question concernant les outils de développement.

  • Envoyez-nous une suggestion ou des commentaires via crbug.com.
  • Signalez un problème dans les outils de développement via Plus d'options   More > Aide > Signaler un problème dans les outils de développement dans les Outils de développement.
  • Envoyez un tweet à @ChromeDevTools.
  • Dites-nous en plus sur les nouveautés concernant les vidéos YouTube dans les outils de développement ou sur les vidéos YouTube de nos conseils relatifs aux outils de développement.