Introduction et installation#
Qu’est-ce que TypeScript ?#
TypeScript est un langage de programmation open source développé et maintenu par Microsoft. Il a été conçu par Anders Hejlsberg — le créateur de C# et de Delphi — et rendu public pour la première fois en octobre 2012. Sa proposition fondamentale tient en une phrase : TypeScript est un sur-ensemble de JavaScript (superset) qui ajoute un système de types statiques optionnels à la syntaxe du langage.
Concrètement, tout code JavaScript valide est aussi du TypeScript valide. On peut renommer un fichier .js en .ts sans modifier une seule ligne et le compilateur TypeScript l’acceptera. Cette compatibilité ascendante n’est pas un détail technique : c’est un choix philosophique délibéré qui a permis à TypeScript d’être adopté progressivement dans des projets existants, sans nécessiter une réécriture complète.
Le mécanisme central de TypeScript est la transpilation : le compilateur tsc (TypeScript Compiler) lit les fichiers sources .ts, vérifie les types et produit des fichiers .js ordinaires que les moteurs JavaScript (Node.js, les navigateurs) peuvent exécuter directement. Il ne s’agit donc pas d’un nouveau moteur d’exécution, mais d’une étape de vérification et de traduction qui intervient au moment du développement.
Définition 1 (Sur-ensemble de JavaScript)
TypeScript est un sur-ensemble syntaxique de JavaScript : tout programme JavaScript valide est un programme TypeScript valide. TypeScript étend JavaScript en ajoutant des annotations de types, des interfaces, des génériques et d’autres constructions statiques qui sont effacées lors de la transpilation. Le code TypeScript ne s’exécute jamais directement : il est d’abord converti en JavaScript par le compilateur tsc.
La philosophie du typage graduel#
L’une des décisions les plus importantes dans la conception de TypeScript est le principe du typage graduel (gradual typing). Contrairement à des langages à typage statique strict comme Java ou C++, TypeScript ne vous oblige pas à annoter chaque variable, chaque paramètre de fonction et chaque valeur de retour. Le compilateur est capable d’inférer de nombreux types automatiquement à partir du contexte, et il tolère, à travers le type spécial any, des zones d’incertitude explicites où la vérification est suspendue.
Cette flexibilité est intentionnelle. Elle permet d’adopter TypeScript de façon incrémentale : on peut commencer avec un fichier JavaScript existant, ajouter quelques annotations critiques, puis renforcer progressivement la rigueur du typage au fur et à mesure que la confiance dans le système de types grandit. Le curseur va du typage très souple (activé avec "strict": false) au typage rigoureux qui débusque le plus grand nombre d’erreurs potentielles (activé avec "strict": true).
Pourquoi TypeScript ?#
La question mérite d’être posée. JavaScript est un langage complet, mature et universel. Pourquoi ajouter une couche de complexité supplémentaire ? Les raisons sont multiples et se renforcent mutuellement.
Détection d’erreurs à la compilation#
La raison la plus souvent citée est la capacité de TypeScript à détecter des catégories entières d’erreurs avant l’exécution. En JavaScript, une faute de frappe dans un nom de propriété, un appel de fonction avec le mauvais nombre d’arguments ou une confusion entre une chaîne de caractères et un nombre n’est visible qu’au moment où le code s’exécute — parfois en production, parfois dans un chemin de code rarement emprunté. TypeScript signale ces erreurs dès la phase d’édition, dans votre éditeur, avant même que vous n’ayez sauvegardé le fichier.
// JavaScript : aucune erreur jusqu'à l'exécution
function saluer(nom) {
return "Bonjour, " + nom.toUppercase(); // 'toUppercase' n'existe pas
}
// TypeScript : erreur immédiate dans l'éditeur
function saluer(nom: string): string {
return "Bonjour, " + nom.toUppercase();
// Erreur TS : Property 'toUppercase' does not exist on type 'string'.
// Did you mean 'toUpperCase'?
}
Autocomplétion et outillage#
Un système de types précis permet aux éditeurs de code de proposer une autocomplétion contextuelle fiable. Lorsque vous tapez utilisateur., l’éditeur sait exactement quelles propriétés et quelles méthodes sont disponibles sur l’objet utilisateur parce que son type a été déclaré ou inféré. Cette intelligence du code réduit les aller-retours vers la documentation et accélère considérablement le développement.
Visual Studio Code, l’éditeur de Microsoft, offre un support TypeScript de premier ordre nativement. Des éditeurs comme WebStorm, Neovim avec des extensions LSP, ou encore Zed bénéficient également d’une intégration complète.
Documentation vivante#
Les annotations de types constituent une forme de documentation intégrée au code, qui ne peut pas être obsolète puisqu’elle est vérifiée par le compilateur. La signature d’une fonction TypeScript dit immédiatement ce qu’elle attend en entrée et ce qu’elle produit en sortie, sans qu’il soit nécessaire de lire son corps ou de consulter un commentaire JSDoc qui pourrait ne plus être à jour.
// La signature dit tout : on donne un tableau de nombres, on obtient un nombre
function moyenne(valeurs: number[]): number {
return valeurs.reduce((acc, v) => acc + v, 0) / valeurs.length;
}
Adoption progressive et ecosystème#
TypeScript est aujourd’hui adopté par l’immense majorité des grands projets JavaScript : Angular l’utilise nativement, React l’encourage, Vue, NestJS, Deno et de nombreuses autres bibliothèques publient leurs propres définitions de types. Le dépôt DefinitelyTyped (@types/...) fournit des définitions de types pour des milliers de bibliothèques JavaScript qui ne les embarquent pas directement.
L’adoption de TypeScript dans l’industrie a connu une croissance remarquable depuis sa création. Des enquêtes annuelles menées auprès des développeurs (Stack Overflow Developer Survey, State of JS) le classent régulièrement parmi les langages les plus appréciés et les plus utilisés dans le développement web moderne.
Installation et outils#
Prérequis : Node.js#
TypeScript s’installe via npm, le gestionnaire de paquets de Node.js. Il est donc nécessaire d’avoir Node.js installé sur sa machine. La version LTS (Long Term Support) la plus récente est recommandée. On peut vérifier si Node.js est disponible avec :
node --version
npm --version
Si Node.js n’est pas installé, on peut le télécharger depuis nodejs.org ou utiliser un gestionnaire de versions comme nvm (Node Version Manager), qui permet de basculer facilement entre plusieurs versions de Node.js.
Installer le compilateur TypeScript#
Le compilateur TypeScript s’installe globalement (pour toute la machine) ou localement (dans un projet spécifique). L’installation globale permet d’utiliser tsc directement depuis n’importe quel répertoire :
# Installation globale
npm install -g typescript
# Vérifier la version installée
tsc --version
# Exemple de sortie : Version 5.4.5
Pour un projet spécifique, on préfère souvent l’installation locale, ce qui garantit que tous les membres de l’équipe utilisent exactement la même version :
# Installation locale dans un projet
npm install --save-dev typescript
# Utilisation via npx
npx tsc --version
ts-node et tsx#
Pour des scripts ou des expérimentations rapides, recompiler manuellement à chaque modification est fastidieux. Deux outils permettent d’exécuter du code TypeScript directement, sans étape de compilation explicite.
ts-node est un environnement d’exécution TypeScript pour Node.js. Il compile à la volée et exécute le résultat :
npm install -g ts-node
# Exécuter un fichier TypeScript directement
ts-node mon_script.ts
# Mode REPL interactif
ts-node
tsx est une alternative plus moderne à ts-node, basée sur esbuild. Elle est significativement plus rapide pour les projets de grande taille car elle utilise une transpilation légère sans vérification de types complète :
npm install -g tsx
# Exécuter un fichier TypeScript
tsx mon_script.ts
# Mode watch : relance à chaque modification
tsx watch mon_script.ts
Remarque 1
tsx et ts-node n’effectuent pas de vérification de types complète par défaut : ils se contentent de transpiler le TypeScript en JavaScript. Pour la vérification complète des types, il faut toujours passer par tsc. En pratique, on utilise tsx ou ts-node pour l’exécution rapide pendant le développement, et tsc dans le pipeline d’intégration continue pour garantir la validité du code.
Configuration : tsconfig.json#
Le comportement du compilateur TypeScript est contrôlé par un fichier de configuration nommé tsconfig.json, placé à la racine du projet. On le génère avec la commande :
tsc --init
Cette commande crée un fichier tsconfig.json avec toutes les options disponibles commentées. Un fichier minimal mais bien configuré ressemble à ceci :
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"outDir": "./dist",
"rootDir": "./src",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Options fondamentales#
target détermine la version d’ECMAScript vers laquelle le code est transpilé. Une valeur élevée comme ES2022 conserve les fonctionnalités modernes telles que les classes, async/await, les opérateurs optionnels. Si l’on cible de vieux navigateurs, on choisira ES5 ou ES6.
module contrôle le système de modules utilisé dans le code généré. NodeNext utilise le système de modules natif de Node.js (ESM ou CommonJS selon l’extension du fichier). CommonJS génère du code avec require() et module.exports.
outDir et rootDir définissent respectivement le répertoire de sortie des fichiers .js compilés et le répertoire racine des sources .ts. Cette séparation permet de garder un projet propre.
strict est l’option la plus importante pour la qualité du code. Il s’agit d’un raccourci qui active simultanément un ensemble de vérifications rigoureuses.
```{prf:definition} Mode strict (strict: true)
:label: definition-01-02
L’option "strict": true active les sous-options suivantes :
strictNullChecks:nulletundefinedne sont plus assignables à n’importe quel type. Une variable de typestringne peut pas contenirnullsans déclaration explicite (string | null).strictFunctionTypes: les types de fonctions sont vérifiés de façon contravariante pour les paramètres.strictBindCallApply:bind,calletapplysont vérifiés avec les types corrects.strictPropertyInitialization: toutes les propriétés de classe doivent être initialisées dans le constructeur.noImplicitAny: le compilateur signale une erreur lorsqu’il ne peut pas inférer le type et devrait recourir àany.noImplicitThis:thisdoit être annoté explicitement quand son type est ambigu.alwaysStrict: génère"use strict"dans tous les fichiers de sortie.
```{prf:remark}
:label: remark-01-02
Il est fortement recommandé d'activer `"strict": true` dès le début d'un projet. Ajouter ce mode sur un projet existant de grande taille peut générer des centaines d'erreurs à corriger — c'est l'une des raisons pour lesquelles certains projets héritent de configurations laxistes. Pour les nouveaux projets, le mode strict est la norme de l'industrie et capture les bugs les plus courants avant qu'ils n'atteignent la production.
Premier programme TypeScript#
Créons notre premier programme TypeScript. On commence par créer la structure minimale d’un projet :
mkdir mon-projet-ts
cd mon-projet-ts
npm init -y
npm install --save-dev typescript
npx tsc --init
mkdir src dist
On crée ensuite le fichier src/bonjour.ts :
// src/bonjour.ts
function saluer(prenom: string, age: number): string {
return `Bonjour, ${prenom} ! Vous avez ${age} ans.`;
}
const nom: string = "Alice";
const anneeNaissance: number = 1990;
const anneeActuelle: number = new Date().getFullYear();
const message: string = saluer(nom, anneeActuelle - anneeNaissance);
console.log(message);
Voici le même programme en JavaScript pur, pour comparaison :
// Équivalent JavaScript
function saluer(prenom, age) {
return `Bonjour, ${prenom} ! Vous avez ${age} ans.`;
}
const nom = "Alice";
const anneeNaissance = 1990;
const anneeActuelle = new Date().getFullYear();
const message = saluer(nom, anneeActuelle - anneeNaissance);
console.log(message);
La structure est identique. La seule différence visible est l’ajout des annotations : string et : number. C’est la nature non intrusive de TypeScript : il enrichit sans bouleverser.
On compile le fichier avec :
npx tsc
Le compilateur lit tsconfig.json, transpile src/bonjour.ts et génère dist/bonjour.js. On exécute ensuite le fichier JavaScript produit :
node dist/bonjour.js
# Sortie : Bonjour, Alice ! Vous avez 35 ans.
Ce que TypeScript attrape immédiatement#
Essayons maintenant d’introduire volontairement une erreur :
// Cette ligne provoque une erreur de compilation
const message = saluer(42, "Alice"); // Arguments inversés !
// Erreur TS2345 : Argument of type 'number' is not assignable to
// parameter of type 'string'.
TypeScript refuse de compiler ce code. En JavaScript, la fonction s’exécuterait sans erreur mais produirait un résultat incohérent ("Bonjour, 42 ! Vous avez NaN ans."). C’est précisément ce genre d’erreur silencieuse — difficile à détecter par les tests — que TypeScript élimine.
Exemple 1 (Comparaison JavaScript / TypeScript sur une erreur courante)
Considérons une fonction qui calcule le prix total d’une commande.
En JavaScript :
function calculerTotal(articles, remise) {
return articles.reduce((s, a) => s + a.prix, 0) * (1 - remise);
}
// Appel correct
calculerTotal([{ prix: 10 }, { prix: 25 }], 0.1); // 31.5
// Appel erroné — JavaScript n'avertit pas
calculerTotal([{ prix: 10 }, { Prix: 25 }], "10%"); // NaN
En TypeScript avec des interfaces :
interface Article {
readonly prix: number;
nom: string;
}
function calculerTotal(articles: Article[], remise: number): number {
return articles.reduce((s, a) => s + a.prix, 0) * (1 - remise);
}
// Erreur immédiate : 'Prix' n'existe pas sur le type 'Article'
calculerTotal([{ prix: 10 }, { Prix: 25, nom: "stylo" }], 0.1);
// Erreur immédiate : '"10%"' n'est pas assignable au type 'number'
calculerTotal([{ prix: 10, nom: "crayon" }], "10%");
### Visualisation : le pipeline de compilation TypeScript
```{code-cell} python
:tags: [hide-input]
fig, ax = plt.subplots(figsize=(15, 6))
ax.set_xlim(-0.5, 15)
ax.set_ylim(-1.5, 5.5)
ax.axis('off')
ax.set_title('Pipeline de compilation TypeScript', fontsize=16,
fontweight='bold', pad=20)
# Palette de couleurs
c_ts = '#3178c6' # bleu TypeScript
c_tsc = '#e67e22' # orange compilateur
c_js = '#f1c40f' # jaune JavaScript
c_node = '#27ae60' # vert Node.js
c_browser = '#8e44ad' # violet navigateur
c_arrow = '#2c3e50'
bw = 2.4 # box width
bh = 2.2 # box height
by = 1.5 # box y
gap = 1.3 # espace entre les boîtes
positions = {
'ts': 0.5,
'tsc': 0.5 + bw + gap,
'js': 0.5 + 2 * (bw + gap),
'node': 0.5 + 3 * (bw + gap),
'browser': 0.5 + 3 * (bw + gap) + bw + 0.8,
}
def draw_box(ax, x, y, w, h, color, label, sublabel, icon=''):
box = patches.FancyBboxPatch(
(x, y), w, h,
boxstyle="round,pad=0.12", linewidth=2.5,
edgecolor=color, facecolor=color, alpha=0.18
)
ax.add_patch(box)
border = patches.FancyBboxPatch(
(x, y), w, h,
boxstyle="round,pad=0.12", linewidth=2.5,
edgecolor=color, facecolor='none'
)
ax.add_patch(border)
ax.text(x + w / 2, y + h - 0.55,
icon + label, ha='center', va='center',
fontsize=12, fontweight='bold', color=color)
ax.text(x + w / 2, y + 0.55,
sublabel, ha='center', va='center',
fontsize=8.5, color='#555555', style='italic',
wrap=True)
# Boîte 1 : fichiers .ts
draw_box(ax, positions['ts'], by, bw, bh, c_ts,
'Fichiers .ts', 'Code TypeScript\nannoté', '📄 ')
# Boîte 2 : tsc
draw_box(ax, positions['tsc'], by, bw, bh, c_tsc,
'tsc', 'Vérification des types\n+ transpilation', '⚙️ ')
# Boîte 3 : fichiers .js
draw_box(ax, positions['js'], by, bw, bh, c_js,
'Fichiers .js', 'JavaScript pur\n(types effacés)', '📄 ')
# Boîtes 4a et 4b : Node.js et Navigateur (split)
bh2 = (bh - 0.3) / 2
draw_box(ax, positions['node'], by + bh2 + 0.3, bw * 0.85, bh2,
c_node, 'Node.js', 'Serveur / scripts', '')
draw_box(ax, positions['node'], by, bw * 0.85, bh2,
c_browser, 'Navigateur', 'Application web', '')
# Flèches
arrow_y = by + bh / 2
def draw_arrow(ax, x1, x2, y, color, label):
ax.annotate('',
xy=(x2, y),
xytext=(x1, y),
arrowprops=dict(arrowstyle='->', color=color, lw=2.5))
ax.text((x1 + x2) / 2, y + 0.42,
label, ha='center', va='center',
fontsize=9, fontweight='bold', color=color,
fontfamily='monospace',
bbox=dict(boxstyle='round,pad=0.18', facecolor='white',
edgecolor=color, alpha=0.92))
# .ts → tsc
draw_arrow(ax,
positions['ts'] + bw + 0.08,
positions['tsc'] - 0.08,
arrow_y, c_arrow, 'tsc')
# tsc → .js
draw_arrow(ax,
positions['tsc'] + bw + 0.08,
positions['js'] - 0.08,
arrow_y, c_arrow, '.js')
# .js → Node.js / Navigateur
x_split_start = positions['js'] + bw + 0.08
x_split_end = positions['node'] - 0.08
y_node = by + bh2 + 0.3 + bh2 / 2
y_browser = by + bh2 / 2
# Ligne verticale de distribution
mid_x = (x_split_start + x_split_end) / 2
ax.plot([x_split_start, mid_x], [arrow_y, arrow_y],
color=c_arrow, lw=2.5)
ax.plot([mid_x, mid_x], [y_browser, y_node],
color=c_arrow, lw=2.5)
ax.annotate('',
xy=(x_split_end, y_node),
xytext=(mid_x, y_node),
arrowprops=dict(arrowstyle='->', color=c_node, lw=2.5))
ax.annotate('',
xy=(x_split_end, y_browser),
xytext=(mid_x, y_browser),
arrowprops=dict(arrowstyle='->', color=c_browser, lw=2.5))
ax.text(mid_x + 0.6, arrow_y + 0.42,
'Exécution', ha='center', va='center',
fontsize=9, fontweight='bold', color=c_arrow,
bbox=dict(boxstyle='round,pad=0.18', facecolor='white',
edgecolor=c_arrow, alpha=0.92))
# Légende : erreur bloquée par tsc
ax.annotate('',
xy=(positions['tsc'] + bw / 2, by - 0.2),
xytext=(positions['tsc'] + bw / 2, by - 1.0),
arrowprops=dict(arrowstyle='->', color='#c0392b', lw=2,
linestyle='dashed'))
ax.text(positions['tsc'] + bw / 2, by - 1.2,
'✗ Erreur de types → compilation refusée',
ha='center', va='center',
fontsize=9, color='#c0392b', fontweight='bold',
bbox=dict(boxstyle='round,pad=0.18', facecolor='#fdecea',
edgecolor='#c0392b', alpha=0.95))
plt.tight_layout()
plt.show()
```
## Résumé
Dans ce chapitre, nous avons posé les fondements de TypeScript :
- TypeScript est un **sur-ensemble de JavaScript** créé par Microsoft en 2012, conçu par Anders Hejlsberg. Tout code JavaScript valide est du TypeScript valide. TypeScript ajoute des annotations de types qui sont **effacées à la compilation**, produisant du JavaScript ordinaire.
- La **philosophie du typage graduel** permet d'adopter TypeScript progressivement, du fichier JavaScript sans annotation jusqu'au mode strict le plus rigoureux.
- Les bénéfices concrets sont la **détection d'erreurs à la compilation**, une **autocomplétion précise**, une **documentation vivante** intégrée au code et un meilleur refactoring.
- L'**installation** passe par `npm install -g typescript`, avec `tsc` comme compilateur principal et `tsx` ou `ts-node` pour l'exécution directe en développement.
- Le fichier **`tsconfig.json`** contrôle toutes les options du compilateur. L'option `"strict": true` est la plus importante : elle active `strictNullChecks`, `noImplicitAny` et plusieurs autres vérifications essentielles.
- Le **pipeline de compilation** est linéaire : fichiers `.ts` → `tsc` → fichiers `.js` → Node.js ou navigateur.
Dans le chapitre suivant, nous plongerons dans le cœur de TypeScript : le système de types. Nous verrons les types primitifs, l'inférence automatique, les types spéciaux `any`, `unknown` et `never`, ainsi que les mécanismes de *narrowing* qui permettent à TypeScript de raffiner le type d'une valeur au fil de l'exécution.