
JavaScript est l'un des langages à saisie dynamique. Ces langages sont pratiques pour le développement rapide d'applications, mais lorsque plusieurs équipes se lancent dans le développement d'un grand projet, il est préférable de choisir l'un des outils pour la vérification de type dès le début.
Vous pouvez commencer à développer du code TypeScript ou l'inclure dans un projet Flow. TypeScript est une version compilée de JavaScript développée par Microsoft. Contrairement à TypeScript, Flow n'est pas un langage, mais un outil qui vous permet d'analyser le code et de vérifier les types. Vous pouvez trouver de nombreux articles et vidéos sur le net sur ces approches, ainsi qu'un guide sur la façon de commencer à utiliser la saisie. Dans cet article, nous aimerions vous expliquer pourquoi Flow ne nous convenait pas et comment nous avons commencé à passer à Typescript.
Un peu d'histoire
En 2016, nous avons commencé à développer un client Web basé sur React / Redux pour notre système ECM. Le flux a été sélectionné pour tester la saisie pour les raisons suivantes:
- React et Flow sont des produits de la même société Facebook.
- Flow s'est développé plus activement.
- Flow est facilement intégré au projet.
Mais le projet a grandi, le nombre d'équipes de développement a augmenté et un certain nombre de problèmes sont apparus lors de l'utilisation de Flow:
- Vérification du type d'arrière-plan Le flux a utilisé trop de ressources PC. En conséquence, certains développeurs l'ont désactivé et ont commencé à vérifier au besoin.
- Il y avait des situations où il fallait autant de temps pour mettre le code en conformité avec Flow que d'écrire le code lui-même.
- Le code a commencé à apparaître dans le projet, nécessaire uniquement pour passer le test Flow. Par exemple, double vérification pour null:
foo() { if (this.activeFormContainer == null) { return; }
- La plupart des développeurs ont utilisé l'éditeur de code Visual Studio Code, dans lequel Flow ne prend pas en charge aussi bien que TypeScript. La saisie semi-automatique (IntelliSense) n'a pas toujours fonctionné pendant le développement et la navigation dans le code était instable. Je voudrais avoir la même commodité de développement que lors de l'écriture en C # dans Visual Studio.
Certains développeurs ont eu l'idée d'essayer de passer à TypeScript. Afin de tester l'idée de la transition et convaincre la direction, nous avons décidé d'essayer le prototype.
Prototype
Nous voulions tester deux idées sur les prototypes:
- Essayez de traduire l'ensemble du projet.
- Configurez le projet pour pouvoir utiliser à la fois Flow et Typescript en parallèle.
Pour la première idée, un utilitaire était nécessaire pour convertir tous les fichiers du projet. Sur le réseau a trouvé l'un d'eux. À en juger par la description, elle pourrait traduire la plupart, mais certaines des modifications devraient être éditées par nous-mêmes, ou l'utilitaire lui-même devrait être ajouté. Nous avons pu convertir un projet de test avec un petit nombre de fichiers. Mais le vrai projet n'a pas pu être compilé, il aurait fallu éditer trop de fichiers. Nous avons décidé de ne pas continuer dans cette direction, car:
- Il y avait encore beaucoup à faire! Et tandis que nous finaliserons le projet, les équipes restantes continueront à développer de nouvelles fonctionnalités, à corriger des bugs, à écrire des tests. De plus, la fusion des fichiers prendrait beaucoup de temps.
- Même si nous traduisions le projet de cette manière, alors combien de travail nos testeurs auraient à faire!
Bien que nous ayons abandonné cette option, nous avons acquis une expérience utile à ce sujet. Il est devenu clair la quantité approximative de travail à faire pour traduire chaque fichier. Voici à quoi ressemble la traduction d'un simple composant React.

Comme vous pouvez le voir, il n'y a pas beaucoup de changements. Fondamentalement, ils sont les suivants:
- remove // @ flow;
- remplacer le type par une interface plus familière;
- ajouter des modificateurs d'accès;
- remplacer les types par des types issus des bibliothèques ts (à partir de l'exemple de l'image: les gestionnaires d'événements et les événements eux-mêmes).
L'implémentation sur la deuxième idée permettrait un développement ultérieur, mais déjà sur TypeScript, et en arrière-plan pour traduire lentement la base de code existante. Cela offrait plusieurs avantages:
- Facile à traduire, sans crainte de manquer quelque chose.
- Facile à tester.
- Fusion facile des modifications.
Mais il n'était pas tout à fait clair si le projet pouvait être configuré pour fonctionner avec deux types de frappe en parallèle. Une recherche sur Internet n'a abouti à rien de concret, alors ils ont commencé à le régler eux-mêmes. En théorie, l'analyseur de flux vérifie uniquement les fichiers avec l'extension js / jsx et contenant un commentaire:
Pour le compilateur TypeScript, les fichiers doivent avoir l'extension ts / tsx. D'où il résulte que les deux approches de la frappe devraient fonctionner simultanément et ne pas interférer l'une avec l'autre. Sur cette base, nous avons mis en place l'environnement du projet. En utilisant l'expérience du premier prototype, nous avons traduit quelques fichiers. Compilation du projet, lancement du client - tout fonctionnait comme avant!
Feu vert
Et un beau jour - le jour de la planification du sprint, notre équipe a une histoire utilisateur «Commencer à passer à TypeScript» dans le backlog, avec la liste des travaux suivante:
- Configurez le webpack.
- Configurez tslint.
- Configurez un environnement de test.
- Traduire des fichiers en TypeScript.
Configuration du Webpack
La première étape consiste à enseigner à webpack comment gérer les fichiers avec l'extension ts / tsx. Pour ce faire, nous avons ajouté une règle à la section des règles du fichier de configuration. Chargeur ts utilisé à l'origine:
Pour accélérer l'assemblage, la vérification de type a été désactivée:
transpileOnly: true
, car L'IDE indique déjà des erreurs lors de l'écriture du code.
Mais lorsque nous avons commencé à traduire nos actions Redux, il est devenu clair qu'elles avaient besoin du
plugin babel-plugin-transform-class-display-name pour fonctionner. Ce plugin ajoute une propriété displayName statique à toutes les classes. Après la traduction, seules les actions de ts-loader ont été traitées, ce qui n'a pas permis de leur appliquer des plugins babel. En conséquence, nous avons abandonné ts-loader et étendu la règle existante pour js / jsx en ajoutant
babel / preset-typescript:
Pour que le compilateur TypeScript fonctionne correctement, vous devez ajouter le fichier de configuration tsconfig.json, il provient de la documentation.
Configurer Tslint
Le code écrit à l'aide de Flow a également été vérifié à l'aide d'eslint. TypeScript a son homologue, tslint. Au départ, je voulais transférer toutes les règles d'eslint à tslint. Il y a eu une tentative de synchronisation des règles via le plugin tslint-eslint-rules, mais la plupart des règles ne sont pas prises en charge. Il est également possible d'utiliser eslint pour vérifier les fichiers ts à l'aide de typescript-eslint-parser. Mais, malheureusement, un seul analyseur peut être connecté à eslint. Si vous utilisez uniquement ts-parser pour tous les types de fichiers, de nombreuses erreurs étranges apparaissent dans les fichiers js et ts. En conséquence, nous avons utilisé l'ensemble de règles recommandé, étendu à nos exigences:
// tslint.json "extends": ["tslint:recommended", "tslint-react"]
Traduire un fichier en TypeScript
Maintenant, tout est prêt et vous pouvez commencer à traduire des fichiers. Pour commencer, nous avons décidé de transférer un petit composant React, qui est utilisé tout au long du projet. Le choix s'est porté sur le composant «Button».

Nous avons rencontré un problème lors du processus de traduction: toutes les bibliothèques tierces n'ont pas de typage TypeScript, par exemple, bem-cn-lite. Sur la ressource
TypeSearch de Microsoft, la bibliothèque de types de celle-ci est introuvable. pour presque toutes les bibliothèques nécessaires, nous avons trouvé et connecté des bibliothèques de type ts. Une solution consistait à se connecter via require:
const b = require('bem-cn-lite');
Mais en même temps, le problème du manque de types n'a pas été résolu. Par conséquent, nous avons généré un «stub» pour les types nous-mêmes, en utilisant l'utilitaire
dts-gen :
dts-gen -m bem-cn-lite
L'utilitaire a généré un fichier avec l'extension * .d.ts. Le fichier a été placé dans le dossier @types et configuré tsconfig.json:
// tsconfig.json "typeRoots": [ "./@types", "./node_modules/@types" ]
Ensuite, par analogie avec le prototype, nous avons traduit le composant. Compilation du projet, lancement du client - tout a fonctionné! Mais les tests ont échoué.
Configuration de l'environnement de test
Pour tester l'application, nous utilisons Storybook et Mocha.
Storybook est utilisé pour les tests de régression visuelle (
article ). Comme le projet lui-même, il est construit à l'aide de webpack et possède son propre fichier de configuration. Par conséquent, pour fonctionner avec les fichiers ts / tsx, il fallait le configurer par analogie avec la configuration du projet lui-même.
Pendant que nous utilisions ts-loader pour construire le projet, nous avons arrêté d'exécuter les tests Mocha. Pour résoudre ce problème, ajoutez ts-node à l'environnement de test:
// mocha.opts --require @babel/polyfill --require @babel/register --require test/index.js --require tsconfig-paths/register --require ts-node/register/transpile-only --recursive --reporter mochawesome --reporter-options reportDir=../../bin/TestResults,reportName=js-test-results,inlineAssets=true --exit
Mais après être passé à Babel, vous pouvez vous en débarrasser.
Les problèmes
Dans le processus de traduction, nous avons rencontré un grand nombre de problèmes de divers degrés de complexité. Ils étaient principalement liés à notre manque d'expérience avec TypeScript. En voici quelques uns:
- Importez des composants / fonctions à partir de différents types de fichiers.
- Traduction de composants d'ordre supérieur.
- Historique des pertes de changement.
Importer des composants / fonctions à partir de différents types de fichiers
Lors de l'utilisation de composants / fonctions de différents types de fichiers, il est devenu nécessaire de spécifier l'extension de fichier:
import { foo } from './utils.ts'
Cela vous permet d'ajouter des extensions valides aux fichiers de configuration webpack et eslint:
Traduction de composants d'ordre supérieur
De tous les types de fichiers, la traduction du composant d'ordre supérieur (HOC) a causé le plus de problèmes. Il s'agit d'une fonction qui prend un composant en entrée et renvoie un nouveau composant. Il est principalement utilisé pour réutiliser la logique, par exemple, il peut s'agir d'une fonction qui ajoute la possibilité de sélectionner des éléments:
const MyComponentWithSeletedItem = withSelectedItem(MyComponent);
Ou la connexion la plus célèbre de la bibliothèque Redux. La saisie de ces fonctions n'est pas anodine et nécessite la connexion d'une bibliothèque supplémentaire pour travailler avec les types. Je ne décrirai pas le processus de traduction en détail, car vous pouvez trouver de nombreux manuels sur le sujet sur le net. En bref, le problème est qu'une telle fonction est abstraite: tout composant avec n'importe quel ensemble de propriétés peut accepter une entrée. Il peut s'agir d'un composant Button avec les propriétés title et onClick ou un composant Picture avec les propriétés alt et imgUrl. L'ensemble de ces propriétés ne nous est pas connu à l'avance; seules les propriétés que la fonction elle-même ajoute sont connues. Pour que le compilateur TypeScript ne jure pas lors de l'utilisation de composants obtenus à l'aide de telles fonctions, il est nécessaire de "couper" les propriétés que la fonction ajoute du type renvoyé.
Pour ce faire, vous avez besoin de:
- Tirez ces propriétés dans l'interface:
interface IWithSelectItem { selectedItem: number; handleSelectedItemChange: (id: number) => void; }
- Supprimez toutes les propriétés qui entrent dans l'interface IWithSelectItem de l'interface du composant. Pour ce faire, vous pouvez utiliser l'opération Diff <T, U> à partir de la bibliothèque de types d'utilitaires .
React.ComponentType<Diff<TPropsComponent, IWithSelectItem>>
Historique des pertes de changements
Pour travailler avec des sources, par exemple, la révision de code, nous utilisons Team Foundation Server. Lors de la traduction de fichiers, nous sommes tombés sur une caractéristique désagréable. Au lieu d'un fichier modifié, deux apparaissent dans le pool de demandes:
- remote - l'ancienne version du fichier;
- créé - nouvelle version.

Ce comportement est observé s'il existe de nombreuses modifications dans le fichier (similitude <50%), par exemple, pour les petits fichiers. Pour résoudre ce problème, nous avons essayé d'utiliser:
- commande git mv
- exécuter deux commits: le premier modifie l'extension du fichier, le second avec des corrections immédiates.
Mais, malheureusement, les deux approches ne nous ont pas aidés.
Résumé
Utilisez Flow ou TypeScript - chacun décide pour lui-même, les deux approches ont leurs avantages et leurs inconvénients. Nous avons choisi TypeScript pour nous-mêmes. Et vous avez été convaincu par votre propre expérience: si vous avez choisi l'une des approches et que vous avez soudain réalisé, même après trois ans, que cela ne vous convenait pas, vous pouvez toujours la changer. Et pour une transition plus fluide, vous pouvez configurer le projet, comme nous, pour fonctionner en parallèle.
Au moment d'écrire ces lignes, nous n'étions pas encore complètement passés à TypeScript, mais nous avons déjà réécrit la partie principale - le «noyau» du projet. Dans la base de code, vous pouvez trouver des exemples de traduction de toutes sortes de fichiers, d'un simple composant React à des composants d'ordre supérieur. De plus, une formation a été dispensée à toutes les équipes de développement, et maintenant chaque équipe, dans le cadre de sa tâche, transfère une partie du projet à ces fonctions.
Nous prévoyons de terminer la transition avant la fin de l'année, de traduire des tests et un livre de contes, et peut-être même d'écrire certaines de nos règles tslint.
Selon mes sentiments personnels, je peux dire que le développement a commencé à prendre moins de temps, la vérification de type se fait à la volée, sans charger le système, et les messages d'erreur pour moi personnellement sont devenus plus compréhensibles.