Aller au contenu

TypeScript : modules et tsconfig

src/clients.ts
export interface Client {
id: number;
nom: string;
}
export function creerClient(nom: string): Client {
return { id: 1, nom };
}
export default class DepotClients {
/* ... */
}
// src/index.ts
import DepotClients, { creerClient, type Client } from './clients.js';
import type { Client as ClientType } from './clients.js';
export type { Client };
Syntaxe Rôle
import type { X } Importe uniquement un type (effacé à la compilation)
import { type X, y } Mélange type et valeur
export type { X } Exporte un type

Avec verbatimModuleSyntax, TypeScript impose import type pour les imports qui ne servent que comme types : le code JavaScript produit correspond exactement au code écrit.

Avec "module": "NodeNext" (Node.js, modules ES), les imports relatifs portent l’extension du fichier produit :

import { creerClient } from './clients.js'; // le fichier source est clients.ts

On écrit .js même si le fichier source est en .ts : c’est le nom du fichier après compilation. Avec un outil de construction (Vite, Astro, Next.js) et "moduleResolution": "Bundler", l’extension est facultative. Pour l’exécution directe par Node.js, certaines versions acceptent (et exigent) l’extension .ts ; l’option allowImportingTsExtensions / rewriteRelativeImportExtensions existe selon la version de TypeScript.

Contexte module moduleResolution
Application ou bibliothèque Node.js NodeNext NodeNext
Application web avec Vite, Astro, Next.js, esbuild ESNext Bundler
Script exécuté avec tsx NodeNext ou ESNext NodeNext ou Bundler

Un mauvais couple produit des erreurs « Cannot find module » alors que le fichier existe.

{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2023"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"verbatimModuleSyntax": true,
"isolatedModules": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"rootDir": "src",
"outDir": "dist",
"sourceMap": true,
"declaration": true,
"baseUrl": ".",
"paths": { "@/*": ["src/*"] }
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
Option Rôle
target Version de JavaScript produite
lib API disponibles (DOM pour le navigateur, ES2023 pour Node.js)
strict strictNullChecks, noImplicitAny, strictFunctionTypes, etc.
noUncheckedIndexedAccess Indexation : T | undefined
exactOptionalPropertyTypes x?: number n’accepte pas undefined explicitement
noImplicitOverride Impose le mot-clé override (voir POO : encapsulation et héritage)
noUnusedLocals, noUnusedParameters Signale le code inutilisé
isolatedModules Garantit que chaque fichier peut être transformé seul (outils de construction)
forceConsistentCasingInFileNames Évite les imports dont la casse diffère (Linux est sensible à la casse)
declaration Produit des fichiers .d.ts (bibliothèques)
paths Alias d’import (à doubler dans l’outil de construction)

strict: true dès le début : l’activer sur un projet existant coûte beaucoup plus cher.

tsconfig.build.json
// tsconfig.json (base)
{ "compilerOptions": { "strict": true, "target": "ES2022" } }
{ "extends": "./tsconfig.json", "include": ["src"], "exclude": ["**/*.test.ts"] }
Fenêtre de terminal
npx tsc -p tsconfig.build.json

extends hérite d’une configuration ; des préréglages existent (@tsconfig/node24, @tsconfig/strictest, @tsconfig/vite-react).

Un fichier .d.ts décrit les types d’un code JavaScript sans les implémenter.

src/types/global.d.ts
declare module '*.svg' {
const chemin: string;
export default chemin;
}
interface Window {
analytics?: { suivre(evenement: string): void };
}
declare const VERSION_APP: string;
Usage Exemple
Importer des fichiers non JavaScript declare module '*.css'
Étendre un type global interface Window { … }
Typer une variable injectée par l’outil de construction declare const VERSION_APP: string
Cas Solution
Paquet qui fournit ses types (types dans son package.json) Rien à faire
Paquet sans types npm install -D @types/nom-du-paquet (projet DefinitelyTyped)
Aucun type disponible declare module 'nom'; dans un .d.ts (type any), ou écrire ses propres déclarations
Fenêtre de terminal
npm install -D @types/node @types/express
npm ls @types/node

Les types d’un paquet doivent correspondre à sa version majeure (@types/node à la version de Node.js utilisée).

dist/
*.tsbuildinfo

Ne pas versionner le code compilé ni les fichiers d’information de compilation incrémentale (incremental: true produit .tsbuildinfo).

{
"compilerOptions": {
"allowJs": true,
"checkJs": false,
"outDir": "dist"
},
"include": ["src"]
}

allowJs accepte les fichiers .js dans le projet ; checkJs les vérifie aussi (avec des commentaires // @ts-check fichier par fichier au départ). C’est la base d’une migration progressive (voir Dépannage et migration).

{
"files": [],
"references": [{ "path": "./paquets/api" }, { "path": "./paquets/web" }]
}
Fenêtre de terminal
npx tsc --build

composite: true dans chaque sous-projet permet des compilations incrémentales. Utile à partir de plusieurs paquets liés.

Fenêtre de terminal
npx tsc --showConfig # configuration résolue (après extends)
npx tsc --listFiles | head # fichiers inclus
npx tsc --explainFiles | head -30 # pourquoi chaque fichier est inclus
npx tsc --noEmit --extendedDiagnostics # temps de compilation
Symptôme Piste
Cannot find module './x' or its corresponding type declarations Extension manquante (./x.js) avec NodeNext, ou moduleResolution inadapté
An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled Imports en .ts : option dédiée selon la version, ou écrire .js
Cannot find module '@/x' Alias paths non répété dans l’outil de construction (Vite : resolve.alias)
'X' is a type and must be imported using a type-only import verbatimModuleSyntax : import type
Could not find a declaration file for module 'x' @types/x absent, ou declare module 'x'
Fichier non pris en compte Absent de include, présent dans exclude, extension non reconnue
error TS6059: File is not under 'rootDir' Fichier hors de rootDir : ajuster rootDir ou include
La casse des imports échoue seulement sous Linux forceConsistentCasingInFileNames, corriger les noms

Ubuntu 24.04 et 26.04 : différences à connaître

Section intitulée « Ubuntu 24.04 et 26.04 : différences à connaître »

Sous Linux, les noms de fichiers sont sensibles à la casse : import './Client' échoue si le fichier s’appelle client.ts. Activer forceConsistentCasingInFileNames.