Migration en douceur vers TypeScript

9 minutes de lecture

Migration en douceur vers TypeScript

Rappel : TypeScript est un analyseur de code statique avec quelques syntaxes additionnelles au-dessus du langage JavaScript.

Les avantages de l’utilisation de TypeScript ne sont plus à faire, mais en voici quelques-uns que nous apprécions particulièrement.

  • 📚 Documentation “automatique” du code
  • ✅ Détecter les erreurs à la compilation
  • ⛑ Se sentir en sécurité, confiant
  • 😌 Écrire moins de tests

Depuis plus d’un an nous utilisons activement le langage et n’en décrochons plus. Toutefois nous travaillons parfois sur des projets hétéroclites, autant en termes de technologies que d’équipe et il est important pour la santé du projet de pouvoir le mettre à jour graduellement.

En douceur

L’utilisation d’un nouveau langage peut être une prise de risque car elle implique de nouveaux outils et connaissances, de plus l’équipe ne sera pas forcément favorable à ajouter encore un outil dans un écosystème qui est déjà très chargé.

Nous recommandons l’usage de typage statique avec TypeScript, cependant, il y a des compromis en termes d’installation, de volume de code et de lisibilité. L’avantage est certains sur de gros projets à long terme, mais pourra être une trop grosse contrainte pour les plus petits projets.

Les différentes méthodes et outils présentées dans cet article vont vous permettre d’introduire TypeScript progressivement dans un projet ou une équipe, de constater les avantages d’un langage typé et surtout de découvrir des bugs déjà présents que l’approche très flexible du JavaScript aurait pu laisser passer.

Les must-read avant de démarrer

Les outils

Selon votre projet, différents outils peuvent être utilisés.

La configuration TypeScript

Tout passe par un fichier tsconfig.json à la racine de votre projet. Il va contenir différentes options de compilation, règles d’inclusions/exclusions de vos fichiers. En voici un exemple très simple pour une application classique.

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es2017",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true
  },
  "exclude": ["node_modules", "dist"]
}

option noEmit

Il est possible d’utiliser TypeScript seulement comme vérificateur de types avec l’option noEmit, aucun code ne sera généré mais des erreurs seront remontées. À la manière d’un linter comme ESLint (qui en passant supporte la syntaxe TS).

Option checkJS+allowJs

L’option checkJS va vous permettre de valider vos fichiers JS grâce à la déduction de types offert par TypeScript.

Cette déduction va se baser sur la documentation JSDoc ou les assignations de variables, tout le reste aura le type any.

Le type any sera votre joker dans l’utilisation de TypeScript, très puissant mais à utiliser avec parcimonie pour ne pas perdre l’intérêt du typage.

Voir https://github.com/Microsoft/TypeScript/wiki/Type-Checking-JavaScript-Files

Librairies tierces

Un problème couramment rencontré est l’utilisation d’une librairie tierce. Plusieurs cas sont possibles

  • La librairie est écrite en TypeScript et ses types sont automatiquements importés
  • La librairie est écrite en JavaScript et ses types sont publiés sur l’organisation @types, vous pouvez donc les ajouter à votre projet avec yarn add @types/the-library-you-need.
  • La librairie est écrite en JavaScript et aucun type ne sont disponibles, donc la librairie aura le type any (à moins que vous écriviez les types de cette dernière…)

Heureusement les librairies les plus utilisées ont globalement migré vers TypeScript et ce dernier cas se fait de plus en plus rare.

React

Create react app

Chez Premier Octet nous travaillons principalement sur des applications React donc avec l’outil create-react-app qui supporte déjà TypeScript.

La documentation nous indique les commandes à suivre ainsi que différents liens pour bien démarrer avec React+TS.

La manière la plus rapide est d’écrire directement un fichier avec l’extension .ts ou .tsx et relancer le packager. CRA va automatiquement ajouter les dépendances et vous générer un fichier de configuration tsconfig.json ✨. Toutefois la configuration ne valide pas le JavaScript donc il est possible d’ajouter l’option checkJs cité plus haut.

PropTypes

Votre code contient surement déjà des PropTypes pour valider les props de vos composants. Si vous ne souhaitez pas les convertir il est possible de les utiliser directement avec le type InferProps du package prop-types.

import PropTypes, { InferProps } from 'prop-types';

const myTypes = {
  isActive: PropTypes.bool,
  // ...
};

type MyComponentProps = InferProps<typeof myTypes>;

L’alternative “full” TypeScript et plus consise est de typer directement les props.

type MyComponentProps = {
  isActive: boolean;
  // ...
};

const MyComponent = ({ isActive }: MyComponentProps) => <p>Active: {isActive.toString()}</p>;

HOC

Nous recommandons d’utiliser plutôt les hooks introduit en React 16.8, cependant si vous souhaiter typer un HOC il va falloir se munir d’un peu plus de connaissances selon la complexité du composant. En effet un HOC permet d’augmenter les fonctionnalités d’un composant en lui ajoutant des props, il va donc falloir utiliser les types génériques et peut être des utilitaires pour omettre ou extraire des types (cf la documentation).

Cet article détaille assez bien le typage de différentes implémentations de HOC.

Redux

TypeScript est supporté et même recommandé lors de l’utilisation de Redux. Toutefois nous sommes ici dans le coeur de l’application et la transition peut s’avérer fastidieuse.

Les states, actions, reducers, action creators, et connecteurs vont pouvoir être typés.

Nous recommandons cependant d’utiliser des surcouches comme Redux toolkit ou encore Rematch pour vous faciliter l’implémentation de Redux de manière générale mais aussi l’adoption de TypeScript sur cette partie.

Exemple avec un modèle Rematch pour l’authentification :

import { createModel } from '@rematch/core';
import { login } from '~api/auth';
import { LoginDto } from '~typings';

interface AuthState {
  token: string | null;
}

const auth = createModel({
  state: {
    token: null,
  } as AuthState,
  reducers: {
    setToken(state: AuthState, token: string): AuthState {
      return {
        token,
      };
    },
  },
  effects: {
    async login(loginData: LoginDto) {
      const { data } = await login(loginData);
      this.setToken(data.access_token);
    },
    logout() {
      this.setToken(null);
    },
  },
});

export default auth;

Génération de types

Le site transform.tools propose différents outils de transformation comme par exemple JSON vers TypeScript ou encore Flow vers TypeScript. De nombreuses librairies sont utilisés sur ce site, les détails d’implémentations sont disponibles sur Github si besoin.

Il est possible de typer une application consommant une API REST documenté avec OpenAPI ou encore une API GraphQL via de la génération de type.

OpenAPI

Si votre API utilise la spécification OpenAPI (ou Swagger), il est possible de générer des types automatiquements pour votre application via l’outil swagger-typescript-api. La commande suivante va requêter votre API et créer un fichier dans src/typings/index.d.ts contenant tous les types définis par votre API.

swagger-typescript-api -p http://localhost:3000/swagger-json -o ./src/typings -n index.d.ts

Exemple pour un endpoint de login, nécessitant un mot de passe et nom d’utilisateur puis retournant un access token :

export interface LoginRequestDto {
  emailAddress: string;
  password: string;
}

export interface LoginResponseDto {
  access_token: string;
}

Il est aussi possible d’utiliser le client d’API généré, utilisant fetch.

swagger-typescript-api Démo

GraphQL

L’outil graphql-codegen, assez similaire va permettre de générer les types pour une API GraphQL, avec possibilité de générer le client Apollo avec les hooks typés pour vous faire gagner encore plus de temps.

Nous avons déjà écrit sur le sujet sur notre blog en détail avec l’article « Typage de bout en bout avec TypeScript ».

Exemple pour une requête de récupération de l’utilisateur :

query GetUser {
  me {
    id
    email
    name
  }
}
import React from 'react';
import { useGetUser, GetUserMe } from './generated/graphql';

// Props are typed according to GraphQL query
type UserProps = {
  user: GetUserMe;
};

// Components are typed with props definition
const UserComponent = ({ user }: UserProps) => <p>Hello {user.name}</p>;

export default () => {
  // react-apollo hook, we got autocompletion for data and graphql variables here
  const { data, loading } = useGetUser();

  return (
    <div className="App">
      <header className="App-header">
        {/** Accessing an undefined key here will break the build */}
        {loading && <p>"Loading"</p>}
        {data && data.me && <UserComponent user={data.me} />}
      </header>
    </div>
  );
};

Node.js

Côté Node on peut souligner l’apparatition du runtime dédié à TypeScript, deno. Ce runtime apporte un vent de fraicheur dans l’écosystème Node avec des concepts intéressant comme le mode sandbox par défaut, ou encore le chargement des modules via une simple URL.

IDE

Notre IDE de prédilection est VSCode, mais IntelliJ IDEA supporte aussi nativement TypeScript.

Conclusion

Nous espérons que ces outils et recommendations vous permettront de découvrir TypeScript avec le moins de frustration possible. C’est maintenant un outil indispensable de nos équipes et qui nous assure une bonne robustesse de nos projets.

N’hésitez pas à sauter le pas !

Bibliographie

Ci-joint les liens qui ont inspiré cet article :

Si vous êtes plutôt tutoriels vidéos, retrouvez ce cours sur la plateforme egghead.

Continuer la discussion sur Twitter