Retour au cours

frontend / typescript

Typage de librairies externes

Leçon 181 exercice

Explication

Ce que vous allez apprendre

  • Installer et utiliser un paquet @types/nom-de-la-librairie depuis DefinitelyTyped
  • Écrire un fichier .d.ts avec declare module pour une librairie sans types
  • Typer l'import de fichiers non-JavaScript (SVG, modules CSS) via une déclaration de module
  • Déclarer une variable globale injectée par un script tiers avec declare global
  • Étendre un type tiers déjà existant grâce à la module augmentation, sans le forker
  • Comprendre pourquoi ces déclarations n'ont aucun effet sur le comportement réel du code

Dans quel contexte ?

Un développeur ajoute au projet une petite librairie de tracking analytics trouvée sur npm, ma-lib-tracking, qui n'a jamais été portée en TypeScript et ne fournit aucun @types associé. À l'import, TypeScript refuse de compiler : Could not find a declaration file for module 'ma-lib-tracking'. Deux solutions s'offrent alors : chercher si un paquet @types/ma-lib-tracking existe malgré tout côté communauté, ou écrire soi-même un petit fichier .d.ts de quelques lignes décrivant la forme de l'API exposée. Cette leçon couvre les deux cas, plus les variantes qu'on rencontre régulièrement (variables globales, imports non-JS, extension d'un type tiers).

Que se passe-t-il quand une librairie n'a pas de types ?

Toutes les bibliothèques JavaScript ne sont pas écrites en TypeScript, et certaines plus anciennes ne fournissent aucune information de type. Pour rester compatible avec l'écosystème JavaScript existant sans perdre les bénéfices de TypeScript, le langage propose plusieurs mécanismes pour décrire, séparément du code source, la forme d'une librairie externe.

DefinitelyTyped, le cas le plus courant

Pour la majorité des librairies populaires, quelqu'un a déjà écrit et publié ces types dans le paquet @types/nom-de-la-librairie : il suffit de l'installer pour bénéficier d'une expérience typée complète, sans écrire une seule ligne de déclaration soi-même.

SituationSolution
Librairie populaire, sans types intégrésnpm install -D @types/nom-de-la-librairie
Petite librairie interne ou obscure, sans @typesÉcrire soi-même un .d.ts avec declare module
Variable globale injectée par un script tiersdeclare global { interface Window { ... } }
Import de fichiers non-JS (SVG, CSS modules)declare module "*.svg" dans un .d.ts
Ajouter un champ à un type tiers déjà typéModule augmentation (declare module "express")

Prérequis

Cette leçon suppose que vous êtes à l'aise avec les interfaces et la syntaxe import/export de TypeScript : elle ne touche à aucune nouvelle syntaxe de typage, seulement à l'endroit où ces déclarations vivent (des fichiers .d.ts séparés du code exécuté).

Écrire ses propres déclarations

Quand aucun @types n'existe, on peut écrire un fichier .d.ts, un fichier de déclaration sans implémentation, qui décrit la forme attendue avec declare module. C'est aussi cette technique qui permet de typer l'import de fichiers non-JavaScript comme les SVG ou les modules CSS, ou de déclarer des variables globales injectées par un script tiers, comme un widget de suivi chargé via une balise script.

Étendre un type existant sans le modifier

La module augmentation (declare module "express") permet d'ajouter des propriétés à un type déjà défini par une librairie tierce — par exemple ajouter un champ utilisateurId à l'objet Request d'Express après qu'un middleware d'authentification l'ait injecté — sans avoir à modifier ni forker le code source de la librairie elle-même.

Piège fréquent

Un fichier .d.ts qui contient un export ou un import devient un module, et declare global doit alors être complété par un export {} explicite en fin de fichier pour rester reconnu comme tel — sans ce détail, TypeScript ignore silencieusement les déclarations globales qu'il contient.

Le fil conducteur

Cette leçon boucle une idée déjà croisée plusieurs fois dans ce cours : les types de TypeScript n'ont aucune existence à l'exécution, ils sont un outil de vérification pure. Décrire une librairie externe, c'est donc uniquement informer le compilateur, jamais modifier son comportement réel.

Commandes & code

Typage de librairies externes

ts
// Cas 1 : une librairie JS sans types fournit un fichier .d.ts séparé (DefinitelyTyped)
// npm install -D @types/lodash
import _ from "lodash";
_.chunk([1, 2, 3, 4], 2); // types fournis par @types/lodash, aucun code à écrire

// Cas 2 : écrire ses propres déclarations pour une lib sans @types
// fichier : src/types/ma-lib-non-typee.d.ts
declare module "ma-lib-non-typee" {
  export function initialiser(config: { cle: string }): void;
  export function envoyer(evenement: string, donnees?: Record<string, unknown>): Promise<void>;
  const version: string;
  export default version;
}

// Utilisation ensuite comme une lib typée normale
import version, { initialiser, envoyer } from "ma-lib-non-typee";

// Cas 3 : déclarer des variables globales injectées par un script externe (CDN, widget tiers)
// fichier : src/types/globals.d.ts
declare global {
  interface Window {
    gtag: (commande: string, id: string, params?: Record<string, unknown>) => void;
    __CONFIG__: {
      apiUrl: string;
      environnement: "dev" | "prod";
    };
  }
}
export {}; // requis pour que ce fichier soit traité comme un module

// Utilisation
window.gtag("event", "achat", { valeur: 42 });
console.log(window.__CONFIG__.apiUrl);

// Cas 4 : typer l'import d'assets non-JS (SVG en tant que composant, CSS modules)
// fichier : src/types/assets.d.ts
declare module "*.svg" {
  const contenu: string;
  export default contenu;
}
declare module "*.module.css" {
  const classes: { readonly [nomClasse: string]: string };
  export default classes;
}

// Cas 5 : élargir un module tiers déjà typé (module augmentation)
import "express";
declare module "express" {
  interface Request {
    utilisateurId?: string; // injectée par un middleware d'authentification
  }
}

// Cas 6 : typer un module CommonJS "brut" en export = / import =
// fichier : legacy-lib.d.ts
declare module "legacy-lib" {
  function run(config: unknown): void;
  export = run;
}
import run = require("legacy-lib");

Résumé

  • declare module "nom" type une librairie sans @types disponible sur npm.
  • declare global étend des interfaces globales (Window, globalThis) — ajouter export {}.
  • La module augmentation (declare module "express") enrichit un type tiers sans le forker.

Exercices pratiques

1 disponible
1

Mission : typer ma-lib-tracking sans types officiels

Objectif : Résoudre une erreur de compilation causée par l'absence de types pour une librairie tierce, en écrivant un fichier de déclaration adapté.

Contexte

Un développeur ajoute au projet ma-lib-tracking, une petite librairie npm sans version TypeScript ni paquet @types associé. À l'import, TypeScript refuse de compiler : Could not find a declaration file for module 'ma-lib-tracking'. Ta mission : résoudre cette erreur en écrivant les déclarations nécessaires.

Résoudre l’exercice →