Un module TypeScript est un fichier qui contient au moins un import ou un export de premier niveau. Tout ce qui y est déclaré est privé au fichier, sauf si vous l'exportez avec export, et les autres fichiers importent avec import ce dont ils ont besoin. La syntaxe est celle des modules ES de JavaScript, plus quelques formes réservées aux types.
Exports nommés et par défaut
Un projet compte beaucoup de fichiers, donc le côté qui importe ressemble à ceci. L'export par défaut s'importe sans accolades et peut prendre n'importe quel nom ; les exports nommés vont entre accolades et gardent leur nom, sauf si vous les renommez avec as.
// main.ts
import describe, { distance, ORIGIN, type Point } from "./math.js";
import { distance as dist } from "./math.js"; // renamed on import
import * as math from "./math.js"; // everything, as one object
const p: Point = { x: 6, y: 8 };
console.log(describe(p), distance(ORIGIN, p), dist(p, p), math.ORIGIN);
| Forme | Ce qu'elle importe |
|---|---|
import { a, b } from "./m.js" | les exports nommés a et b |
import x from "./m.js" | l'export par défaut, sous le nom x |
import * as m from "./m.js" | un objet namespace qui contient tous les exports |
import { a as b } from "./m.js" | a, renommé b dans ce fichier |
import type { T } from "./m.js" | des types uniquement, retirés de la sortie |
import "./setup.js" | exécute le fichier pour ses effets de bord |
export { a } from "./m.js" | réexporte a sans l'importer |
export * from "./m.js" | réexporte tous les exports nommés |
Les réexports permettent à un fichier (souvent index.ts) de rassembler l'API publique d'un dossier. Le comportement des modules en JavaScript pur, comme les liaisons vivantes et la mise en cache des modules, est présenté dans les modules ES.
import type et export type
Les types n'existent pas à l'exécution, donc un import utilisé seulement comme type n'a rien à charger. import type le dit explicitement, et l'instruction disparaît de la sortie JavaScript. Le modificateur type fonctionne aussi sur un seul nom dans un import normal.
import type { User } from "./models.js"; // whole statement erased
import { saveUser, type Settings } from "./api.js"; // only saveUser survives
export type { User }; // re-export a type only
export type UserId = User["id"];
Sans ce mot clé, TypeScript retire quand même les noms dont il voit qu'ils ne sont que des types. Deux réglages rendent le mot clé obligatoire :
verbatimModuleSyntax: trueconserve tout import non marquétype, donc un import de type non marqué provoque l'erreur TS1484 :'Point' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.- Exécuter directement des fichiers
.tsdans Node (type stripping) retire les annotations de type mais ne regarde pas les autres fichiers. Unimport { Point }non marqué reste dans le code, et Node échoue à l'exécution avecSyntaxError: The requested module './math.ts' does not provide an export named 'Point'.
Écrire type sur chaque import réservé aux types fonctionne dans les deux cas : c'est donc l'habitude à prendre.
Cannot find module
Quand le chemin d'un import ne mène ni à un fichier ni à un paquet avec des types, le compilateur s'arrête sur l'erreur TS2307. Exécutez cet exemple pour la voir :
La sortie est index.ts(2,29): error TS2307: Cannot find module './utils.js' or its corresponding type declarations. Les causes habituelles :
- Une faute de frappe dans un chemin relatif, ou un
./manquant (sans lui, le nom est cherché dansnode_modules). - Un paquet JavaScript sans types intégrés : installez
@types/{package}s'il existe, ou écrivez un fichier de déclaration pour lui. - Un sous-chemin que le paquet ne liste pas dans le champ
exportsde sonpackage.json. Avec la résolutionnode16,nodenextetbundler, seuls les points d'entrée listés peuvent être importés.
Sortie en modules ES ou en CommonJS
Vous écrivez toujours import et export. L'option module du tsconfig.json décide du JavaScript produit.
// Source, the same in both cases
import { add } from "./math.js";
console.log(add(1, 2));
// module: nodenext, in a package with "type": "module"
import { add } from "./math.js";
console.log(add(1, 2));
// module: nodenext, in a package without "type": "module"
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const math_js_1 = require("./math.js");
console.log((0, math_js_1.add)(1, 2));
Avec node16, node18, node20 ou nodenext, TypeScript suit les règles de Node pour chaque fichier :
| Fichier | Format de sortie |
|---|---|
.ts dans un paquet avec "type": "module" | module ES |
.ts dans un paquet sans ce champ | CommonJS |
.mts | toujours module ES, émis en .mjs |
.cts | toujours CommonJS, émis en .cjs |
Avec module: "esnext" ou "preserve", la sortie garde import/export, ce qu'attendent les bundlers comme Vite et esbuild. Les différences entre les deux systèmes de modules à l'exécution sont présentées dans CommonJS ou ESM.
Extensions de fichier dans les imports
Sous module: node16 ou nodenext, un fichier module ES doit nommer le fichier que Node chargera réellement, c'est-à-dire le fichier .js compilé. TypeScript fait correspondre ./math.js à math.ts pour la vérification des types.
import { add } from "./math"; // error TS2835 in an ES module file
import { add } from "./math.js"; // correct: the path as it exists after compiling
Le texte de l'erreur est Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'. Did you mean './math.js'? Les fichiers CommonJS avec les mêmes réglages peuvent omettre l'extension, car require essaie lui-même les extensions.
Deux autres configurations changent la règle :
moduleResolution: "bundler"(avecmoduleréglé suresnext,preserveoucommonjs) accepte./mathsans extension, puisque le bundler le résout.rewriteRelativeImportExtensions: truevous laisse écrire./math.ts, le même chemin qui fonctionne quand Node exécute directement le fichier.ts, et le réécrit en./math.jsdans la sortie.
Résolution des modules et paths
Pour un nom nu comme "zod", TypeScript cherche dans node_modules, lit le package.json du paquet (ses champs exports et types) et se rabat sur node_modules/@types/zod. Les noms relatifs (./, ../) sont résolus à partir du fichier qui importe. Un fichier .json peut aussi être importé ; les réglages nécessaires sont sur la page JSON.
paths dans tsconfig.json ajoute des alias pour vos propres dossiers :
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"paths": {
"@lib/*": ["./src/lib/*"]
}
}
}
paths n'affecte que la vérification des types. Le fichier émis contient toujours import { v } from "@lib/util", donc autre chose doit le résoudre à l'exécution : un bundler configuré avec le même alias, ou le champ imports de Node dans package.json (des alias qui commencent par #, comme "#lib/*": "./dist/lib/*"), qui fonctionne sans outil supplémentaire. baseUrl a disparu dans TypeScript 7 (erreur TS5102) ; écrivez les entrées de paths relativement au tsconfig.json, en commençant par ./.
Questions fréquentes
Quelle est la différence entre import et import type en TypeScript ?
import type { User } from "./user.js" ne peut importer que des types, et toute l'instruction disparaît de la sortie JavaScript. Un import simple peut importer des valeurs et des types ; TypeScript retire les noms utilisés uniquement comme types, mais avec verbatimModuleSyntax il exige que vous les marquiez avec type pour que la sortie soit exactement ce que vous avez écrit.
Pourquoi TypeScript veut-il .js dans les chemins d'import ?
Sous module: node16 ou nodenext, un fichier module ES doit importer avec le vrai nom de fichier que Node chargera à l'exécution, et ce fichier est le .js compilé. TypeScript résout ./math.js en math.ts pendant la vérification des types. Omettre l'extension dans un fichier module ES provoque l'erreur TS2835.
Faut-il utiliser export default ou des exports nommés en TypeScript ?
Les deux fonctionnent. Beaucoup d'équipes préfèrent les exports nommés : le nom est le même dans chaque fichier qui l'importe, les éditeurs les importent automatiquement de façon fiable, et un renommage est un refactoring plutôt qu'une recherche. Un export par défaut laisse chaque fichier qui l'importe choisir son propre nom.
L'option paths du tsconfig modifie-t-elle l'import dans la sortie ?
Non. paths indique seulement au vérificateur de types où trouver un module. Le JavaScript émis garde "@lib/util" tel quel, donc un bundler, ou le champ imports de Node dans package.json, doit le résoudre à l'exécution.
Comment corriger « Cannot find module » en TypeScript ?
L'erreur TS2307 signifie que le chemin ne mène pas à un fichier que TypeScript trouve, ou qu'un paquet ne fournit pas de types. Vérifiez le chemin relatif et l'extension, installez le paquet @types/... correspondant s'il n'a pas de types intégrés, ou écrivez un petit fichier de déclaration pour lui.