Menu

Modules TypeScript : import, export et import type

Tout fichier TypeScript qui contient un import ou un export de premier niveau est un module. Découvrez les exports nommés et par défaut, import type et export type, comment l'option module choisit entre une sortie module ES et CommonJS, et pourquoi node16 et nodenext veulent des extensions .js dans les imports.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

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);
FormeCe 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: true conserve 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 .ts dans Node (type stripping) retire les annotations de type mais ne regarde pas les autres fichiers. Un import { Point } non marqué reste dans le code, et Node échoue à l'exécution avec SyntaxError: 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é dans node_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 exports de son package.json. Avec la résolution node16, nodenext et bundler, 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 :

FichierFormat de sortie
.ts dans un paquet avec "type": "module"module ES
.ts dans un paquet sans ce champCommonJS
.mtstoujours module ES, émis en .mjs
.ctstoujours 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" (avec module réglé sur esnext, preserve ou commonjs) accepte ./math sans extension, puisque le bundler le résout.
  • rewriteRelativeImportExtensions: true vous laisse écrire ./math.ts, le même chemin qui fonctionne quand Node exécute directement le fichier .ts, et le réécrit en ./math.js dans 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.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER