Los archivos de declaración (.d.ts) son el mecanismo por el que TypeScript conoce los tipos de código que no fue escrito en TypeScript librerías JavaScript, código generado, APIs del navegador sin ejecutar ese código. Entender cómo funcionan es clave para tipar paquetes sin tipos, extender módulos existentes y generar tipos para tus propias librerías.
Ambient declarations: declare
La palabra clave declare le dice a TypeScript que algo existe pero que su implementación está en otro sitio (normalmente en un archivo .js o en el runtime):
// global.d.ts
declare const VERSION: string;
declare function registrar(msg: string): void;
declare class Gestor {
constructor(config: { timeout: number });
conectar(): Promise<void>;
desconectar(): void;
}
// Ahora puedes usar VERSION, registrar() y Gestor en cualquier archivo .ts
// sin importar nada, porque se asume que estarán disponibles en runtime
declare module: tipar paquetes sin tipos
Cuando un paquete de npm no tiene @types/paquete ni tipos propios, TypeScript da un error Could not find a declaration file. La solución rápida es declarar el módulo como any; la correcta es escribir los tipos mínimos que necesitas:
// types/mi-libreria.d.ts (añadir el directorio a typeRoots en tsconfig)
// Solución mínima (acepta cualquier import desde "mi-libreria"):
declare module "mi-libreria";
// Solución correcta (tipos reales de las partes que usas):
declare module "color-hash" {
export default class ColorHash {
constructor(options?: { lightness?: number; saturation?: number });
hex(str: string): string;
rgb(str: string): [number, number, number];
hsl(str: string): [number, number, number];
}
}
Module augmentation: extender módulos existentes
El module augmentation permite añadir propiedades a tipos de un módulo sin modificar sus archivos originales. Es el patrón estándar para extender tipos de librerías:
// express-augment.d.ts
import { Request } from "express";
declare module "express-serve-static-core" {
interface Request {
usuario?: { id: number; email: string; rol: string };
idSesion?: string;
}
}
// Ahora en cualquier middleware:
import { Request, Response, NextFunction } from "express";
function autenticar(req: Request, res: Response, next: NextFunction) {
// TypeScript conoce req.usuario sin error
if (!req.usuario) return res.status(401).json({ error: "No autorizado" });
next();
}
Global augmentation: extender el objeto window
// window-extensions.d.ts
declare global {
interface Window {
analytics: {
track(evento: string, props?: Record<string, unknown>): void;
};
__APP_CONFIG__: {
apiUrl: string;
version: string;
};
}
// Variables globales inyectadas en el HTML:
const __DEV__: boolean;
const __VERSION__: string;
}
// Necesario en archivos .d.ts que usan declare global para que sean módulos:
export {};
Generar .d.ts para tu propia librería
Al compilar con tsc --declaration (o con declaration: true en el tsconfig), TypeScript genera un archivo .d.ts para cada archivo .ts. Con declarationMap: true también genera sourcemaps para que el IDE pueda saltar al código fuente original:
// tsconfig.json de una librería
{
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"declarationDir": "./dist/types",
"emitDeclarationOnly": false,
"outDir": "./dist"
}
}
// package.json de la librería
{
"main": "./dist/index.js",
"types": "./dist/types/index.d.ts",
"exports": {
".": {
"types": "./dist/types/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
}
}
El directorio @types y typeRoots
// Por defecto TypeScript busca tipos en:
// 1. El campo "types" del package.json del paquete
// 2. node_modules/@types/nombre-paquete
// Si usas un directorio propio de declaraciones:
{
"compilerOptions": {
"typeRoots": ["./types", "./node_modules/@types"]
}
}
// O si solo quieres usar ciertos paquetes de @types:
{
"compilerOptions": {
"types": ["node", "jest"] // solo @types/node y @types/jest
}
}
Saber escribir y extender archivos .d.ts es lo que diferencia a quien solo usa TypeScript de quien es capaz de integrar cualquier dependencia JavaScript en un proyecto TypeScript bien tipado. El 90% de los errores de tipado relacionados con librerías se resuelven con un declare module bien escrito o con un @types/paquete que instalar.
Imagen: Pexels / Seraphfim Gallery
