Développement Web

Installer TSC et configurer tsconfig.json pas à pas

14 min de lecture

📍 Guide principal de la série : TypeScript moderne — du JS au système de types industriel

Ce tutoriel installe la chaîne de compilation TypeScript et configure un tsconfig.json aligné sur les versions stables actuelles (TypeScript 5.9 et 6.0).

Introduction

Démarrer un projet TypeScript sans s’égarer commence par deux choses : installer le compilateur dans des conditions reproductibles, et générer un tsconfig.json qui ne traîne pas trente options commentées datant d’une autre époque. Ce tutoriel construit cette base étape par étape, en expliquant chaque drapeau plutôt qu’en copiant-collant une configuration trouvée au hasard. À la fin, vous disposerez d’un projet prêt à accueillir du code, avec une sortie compilée propre, des sourcemaps pour le débogage, des déclarations de types pour exposer une bibliothèque, et un mode watch fonctionnel.

Prérequis

  • Node.js 22 LTS ou supérieur (idéalement Node 24 LTS, en service depuis octobre 2025). Vérifiez avec node --version. Node 22 et 24 apportent en plus l’exécution native des .ts via type stripping.
  • npm (livré avec Node) ou un gestionnaire équivalent (pnpm, yarn, bun).
  • Un éditeur capable de lire les diagnostics TypeScript — Visual Studio Code est le choix par défaut, JetBrains WebStorm fonctionne aussi nativement, Vim et Emacs avec un client LSP également.
  • Niveau attendu : confort avec le terminal et avec les notions élémentaires de JavaScript (variables, fonctions, modules).
  • Temps estimé : 35 à 45 minutes la première fois.

Étape 1 — Installer Node.js et vérifier l’environnement

Avant d’installer TypeScript, on vérifie que Node est présent dans une version supportée. Node 22 LTS apporte l’exécution native des .ts via le drapeau initial --experimental-strip-types, et Node 22.18 ainsi que Node 23.6 l’activent par défaut sans drapeau. C’est un confort appréciable pour les scripts utilitaires, même si on continuera à passer par tsc pour les builds de production.

node --version
# Doit afficher v22.x ou supérieur

npm --version
# Idéalement 10.x ou supérieur

Si la version est trop ancienne, on installe Node depuis le site officiel ou via un gestionnaire de versions comme nvm sous Unix ou fnm qui fonctionne aussi sous Windows. Le passage par un gestionnaire de versions est fortement recommandé sur une machine de développement, parce qu’il évite de réinstaller Node à chaque nouveau projet.

Étape 2 — Créer le projet et installer TypeScript

On initialise d’abord un dossier de projet avec un package.json qui servira à lister les dépendances et les scripts de build. L’option -y de npm init accepte les valeurs par défaut sans poser de questions ; on les ajustera plus tard si nécessaire.

mkdir mon-projet-ts
cd mon-projet-ts
npm init -y

# Installation locale de TypeScript en dépendance de développement
npm install --save-dev typescript

# Vérification
npx tsc --version
# Affiche : Version 6.0.3 (ou la version installée)

L’installation locale (--save-dev) plutôt qu’une installation globale est la pratique standard. Chaque projet épingle ainsi sa propre version de TypeScript, ce qui évite les divergences entre machines et permet de monter de version projet par projet. On invoque toujours le compilateur via npx tsc, qui pioche le binaire installé localement dans node_modules/.bin/.

Étape 3 — Générer un tsconfig.json minimal

Le fichier tsconfig.json est le pilote du compilateur. Il indique où trouver les sources, quelles règles appliquer, et comment émettre la sortie. TypeScript 5.9 a refondu la commande tsc --init pour générer un fichier minimal aligné sur les bonnes pratiques actuelles, sans le bruit des centaines de lignes commentées qu’on connaissait avant.

npx tsc --init

Le fichier généré ressemble à ceci :

{
  "compilerOptions": {
    "module": "nodenext",
    "target": "esnext",
    "types": [],
    "sourceMap": true,
    "declaration": true,
    "declarationMap": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "strict": true,
    "jsx": "react-jsx",
    "verbatimModuleSyntax": true,
    "isolatedModules": true,
    "noUncheckedSideEffectImports": true,
    "moduleDetection": "force",
    "skipLibCheck": true
  }
}

Ce gabarit constitue une excellente base de départ. Chaque option a été choisie pour son utilité réelle, et l’on évite ainsi d’oublier un drapeau important. À partir de TypeScript 6.0, plusieurs de ces valeurs deviennent des défauts implicites (strict, target: es2025, module: esnext), mais les expliciter dans le fichier reste préférable pour la lisibilité et pour la portabilité entre versions du compilateur.

Étape 4 — Comprendre chaque option du tsconfig moderne

Avant de modifier ce fichier, on prend le temps de comprendre ce que chaque drapeau fait réellement. Les commentaires ci-dessous résument leur rôle, leur effet sur le code émis, et la raison pour laquelle ils figurent dans le gabarit officiel.

  • module: "nodenext" — sélectionne le système de modules à émettre. nodenext calque le comportement de Node.js dans sa version la plus récente : ESM par défaut, support de l’interop avec CommonJS, respect du champ exports du package.json. Pour un projet qui sera bundlé (Vite, esbuild, Webpack), on peut basculer sur "esnext" + "moduleResolution": "bundler".
  • target: "esnext" — version d’ECMAScript ciblée par le code émis. esnext émet la syntaxe la plus moderne disponible et délègue le polyfilling à l’environnement d’exécution, ce qui est correct pour du Node récent ou pour un bundler qui s’occupera des transformations.
  • types: [] — déclaration explicite des paquets de types à inclure globalement. En laissant un tableau vide, on évite que TypeScript injecte automatiquement les types globaux découverts dans node_modules/@types/, ce qui peut polluer le scope. Pour Node, on ajoutera "types": ["node"] après avoir installé @types/node.
  • sourceMap: true — génère des fichiers .js.map permettant de déboguer la source TypeScript dans les devtools du navigateur ou dans l’IDE. Indispensable pendant le développement, parfois retiré en production pour ne pas exposer le code source.
  • declaration: true — émet les fichiers .d.ts contenant les déclarations de types publiques du module. Essentiel si vous publiez une bibliothèque consommée par d’autres projets TypeScript.
  • declarationMap: true — génère une sourcemap pour les .d.ts, ce qui permet à « aller à la définition » de pointer vers le code source plutôt que vers le fichier de déclaration généré.
  • noUncheckedIndexedAccess: true — quand on indexe un objet avec une clé dynamique, TypeScript ajoute undefined au type retourné, forçant le développeur à gérer le cas de la clé absente. Indispensable sur tout code qui manipule des dictionnaires ou des tableaux à index variable.
  • exactOptionalPropertyTypes: true — distingue une propriété absente d’une propriété explicitement undefined. Sans ce drapeau, { a: undefined } est traité comme équivalent à {}, ce qui masque parfois des bogues sérieux.
  • strict: true — méta-drapeau qui active noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, alwaysStrict et useUnknownInCatchVariables. Activé par défaut depuis TypeScript 6.0.
  • jsx: "react-jsx" — active la transformation JSX en utilisant la runtime automatique de React 17+, qui n’oblige plus à importer React dans chaque fichier. Si vous ne faites pas de React, vous pouvez retirer cette option.
  • verbatimModuleSyntax: true — interdit les imports qui pourraient être effacés sans le mot-clé type. Force à écrire import type { Foo } quand on n’importe que des types, ce qui élimine toute ambiguïté sur ce que le compilateur va réellement émettre.
  • isolatedModules: true — garantit que chaque fichier peut être transpilé indépendamment, condition nécessaire pour utiliser des transpileurs rapides comme esbuild, swc ou Babel à la place de tsc pour les builds.
  • noUncheckedSideEffectImports: true — signale les imports d’effet de bord (import "./styles.css") qui ne correspondent à aucun module connu. Activé par défaut depuis TypeScript 6.0.
  • moduleDetection: "force" — force tous les fichiers à être traités comme des modules ECMAScript, même s’ils n’ont pas d’import ou d’export. Évite la surprise d’un fichier traité comme script global.
  • skipLibCheck: true — saute le type-checking des fichiers .d.ts tiers. Améliore drastiquement la vitesse de compilation sans réelle perte de sûreté, parce que les bibliothèques publient déjà des déclarations validées en amont.

Étape 5 — Définir les dossiers source et sortie

Le gabarit minimal ne fixe pas la structure de répertoires. Pour un projet propre, on ajoute trois options qui isolent les sources des artefacts de compilation, et l’on déclare les chemins inclus et exclus.

{
  "compilerOptions": {
    "module": "nodenext",
    "target": "esnext",
    "rootDir": "./src",
    "outDir": "./dist",
    "strict": true,
    "sourceMap": true,
    "declaration": true,
    "declarationMap": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "verbatimModuleSyntax": true,
    "isolatedModules": true,
    "skipLibCheck": true,
    "types": ["node"]
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

L’option rootDir définit le dossier qui sert de racine pour préserver la structure dans la sortie. outDir indique où le code compilé sera émis. Les champs include et exclude contrôlent l’ensemble des fichiers que tsc doit considérer. Le motif src/**/* couvre récursivement tout le contenu de src, l’exclusion de node_modules et dist évite de re-compiler les dépendances ou les artefacts précédents.

Pour que TypeScript connaisse les types de l’API Node (modules fs, path, process…), on installe les déclarations de types officielles et on les déclare dans types :

npm install --save-dev @types/node

Une fois le paquet installé, on peut importer les modules Node avec le type-checking complet. À partir de TypeScript 6.0, la valeur par défaut de types est [], ce qui oblige à déclarer explicitement les paquets globaux, contrairement au comportement historique qui injectait automatiquement tout ce qu’il trouvait dans @types.

Étape 6 — Compiler un premier fichier

On crée maintenant un fichier source pour vérifier que la chaîne fonctionne de bout en bout. Le code suivant illustre quelques annotations basiques et un appel à une API Node.

mkdir src
touch src/index.ts

Contenu de src/index.ts :

import { readFileSync } from "node:fs";

function loadConfig(path: string): Record<string, unknown> {
  const raw = readFileSync(path, "utf-8");
  return JSON.parse(raw) as Record<string, unknown>;
}

const pkg = loadConfig("./package.json");
console.log("Nom du paquet :", pkg.name);

On compile et on exécute :

npx tsc
node dist/index.js

Si tout s’est bien passé, on voit le nom du paquet apparaître dans la console et un dossier dist/ contenant index.js, index.js.map, index.d.ts et index.d.ts.map. Si tsc émet une erreur du genre Cannot find module 'node:fs', c’est que @types/node n’est pas installé ou que types: ["node"] manque dans le tsconfig — c’est précisément le piège que la valeur par défaut types: [] de TypeScript 6.0 a vocation à rendre visible.

Étape 7 — Ajouter les scripts npm et le mode watch

Pour ne pas retaper npx tsc à chaque modification, on ajoute deux scripts dans package.json. Le mode watch de tsc recompile automatiquement à chaque sauvegarde, ce qui est confortable pendant le développement.

{
  "name": "mon-projet-ts",
  "version": "0.1.0",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "dev": "tsc --watch",
    "start": "node dist/index.js"
  },
  "devDependencies": {
    "typescript": "^6.0.0",
    "@types/node": "^22.0.0"
  }
}

Le champ type: "module" indique à Node que les fichiers .js produits doivent être traités comme des modules ECMAScript, ce qui est cohérent avec module: "nodenext" côté TypeScript. Sans ce champ, Node continuera à traiter les .js comme du CommonJS et tombera sur des erreurs d’import dès que la sortie utilisera la syntaxe import/export.

On lance ensuite :

npm run dev

Le compilateur reste actif, affiche un récapitulatif initial, puis se met en veille jusqu’à la prochaine modification d’un fichier de src/. À chaque sauvegarde, il recompile uniquement les fichiers touchés et leurs dépendants directs.

Étape 8 — Configurer une bibliothèque destinée à la publication

Si l’objectif est de publier un paquet sur npm, on ajoute quelques options qui rendent le paquet correctement consommable par les utilisateurs. On enrichit package.json avec les points d’entrée main, module, types, et idéalement le champ exports qui décrit explicitement les fichiers exposés.

{
  "name": "ma-lib",
  "version": "0.1.0",
  "type": "module",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "default": "./dist/index.js"
    }
  },
  "files": ["dist"],
  "scripts": {
    "build": "tsc",
    "prepublishOnly": "npm run build"
  }
}

Le champ files restreint le contenu publié sur npm à ce qui est utile (le dossier dist), évitant ainsi d’expédier les sources, les tests et la configuration. Le script prepublishOnly garantit qu’une compilation fraîche est faite avant chaque publication, empêchant la livraison accidentelle d’une version obsolète.

Étape 9 — Étendre une base de référence

Les configurations recommandées par l’équipe TypeScript sont publiées sur le dépôt tsconfig/bases. Plutôt que d’écrire son fichier de zéro, on peut hériter d’une base et n’écrire que ce qu’on souhaite surcharger. C’est utile à mesure que le nombre de projets grandit et qu’on veut tenir une politique cohérente.

npm install --save-dev @tsconfig/strictest @tsconfig/node22
{
  "extends": [
    "@tsconfig/strictest/tsconfig.json",
    "@tsconfig/node22/tsconfig.json"
  ],
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

TypeScript 5.0 a introduit le support des héritages multiples, qui sont résolus de gauche à droite. Les options du fichier local surchargent celles des bases. Cette mécanique permet d’avoir une politique « strict » uniforme dans une organisation tout en gardant la liberté d’adapter le couple module/target à chaque projet.

Étape 10 — Vérification finale

On clôture l’installation par une série de contrôles qui confirment que tout fonctionne et qu’aucune option ne bloque silencieusement.

# Vérifier qu'aucune erreur de typage ne traîne
npx tsc --noEmit

# Compiler proprement
npx tsc

# Examiner la sortie
ls dist/

# Exécuter le code compilé
node dist/index.js

L’option --noEmit demande au compilateur de tout vérifier sans rien émettre. Elle est utilisée dans les hooks de pré-commit et dans les pipelines d’intégration continue, parce qu’elle isole la vérification de la production des artefacts. Si tsc --noEmit ne renvoie aucune erreur et que tsc produit bien le dossier dist/ contenant les .js, .js.map, .d.ts et .d.ts.map attendus, l’installation est complète et le projet est prêt à accueillir du code applicatif.

Erreurs fréquentes

Symptôme Cause probable Solution
Cannot find module 'node:fs' @types/node absent ou non déclaré dans types Installer @types/node et ajouter "types": ["node"]
ERR_MODULE_NOT_FOUND à l’exécution Import sans extension dans un projet ESM Toujours importer avec l’extension finale .js dans les sources .ts en mode nodenext
The file is in the program because: Matched by include pattern Le motif include est trop large et capte des fichiers indésirables Affiner include et ajouter les exclusions nécessaires dans exclude
Pas de fichiers .d.ts émis declaration: false ou absent Activer declaration: true et idéalement declarationMap: true
This import is never used sur un type Import normal d’un type avec verbatimModuleSyntax actif Préfixer par type : import type { Foo } from "./foo.js"
Recompilation très lente skipLibCheck désactivé sur un projet à grosses dépendances Activer skipLibCheck: true et envisager le mode incremental
'X' refers to a value, but is being used as a type here Confusion entre type et valeur Utiliser typeof X pour récupérer le type d’une valeur

Tutoriels complémentaires

FAQ

Faut-il installer TypeScript globalement ?
Non. L’installation locale par projet est la pratique standard, parce qu’elle épingle la version utilisée et évite les divergences entre développeurs. On invoque npx tsc ou les scripts npm du projet.

Que choisir entre nodenext et node20 ?
nodenext suit les évolutions futures de Node et peut changer à chaque version mineure de TypeScript. node20 est stable et calque précisément Node 20 LTS. Pour une bibliothèque dont on veut éviter les surprises, node20 est plus prévisible. Pour un service applicatif maintenu activement, nodenext est plus pertinent.

Pourquoi verbatimModuleSyntax impose-t-il import type ?
Parce qu’avec ce drapeau, TypeScript ne tente plus de deviner si un import est utilisé uniquement pour un type. Si vous voulez qu’un import soit effacé à la compilation, vous devez l’écrire explicitement avec type. Ce comportement est requis par les transpileurs rapides comme esbuild et swc qui ne savent pas distinguer.

Faut-il commettre dist/ dans Git ?
Non. Le dossier dist/ est le résultat de la compilation et doit être généré localement ou par la CI. On l’ajoute à .gitignore. La publication sur npm reste possible grâce au script prepublishOnly.

Node peut-il exécuter directement les .ts ?
Oui, depuis Node 23.6 et Node 22.18 le type stripping est activé par défaut. Limitations : pas d’enums, pas de namespaces à valeur, pas de propriétés de paramètre, pas de décorateurs sans transformation. Pour les builds de production on continue à passer par tsc ou par un bundler.

Ressources et références

Service ITSkillsCenter

Site ou application web sur mesure

Conception Pro + Nom de domaine 1 an + Hébergement 1 an + Formation + Support 6 mois. Accès et code livrés. À partir de 350 000 FCFA.

Demander un devis
Publicité