Rapidement Configurer un Projet TypeScript

1/5)

Introduction

En cours de rédaction :)

Beaucoup de gens utilisent des outils dits de scaffolding  (littéralement "construction d'échafaudage") pour créer la myriade de fichiers de configuration nécessaire à un projet web digne de ce nom de nos jours.

D'autres se créent un ou plusieurs dépôts dits "de boilerplate"  qu'ils clonent à chaque nouveau projet.

Ces approches sont pratiques, mais je ne les aime pas beaucoup :

  • avec le scaffolding je ne comprends pas ce que je fais, je manque de flexibilité, j'ai du mal à débugger quand quelque chose ne fonctionne pas correctement
  • avec les boilerplate je me coupe de l'avancée de l'état de l'art et m'enferme dans des habitudes

Ma stratégie personnelle est de tout reconstruire à partir de zéro à chaque fois, tout en m'inspirant de sources que je connais bien pour accélérer la manoeuvre.

Ces sources peuvent être certains de mes projets, de la documentation officielle bien réputée...

Plus on fait ça, plus on va vite et plus on en apprend sur ses outils. Je perds sans doute une dizaine de minute à chaque projet par rapport à quelqu'un qui va utiliser un outil magique, mais que sont dix minutes sur la vie d'un projet, surtoût quand on prend en compte le bénéfice de l'apprentissage'?

Pour rédiger cette page, je me suis installé un Ubuntu 20.04 tout frais dans une VM, histoire de vraiment prendre les choses depuis le début.

Je vais tâcher de documenter la manière la plus efficace, selon moi, de créer un projet en TypeScript avec le linting et tout ce qui va bien en partant d'une installation vierge d'Ubuntu.

Nous sommes le 29 juin 2021, cette documentation sera sans doute périmée demain. Allons-y...

2/5)

Installation de Node.js et de yarn

J'utilise le plus souvent possible snap pour installer des choses, on a des paquets plus récents, mis à jour automatiquement, et, pour certains, avec un système d'isolation de l'environnement d'exécution du programme qui fait qu'on augmente la sécurité de son système.

Bon, la nature de node fait qu'on n'a pas de sécurité supplémentaire grâce à snap par rapport à apt mais c'est pas grave.
C'est pour ça qu'on met --classic dans la commande qui suit :

Installation de Node.js :
sudo snap install node --channel 16/stable --classic
Zoomez ou dé-zoomez l'exemple avec la commande ci-dessous :

Ensuite, j'installe yarn comme gestionnaire de paquets plutôt que le traditionnel npm, parce que yarn apporte plein d'avantages comme :

  • une installation beaucoup plus rapide des dépendances
  • une meilleure reproductibilité des installations grâce au fait que l'on versionne ses dépendances dans son projet
  • de façon générale, une expérience utilisateur plus agréable

La liste des avantages de yarn est longue, mais ce n'est pas le sujet ici. Installons-le :

Installation de yarn :
sudo npm install -g yarn
Zoomez ou dé-zoomez l'exemple avec la commande ci-dessous :

À l'heure où j'écris ces lignes, c'est la version 1.x de yarn est installée. On verra plus tard qu'on utilisera en fait la v2 pour notre projet.

3/5)

Initialisation de git et de yarn

On se met dans le dossier qu'on veut  puis :

Initialisation de git

J'initialise toujours en premier un dépôt git que je mets sur GitHub, soit en privé soit en public.

C'est gratuit, on n'a pas d'excuse pour ne pas le faire. Versionner son code c'est toujours bien, même quand on pense que le projet ne va servir à rien, on ne sait jamais.
Et puis ça permet de le retrouver de n'importe-où.
Le code pour ce tuto est sur GitHub.

En initialisant son dépôt git en premier, on gagne un peu de temps car certains outils utilisent les informations fournies par git pour accélérer un peu leur config. Notamment yarn init.

Je vais sur GitHub créer le répo et en bon fainéant que je suis je copie-colle la plupart des instructions qu'il me donne pour gagner du temps. Sauf le README. Fuck le README.

Création du dossier :
mkdir ts-experiments
cd ts-experiments
git init .
git branch -M main
git commit --allow-empty -m'first commit'
git remote add origin git@github.com:djfm/ts-experiments.git
git push -u origin main
Zoomez ou dé-zoomez l'exemple avec la commande ci-dessous :

On s'occupe tout de suite de rajouter un fichier .gitignore  avant de faire quoi que ce soit avec yarn.

Fichier .gitignore de base :
node_modules

.yarn/*
!.yarn/cache
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
Zoomez ou dé-zoomez l'exemple avec la commande ci-dessous :

Source de cette information : yarnpkg.com.

Et voila pour git !  On peut passer à la suite.

Initialisation de yarn

Enfin on lance yarn init :

initialisation du package.json :
yarn init
Zoomez ou dé-zoomez l'exemple avec la commande ci-dessous :

Là, il n'y à qu'a répondre aux questions. Pour l'entry point on peut mettre src/index.ts à la place du index.js suggéré.

Grâce au fait que j'ai déjà initialisé mon répo git la réponse à la question repository url est déjà pré-remplie ansi que author.

Ça nous crée un fichier package.json tout comme l'aurait fait npm.
Pour private je mets 'true' car je n'ai pas l'intention de publier ce paquet sur le registry global.

Maintenant que j'ai un package.json je rajoute dedans le très important 'type: module' qui va indiquer à Node.js que j'ai l'intention d'utiliser les modules javascript ES6+, i.e. import/export.

On reparlera de ce grand bazaar.

Au final mon package.json ressemble à ça :

package.json :
{
  "name": "ts-experiments",
  "type": "module",
  "version": "1.0.0",
  "description": "test stuff",
  "main": "src/index.ts",
  "repository": "git@github.com:djfm/ts-experiments.git",
  "author": "djfm <fm.de.jouvencel@gmail.com>",
  "license": "MIT",
  "private": true
}
Zoomez ou dé-zoomez l'exemple avec la commande ci-dessous :

Maintenant qu'on a un package défini, on peut mettre à jour yarn :

Mise à jour de yarn vers la v2 :
yarn set version berry
yarn --version # 2.4.2 dans mon cas
echo "nodeLinker: node-modules" >> .yarnrc.yml
Zoomez ou dé-zoomez l'exemple avec la commande ci-dessous :

Le echo "nodeLinker: node-modules" >> .yarnrc.yml est important, cela dit à yarn d'utiliser un traditionnel dossier node_modules au lieu de sa relativement nouvelle architecture dénommée Plug'n'Play.

4/5)

Initialisation de TypeScript et tsconfig.json

Il nous manque un élément crucial, bien sûr,installer TypeScript !!

installation de typescript :
yarn add -D typescript @types/node
Zoomez ou dé-zoomez l'exemple avec la commande ci-dessous :

Le compilo TypeScript prend par défaut sa myriade de paramètres dans un fichier tsconfig.json à mettre à la racine du projet.

Je détaillerai plus tard, peut-être, ce qu'il faut en retenir, mais en voici déjà un qui marche bien :

tsconfig.json :
{
  "compilerOptions": {
    "esModuleInterop": true,
    "sourceMap": true,
    "jsx": "react",
    "downlevelIteration": true,
    "target": "ESNext",
    "module": "CommonJS",
  }
}
Zoomez ou dé-zoomez l'exemple avec la commande ci-dessous :

Si vous voulez vraiment rigoler, mettez "strictNullChecks": true dans les compilerOptions, on en reparlera :)

5/5)

Configuration du linter eslint

J'installe toujours en premier le linter, on va prendre eslint comme pour le JavaScript. C'est de loin la solution la plus populaire et la plus standard.
C'est toujours bien d'être dans les standards.
À la base eslint a été fait pour linter le JavaScript mais il a un plugin pour parser le TypeScript et tout un tas d'ensembles de règles très bien faites pour nous aider au quotidien, même des règles spécifiques à TypeScript via des plugins qu'on va installer.

Au plus tôt on lint, au plus tôt on évite d'être con.

On l'installe avec yarn et on lance son script d'initialisation  pour ne pas s'embêter à créer notre config de toutes pièces.

Installation du linter :
yarn add -D eslint

# notez que contrairement à npm,
# avec yarn pas besoin de rajouter "run"
# pour lui demander de lancer un binaire qu'il
# a installé
yarn eslint --init
Zoomez ou dé-zoomez l'exemple avec la commande ci-dessous :

On répond aux questions :

How would you like to use ESLint?
To check syntax, find problems, and enforce code style
What type of modules does your project use?
JavaScript modules (import/export)
Which framework does your project use?
React (oui, on sais jamais, me connaissant...)
Does your project use TypeScript?
Yes
Where does your code run?
Browser, Node (on appuie sur "a" pour tout choisir)
How would you like to define a style for your project?
Use a popular style guide
Which style guide do you want to follow?
Airbnb: https://github.com/airbnb/javascript
What format do you want your config file to be in?
JSON

Là, ça va nous proposer d'installer les dépendances qui vont bien, mais il va les installer avec npm et ce n'est pas ce qu'on veut, donc on copie la liste des dépendances et on répond No.

On peut maintenant installer avec yarn les dépendances qu'on a copiées.

Mais la liste n'est hélas pas complète, j'en rajoute donc :

Installation des dépendances d'eslint :
yarn add -D eslint-plugin-react @typescript-eslint/eslint-plugin
yarn add -D eslint-config-airbnb @typescript-eslint/parser
yarn add -D eslint-plugin-import eslint-plugin-jsx-a11y
Zoomez ou dé-zoomez l'exemple avec la commande ci-dessous :

De toutes façons c'est simple, s'il manque des dépendances on regarder les erreurs affichées par yarn et on les installes ensuite avec yarn add -D...

Ensuite, très utile : Se préparer une tâche de lint dans package .json.

on s'en servira rarement, mais ça permet de vérifier au moins que la config est bonne - des fois quand la config n'est pas bonne l'IDE se met tout simplement à arrêter silencieusement de signaler les erreurs.

Donc c'est bien pratique de pouvoir lancer un petit yarn lint  pour se rassurer si jamais on trouve que tout est décidément trop calme.

Pour faire ça, dans package.json on ajoute :

ajout du script lint :
"scripts" : {
  "lint": "yarn eslint 'src/**/*'"
}
Zoomez ou dé-zoomez l'exemple avec la commande ci-dessous :

Configuration eslint de base

Normalement, de base, eslint aura généré quelque chose comme ça :

fichier .eslintrc.json à ce stade :
{
  "env": {
      "browser": true,
      "es2021": true,
      "node": true
  },
  "extends": [
      "plugin:react/recommended",
      "airbnb",
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
      "ecmaFeatures": {
          "jsx": true
      },
      "ecmaVersion": 12,
      "sourceType": "module"
  },
  "plugins": [
      "react",
      "@typescript-eslint"
  ],
  "rules": {
  }
}
Zoomez ou dé-zoomez l'exemple avec la commande ci-dessous :

Mes modifications habituelles aux règles de base d'eslint

Mais à chacun ses gouts (et ses impératifs), je noterai dans ce qui suit les règles que j'éprouve le plus souvent le besoin d'ajuster.

Pour appliquer les règles qui suivent, il n'y a qu'à les insérer dans l'objet "rules" du fichier ".eslintrc.json".

En ce qui concerne React

Modifications aux règles eslint de base pour React :
// celle-la, tout simplement pour
// pouvoir utiliser du JSX dans des fichiers
// portant l'extension ".tsx" en plus de ".jsx"
"react/jsx-filename-extension": [
  1, {
  "extensions": [".tsx", ".jsx"]
}]
Zoomez ou dé-zoomez l'exemple avec la commande ci-dessous :

Pour le TypeScript en général

Il y a pas mal de règles parfaitement sensées en JavaScript  mais qui perdent de leur pertinence en TypeScript grace aux garanties que TypeScript nous apporte sur notre code.

Il ne faut donc pas hésiter à désactiver les règles qui n'ont pas de sens en TypeScript, surtout quand une autre, spécifique à TypeScript la remplace.

Modifications aux règles du .eslintrc.json pour TypeScript :
// oui, c'est une bonne règle,
// mais on en hérite d'une mieux
// fournie par "plugin:@typescript-eslint/recommended"
// et qui tient compte des spécificités de TypeScript,
// donc je désactive celle de base
"no-use-before-define": 0,

// quand on transpile vers de l'ESNext,
// c'est vraiment superflu
"no-restricted-syntax": 0,

// j'ai tendance à aller à la ligne après la flèche
// dans une arrow-function quand la ligne est trop longue,
// car je code souvent avec l'éditeur séparé en deux
// et je déteste les lignes qui dépassent
"implicit-arrow-linebreak": 0
Zoomez ou dé-zoomez l'exemple avec la commande ci-dessous :

Le fastidieux plugin import

On rajoute :

Les settings pour import/resolver :
"settings": {
  "import/resolver": {
      "node": {
          "extensions": [
              ".js",
              ".jsx",
              ".ts",
              ".tsx"
          ]
      }
  }
},
Zoomez ou dé-zoomez l'exemple avec la commande ci-dessous :

Et la règle dans rules , qui dépend un peu du projet...

import/no-unresolved :
"import/extensions": [1, {
  "ts": "never",
  "tsx": "never",
  "jsx": "never",
  "js": "always"
 }]
Zoomez ou dé-zoomez l'exemple avec la commande ci-dessous :

J'installe maintenant VSCode avec un petit :
sudo snap install code --classic 
et je le lance via code ..

© François-Marie de Jouvencel
fm.de.jouvencel@gmail.com