TypeScript en pratique - 01 - tsconfig en profondeur

Les options du tsconfig.json qui comptent vraiment. strict, paths, moduleResolution, verbatimModuleSyntax, et comment configurer un projet en 2026.

01 - tsconfig en profondeur

Ce que tu vas apprendre

  • Les options strict et ce qu'elles activent individuellement
  • moduleResolution, module, target : le trio qui embrouille tout le monde
  • paths pour les alias d'import
  • verbatimModuleSyntax et les imports de types
  • Un tsconfig recommande pour un projet en 2026

Prerequisites

Avoir lu l'introduction de cette serie.


Le fichier que personne ne lit

La plupart des devs copient un tsconfig d'un projet précédent ou d'un template sans comprendre ce qu'il fait. Ca fonctionne jusqu'au jour ou un import echoue, ou un type se comporte bizarrement, ou la compilation est anormalement lente.

Le tsconfig a plus de 100 options. On ne va pas toutes les couvrir. On va se concentrer sur celles qui ont un impact réel sur ton code.

strict : le mode non-negociable

strict: true active un ensemble d'options de vérification :

json{
  "compilerOptions": {
    "strict": true
  }
}

Ce que ca active :

Option Ce qu'elle fait
strictNullChecks null et undefined ne sont plus assignables a tout
strictFunctionTypes Les paramètres de fonctions sont contravariants
strictBindCallApply Verifie les types de bind, call, apply
strictPropertyInitialization Les propriétés de classes doivent etre initialisees
noImplicitAny Refuse les any implicites
noImplicitThis Refuse this non type
useUnknownInCatchVariables catch (err) a le type unknown
alwaysStrict Ajoute "use strict" partout
exactOptionalPropertyTypes Distingue prop?: T de prop: T | undefined

Mon avis : strict: true dans tout nouveau projet, sans exception. Si tu herites d'un projet sans strict, l'article sur la migration couvre la transition progressive.

module et moduleResolution

Le duo le plus confus du tsconfig. module definit le format des modules en sortie. moduleResolution definit comment TypeScript resout les imports.

module

json{
  "compilerOptions": {
    "module": "ESNext"
  }
}

Les valeurs courantes en 2026 :

  • "ESNext" — modules ES natifs (import/export). Le standard.
  • "NodeNext" — modules Node.js (supporte ESM et CJS selon le package.json)
  • "CommonJS" — l'ancien format Node.js (require/module.exports)

Si tu utilises Bun, Vite, ou un bundler moderne : "ESNext". Si tu fais du Node.js pur avec ESM : "NodeNext".

moduleResolution

json{
  "compilerOptions": {
    "moduleResolution": "bundler"
  }
}

Les valeurs qui comptent :

  • "bundler" — pour les projets avec un bundler (Vite, esbuild, Bun). Supporte les imports sans extension, les package.json exports, et les alias.
  • "nodenext" — pour du Node.js pur. Exige les extensions dans les imports (.js).
  • "node" — l'ancien mode Node.js. Evite-le dans les nouveaux projets.

Si tu utilises Bun ou Vite, "bundler" est le bon choix. Si tu fais du Node.js ESM sans bundler, "nodenext".

target

Le niveau ECMAScript de la sortie :

json{
  "compilerOptions": {
    "target": "ES2022"
  }
}

target déterminé quelles features JavaScript sont conservees et lesquelles sont transpilees. ES2022 est un bon défaut en 2026 : tous les runtimes modernes (Node 18+, Bun, navigateurs recents) le supportent.

Si tu utilises Bun, tu peux mettre "ESNext" — Bun supporte tout.

paths : les alias d'import

Les alias remplacent les chemins relatifs longs par des prefixes :

json{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@api/*": ["src/api/*"],
      "@types/*": ["src/types/*"]
    }
  }
}
typescript// Avant
import { User } from "../../../types/user"

// Apres
import { User } from "@types/user"

Les paths sont resolus par TypeScript a la compilation. Ton bundler (Vite, Bun, esbuild) doit avoir la meme configuration pour que les imports fonctionnent au runtime.

Sur paltemps.fr, le monorepo utilise @api/, @shared/, @blog/ pour les imports entre packages.

verbatimModuleSyntax

Depuis TypeScript 5.0, cette option remplace isolatedModules et importsNotUsedAsValues :

json{
  "compilerOptions": {
    "verbatimModuleSyntax": true
  }
}

Elle force l'utilisation de import type pour les imports qui ne sont que des types :

typescript// ❌ Erreur avec verbatimModuleSyntax
import { User } from "./types"  // si User est seulement un type

// ✅ Correct
import type { User } from "./types"

// ✅ Import mixte
import { createUser, type User } from "./users"

Pourquoi c'est important : les bundlers (esbuild, swc, Bun) ne font pas d'analyse de types. Ils ne savent pas si User est une valeur ou un type. import type leur dit explicitement de supprimer cet import au build.

skipLibCheck

json{
  "compilerOptions": {
    "skipLibCheck": true
  }
}

skipLibCheck: true ignore les erreurs de type dans les fichiers .d.ts de node_modules. Ca accéléré la compilation et évité les erreurs causees par des types incompatibles entre deux libs.

Mon avis : toujours true. Les erreurs dans les types des libs ne sont pas ton problème. Si tu les actives, tu passes du temps a debug des conflits dans @types/node vs @types/express qui n'affectent pas ton code.

noUncheckedIndexedAccess

Une option sous-estimee :

json{
  "compilerOptions": {
    "noUncheckedIndexedAccess": true
  }
}

Elle ajoute | undefined au type de retour des acces par index :

typescriptconst arr = [1, 2, 3]
const first = arr[0] // type: number | undefined (pas number)

const dict: Record<string, number> = { a: 1 }
const val = dict["b"] // type: number | undefined (pas number)

Sans cette option, TypeScript pretend que l'acces par index retourne toujours une valeur. Avec, il te force a vérifier. C'est plus strict mais ca attrape des bugs d'acces hors limites.

Mon tsconfig recommande

Pour un projet Bun/Vite en 2026 :

json{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "skipLibCheck": true,
    "verbatimModuleSyntax": true,
    "noUncheckedIndexedAccess": true,
    "noEmit": true,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "jsx": "react-jsx",
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src/**/*", "types/**/*"],
  "exclude": ["node_modules", "dist"]
}

noEmit: true parce que Bun ou le bundler s'occupe de la transpilation. TypeScript ne sert qu'au type checking.


Résumé

  • strict: true est non-negociable — il active 8+ verifications de sécurité
  • module definit le format de sortie, moduleResolution definit la résolution des imports — "ESNext" + "bundler" pour les projets modernes
  • verbatimModuleSyntax force import type pour les imports de types — requis pour les bundlers
  • skipLibCheck: true accéléré la compilation et évité les conflits dans node_modules
  • noUncheckedIndexedAccess ajoute | undefined aux acces par index — plus strict mais plus sur

Article précédent : 00 - Introduction

Article suivant : 02 - Zod : validation runtime et inference

Sources

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.