Table of Content

Migration d'une application native SLOC Flow + React 50K vers TypeScript

[ad_1]



"Cela dépend" est la réponse type lorsqu'un ingénieur en logiciel est invité à donner son avis, si simple que puisse sembler un problème. À la question "Devrions-nous choisir TypeScript ou Flow pour notre prochain projet React Native?" Cependant, la réponse dépend uniquement d'une variable: si vous travaillez ou non sur Facebook .

Il est intéressant d'examiner comment il voit la situation. L'équipe du projet a évalué Flow vs TypeScript pour notre nouvelle application React Native il y a près de trois ans. À ce moment-là, TypeScript n'était pas compatible avec React, il ne permettait pas un abonnement progressif, Babel n'était pas pris en charge et VSCode n'était pas l'éditeur qui fournissait la meilleure expérience de développement JavaScript sur le marché.

Aucun de ces facteurs n'est vrai aujourd'hui, mais pourquoi s'embêter à migrer?

TypeScript a beaucoup à offrir, ce qui rend le commutateur très attrayant:

  • Plus "évolutif"
  • Adoption accrue du logiciel libre et meilleure prise en charge des bibliothèques RN +
  • Expérience impressionnante développeur (VSCode)
  • Compatibilité avec les autres projets du département
  • Plus facile d'embaucher pour

Nous passons donc à autre chose! Mais c'était comment?

Avant: SLK 50K, couverture de flux de 84%

  Application $ npx sloc
---------- Résultat ------------
Physique: 57903
Source: 50859
Nombre de fichiers lus : 709
----------------------------
npx flux-couverture-rapport -i & # 39; application / ** / *. js & # 39;
pourcentage: 84%
$ contrôle du temps
débit 3.64s utilisateur 2.31s système 32% du processeur total 18,458

À partir de

La première étape que nous avons eue a été de mettre à jour notre configuration des outils JS. Pour notre projet, la procédure était relativement simple, principalement guidée par une simple copie des paramètres directement à partir d’un projet de modèle TypeScript nouvellement instancié à l’aide du cli réact-native.

Prettier & ESLint

  {
"Analyseur" : "dactylographié"
}
Avant de choisir TypeScript, TSLint était utilisé, mais heureusement, TSLint est obsolète, ce qui en fait une option simple en 2019: restez avec ESLint!

Littéralement, aucun changement n'est nécessaire (pour notre utilisation de toute façon, et cela incluait des règles et des add-on personnalisés!)

Fait. Ensuite.

Bonus séparé: suppression des importations de module Black Magic

Saviez-vous qu'il est possible d'importer des modules JavaScript en relation avec un fichier package.json en utilisant l'attribut name du fichier json, aucun add-on n'est requis?

Magie noire! Mais la magie noire était utile!

Pour obtenir des importations par route absolues (c'est-à-dire,
  importez Foo depuis "app / ui / components / Foo") 
nous avons inclus dans notre projet un paquet
  . json  
dans le répertoire
   / app  
(et plusieurs autres pour un total de 4 package.json & # 39; s):
Cela fonctionne même quand Importer à partir d'autres dossiers de l'arborescence (ce qui signifie que vous pouvez importer du
  application / components / Foo 
hors du dossier "application".

Eh bien, TypeScript n'aime pas cette putain de magie noire et il ne pouvait pas & # 39; Ne découvrez pas nos importations :(. Ensuite, nous supprimons nos fichiers package.json supplémentaires et choisissons d'utiliser babel à la place.

Babel

Prise en charge intégrée de Babel pour TypeScript dans Babel 7, convertissant notre configuration Flow babel en TypeScript était un processus simple de suppression de compléments inutilisés (tels que @ babel / plugin-transformation-flow-strip-types ).

Voici notre version finale Babel.config.js, complétée par un résolution du module moins magique.

  module  .exports = {
presets : [ & # 39; module: metro-react-native-babel-preset & # 39; ],
modules complémentaires : [
[
& # 39; résolveur de modules & # 39;
{
racine : [ & # 39;. & # 39; ],
alias : {
application : & # 39; ./ app & # 39;
},
},
],
]
}

tsconfig.json

Bien que l'exécution de votre application React Native ne soit pas absolument nécessaire (Babel supprimera / ignorera de toute façon la syntaxe TS), tsconfig est nécessaire pour utiliser le compilateur TypeScript (et les outils VSCode). Détecter les erreurs de type.

Heureusement, nous avons atteint 90% de la route en copiant simplement le tsconfig d'un nouveau projet React Native.

Cependant, nous incluons également certains fichiers .json dans notre projet, qui nécessitent une configuration supplémentaire.

  {
"compilerOptions" : {
"baseUrl" : "."
"allowJs" : true ,
"allowSyntheticDefaultImports" : true ,
"esModuleInterop" : true ,
"Modules isolés" : true ,
"jsx" : "react-native" ,
"lib" : [ "es6" "dom" ],
"moduleResolution" : "noeud" ,
"noEmit" : true ,
"strict" : true ,
"objectif" : "esnext" ,
"resolutJsonModule" : true // Requis pour les fichiers JSON
"skipLibCheck" : true
},
"exclure" : [
"node_modules" ,
"e2e" ,
"** / *. Json" // N'essayez pas de vérifier les fichiers JSON
"** / *. Spec.ts" ,
]
}

Migration de Flow vers le TS et la syntaxe .js vers .ts / x

Heureusement, la communauté open source a effectué un travail visant à convertir la syntaxe de Flow en TypeScript, et les projets ont Prise en charge de nombreuses caractéristiques communes du langage.

Cependant, je ne peux pas dire que ces projets ont tendance à être très bien entretenus ou classiques.

Heureusement, le script-plugin-plug-in-babel a fonctionné pour nous. Presque.

L'écriture de la même erreur liée au support de fonction nous a obligés à utiliser un fork. Je ne sais pas ce qui se passe avec ces outils et avec le support des anciennes fonctions, mais cela a fonctionné, je pense que je devrais être reconnaissant!

Convertissez JavaScript + Flow avec une extension .js en TypeScript avec le thread .ts:

  add global @ babel / cli @ babel / core babel-plugin-flow-to-typescript
# Convertir un fichier unique
npx babel script.js -o script.ts --plugins = babel-plugin-flow-à-typescript
# Convertissez tous les fichiers et supprimez le fichier JS d'origine après conversion.
# Prérequis: & # 39; installer le brassage parallèle & # 39;
application de recherche - type f -name & # 39; *. js & # 39; | parallel "npx babel {} -o {.}. ts && rm {}"
Remarque: Nous utilisons vraiment @ zxbodya / babel-plugin-flow-to-typecript en raison d'une erreur dans le référentiel principal.

Ensuite, renommez les fichiers qui importent React en haut pour utiliser .tsx à la place:

  recherchez app / components -name  "*. Ts"  -  exec  sh - c  & gt mv "$ 0" "$ {0% .ts} .tsx" & # 39;  {} ; 

Bilan de santé: l'application est-elle toujours en cours d'exécution?

Avant de continuer, il est utile de vérifier à ce stade que le projet fonctionne réellement. Parce que Babel est utilisé pour compiler TypeScript en JavaScript et que Babel supprime simplement toute la syntaxe liée à TypeScript, quel que soit le nombre d'erreurs de type de votre projet, il devrait "fonctionner correctement".

Dans notre cas, il a fallu moins d'une demi-journée pour mettre à jour les configurations, renommer les fichiers, migrer la syntaxe complète et obtenir à nouveau une application en cours d'exécution.

Mais le travail est encore loin d'être terminé.

Correction de milliers d'erreurs de compilation

Dans notre cas, l'effort a commencé avec ~ 1800 erreurs de compilation TS.

Corriger ce nombre d'erreurs n'est pas un parcours linéaire dans lequel une solution signifie que vos erreurs sont réduites à 1799. Parfois, un changement entraîne des dizaines d'erreurs, vous faisant vous sentir comme un dieu parmi les mortels. et, parfois, le nombre d'erreurs augmente en général, ce qui ébranle votre confiance. En raison principalement des différences entre TS et Flow en ce qui concerne l'inférence de type, il faut immédiatement ajouter des annotations supplémentaires.

Remarque: n'ajoute pas simplement un type de manière explicite lorsque TypeScript se plaint . Demandez plutôt "Pourquoi?"

   // Problème ici 
const reqs = get (accessoires, et demande [])
const inProgressIds = reqs.map ( req => req.id)
const activeRequests = actions
// Erreur affichée ici
.filter ( action => inProgressIds.includes (action.id))
Au lieu d'ajouter un type à l'action
  
comme le suggère l'erreur, [19659076] reqs un type est manquant, ce qui produit l'erreur suivante. La cause fondamentale pourrait être un autre problème dans la fonction, ou même un fichier complètement différent!

# 2: Mise à jour des composants React

La partie la moins agréable et la moins "compliquée" de la conversion consiste à migrer le code React. Les erreurs générées dans le code React comportent souvent de longues chaînes d'erreur, parfois avec des détails qui semblent totalement étrangers à la solution requise.

Notre projet limite l'utilisation de primitives React Native à partir de nos composants "intelligents". Nous avons plutôt choisi de créer notre propre ensemble de primitives dans un cadre d'interface utilisateur réutilisable.

   / * @flow * / 
import Button en tant que RNButton de & # 39; rea-native & # 39;
// Scellez pour éviter les avertissements concernant une propagation inexacte
type PropTypes = {|
onPress: () => mixte,
coutume : Booléen,
|}
// Composante banalisée
const Button = ( accessories: PropTypes ) =>
Sous le débit, il faut ajouter chaque dernier accessoire dans
  
à
  PropTypes 
où nous préférerions utiliser tous les accessoires que
   
et ajouter notre propre coutume. [19659090] // Importez les accessoires disponibles pour chaque composant React Native! : D
import Button en tant que RNButton, {ButtonProps} de & # 39; rea-native & # 39;
L’interface PropTypes étend ButtonProps {
coutume : Booléen,
}
const Button = ( accessories: PropTypes ) =>

Bien préférable (et précis)!

Les interfaces sont l'une des meilleures parties de TypeScript!

# 3: Refactoriser HOC

Un avantage obtenu avec TypeScript over Flow est la possibilité d'utiliser des arguments de type générique dans le corps de sa fonction. Je ne suis pas sûr que cela soit généralement conseillé, mais c'est une excellente fonctionnalité pour ajouter des types aux composants d'ordre supérieur!

   // TS autorise l'utilisation de génériques dans le corps de votre COH. Oui! 
const withAThing = < T > (config: Config ) =>
(WrappedComponent: React.ComponentType ) =>
(accessoires: T) =>

Toutefois, pour tirer parti de cette fonctionnalité, nous avons plus ou moins besoin de réécrire complètement nos définitions de type pour tous nos composants d'ordre supérieur. Aïe

Inutile de dire que nous préférerions les hameçons!

Suivante: 96% TypeScript couverture

  $  type  -de couverture -p tsconfig.json  # preuve ignorée + livre de récits 
68712/71471 96,13%
$ time tsc
Système TSC 29.82s 1.76s utilisateur 166% Cpu 18.955 total

Augmentation de la couverture d'environ 12%. Runtime plus ou moins identique.

De loin, les lacunes de couverture les plus importantes existent dans notre code Redux Saga. C'était également le cas dans Flow, mais il semble que le passage à TypeScript ne se soit pas beaucoup amélioré, le cas échéant.

Résumé

Si vous utilisez toujours Flow avec React Native, cela n'a jamais été aussi facile de changer! 19659003] Il a fallu l'équivalent de 3 ingénieurs travaillant 10 jours ouvrables pour réaliser la conversion, y compris le temps nécessaire aux tests de régression, à la révision du code, au refactoring et au développement de nouveaux modèles d'utilisation.

La plupart des erreurs peuvent être facilement résolues avec un message d'erreur de recherche Google, bien qu'il faille un peu de temps, de considération et d'expérience pour faire confiance à la solution.

Bien que nous ayons découvert certaines erreurs (principalement liées à l'utilisation incorrecte des API tierces), il s'agissait d'un avantage mineur. L’obtention d’une couverture supplémentaire dans notre code React a permis de supprimer le code mort.

En général, nous avons créé une meilleure base de code pour un développement futur continu, à la fois en termes de support communautaire et d'expérience de développeur.














[ad_2]

Post a Comment