En esta página
Módulos y namespaces
Módulos ES en TypeScript
TypeScript trabaja con el sistema de módulos ES (import/export) de forma nativa. Cada archivo con al menos un import o export de nivel superior es un módulo; el resto son scripts globales (algo que debes evitar en proyectos modernos).
Exportaciones e importaciones básicas
// src/matematicas.ts
export function sumar(a: number, b: number): number {
return a + b;
}
export function multiplicar(a: number, b: number): number {
return a * b;
}
export const PI = 3.14159265358979;
// Exportación por defecto (úsala con moderación)
export default function restar(a: number, b: number): number {
return a - b;
}// src/main.ts
import restar, { sumar, multiplicar, PI } from "./matematicas";
// O renombrando:
import { sumar as add } from "./matematicas";
// O importando todo el namespace:
import * as Mat from "./matematicas";
console.log(sumar(2, 3)); // 5
console.log(Mat.multiplicar(4, 5)); // 20import type: importaciones de solo tipo
Cuando solo necesitas un tipo (interfaz, type alias, enum de tipos), usa import type. TypeScript garantiza que esa importación se elimina completamente en el JavaScript emitido:
// Importación mixta: valor y tipo
import { ServicioUsuarios, type Usuario } from "./servicios/usuarios";
// Importación solo de tipos
import type { Tarea, EstadoTarea, CrearTareaDto } from "./modelos/tarea";
// En archivos .d.ts siempre usa import type para referencias a tipos de otras librerías
import type { Request, Response } from "express";También puedes usar export type para re-exportar solo tipos:
// Correcto: re-exportar un tipo desde un barrel
export type { Usuario, Perfil } from "./modelos";
// Incorrecto en archivos que serán importados sin bundler:
// export { Usuario } from "./modelos"; // puede generar código de runtime si no hay tree-shakingResolución de módulos: node16 y bundler
La configuración moduleResolution en tsconfig.json determina cómo TypeScript busca los archivos importados:
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler"
}
}| Modo | Cuándo usarlo |
|---|---|
bundler |
Proyectos con Vite, Webpack, esbuild, Rollup |
node16 / nodenext |
APIs de Node.js con ESM nativo |
node (legacy) |
CommonJS; evitar en proyectos nuevos |
Con moduleResolution: "node16", las importaciones de archivos .ts deben incluir la extensión .js (la que tendrá el archivo compilado):
// Con moduleResolution: "node16"
import { calcular } from "./calculo.js"; // ← extensión .js aunque el fuente sea .tsCon "bundler", el bundler resuelve extensiones automáticamente y no necesitas incluirlas.
Path aliases
Los path aliases evitan las rutas relativas largas como ../../../servicios/usuarios. Se configuran en tsconfig.json y en la configuración del bundler:
// tsconfig.json
{
"compilerOptions": {
"baseUrl": "src",
"paths": {
"@core/*": ["core/*"],
"@features/*": ["features/*"],
"@shared/*": ["shared/*"],
"@models/*": ["models/*"],
"@utils/*": ["utils/*"]
}
}
}// Antes (rutas relativas):
import { ServicioAuth } from "../../../core/auth/servicio-auth";
import type { Usuario } from "../../models/usuario";
// Después (con aliases):
import { ServicioAuth } from "@core/auth/servicio-auth";
import type { Usuario } from "@models/usuario";Nota: si usas Vite, también debes configurar
resolve.aliasenvite.config.ts. Si usas ts-node, necesitastsconfig-paths.
Barrel files (index.ts)
Un barrel file (archivo barril) re-exporta múltiples módulos desde un punto de entrada único. Facilita importaciones limpias sin exponer la estructura interna de un módulo:
src/
features/
usuarios/
repositorio-usuarios.ts
servicio-usuarios.ts
tipos-usuario.ts
index.ts ← barrel
tareas/
repositorio-tareas.ts
servicio-tareas.ts
tipos-tarea.ts
index.ts ← barrel// src/features/usuarios/index.ts
export { RepositorioUsuarios } from "./repositorio-usuarios";
export { ServicioUsuarios } from "./servicio-usuarios";
export type { Usuario, CrearUsuarioDto } from "./tipos-usuario";// Consumidor — importación limpia y sin rutas internas expuestas
import { ServicioUsuarios } from "@features/usuarios";
import type { Usuario } from "@features/usuarios";Namespaces (legacy)
Los namespaces son una característica propia de TypeScript para agrupar tipos y valores bajo un mismo nombre. En proyectos modernos con ES modules, los namespaces son prácticamente innecesarios, pero los encontrarás en código legado y en archivos .d.ts:
namespace Validacion {
export interface Regla {
mensaje: string;
validar(valor: string): boolean;
}
export function requerido(nombre: string): Regla {
return {
mensaje: `${nombre} es obligatorio`,
validar: (v) => v.trim().length > 0,
};
}
export function longitudMinima(min: number): Regla {
return {
mensaje: `Mínimo ${min} caracteres`,
validar: (v) => v.length >= min,
};
}
}
const reglas: Validacion.Regla[] = [
Validacion.requerido("Nombre"),
Validacion.longitudMinima(3),
];Para código nuevo, prefiere módulos ES con exportaciones nombradas: son más estándar, tienen mejor soporte en herramientas y permiten tree-shaking.
Archivos de declaración (.d.ts)
Los archivos .d.ts contienen solo declaraciones de tipos, sin código JavaScript. Son la forma en que TypeScript conoce los tipos de librerías escritas en JavaScript puro:
// types/mi-libreria.d.ts
declare module "mi-libreria-sin-tipos" {
export interface Opciones {
url: string;
timeout?: number;
reintentos?: number;
}
export function peticion(opciones: Opciones): Promise<unknown>;
export function cancelar(id: string): void;
export const version: string;
}Para extender tipos existentes de otras librerías (module augmentation):
// types/express-extension.d.ts
import type { Usuario } from "@models/usuario";
declare global {
namespace Express {
interface Request {
usuario?: Usuario;
}
}
}Módulos ambientes con `declare module`
Si instalas una librería que no tiene tipos ni en el paquete ni en @types, puedes crear un módulo ambiente para silenciar el error y añadir tipos básicos:
// types/legacy-util.d.ts
declare module "legacy-util" {
export function formatear(valor: string, opciones?: { mayusculas?: boolean }): string;
export function limpiar(valor: string): string;
}Y en tsconfig.json:
{
"compilerOptions": {
"typeRoots": ["./node_modules/@types", "./types"]
}
}Ejemplo completo: proyecto con barrel exports y path aliases
// ─────────────────────────────────────────
// src/models/tipos-comunes.ts
// ─────────────────────────────────────────
export type ID = string;
export interface EntidadBase {
id: ID;
creadaEn: Date;
actualizadaEn: Date;
}
// ─────────────────────────────────────────
// src/models/usuario.ts
// ─────────────────────────────────────────
import type { EntidadBase } from "./tipos-comunes";
export interface Usuario extends EntidadBase {
nombre: string;
email: string;
rol: "admin" | "editor" | "lector";
}
export type CrearUsuarioDto = Omit<Usuario, keyof EntidadBase>;
// ─────────────────────────────────────────
// src/models/index.ts (barrel de models)
// ─────────────────────────────────────────
export type { ID, EntidadBase } from "./tipos-comunes";
export type { Usuario, CrearUsuarioDto } from "./usuario";
// ─────────────────────────────────────────
// src/core/logger.ts
// ─────────────────────────────────────────
export const logger = {
info: (msg: string, ...args: unknown[]) =>
console.log(`[INFO] ${msg}`, ...args),
error: (msg: string, ...args: unknown[]) =>
console.error(`[ERROR] ${msg}`, ...args),
warn: (msg: string, ...args: unknown[]) =>
console.warn(`[WARN] ${msg}`, ...args),
};
// ─────────────────────────────────────────
// src/features/usuarios/servicio-usuarios.ts
// (usa path alias @models y @core)
// ─────────────────────────────────────────
import type { Usuario, CrearUsuarioDto } from "@models";
import { logger } from "@core/logger";
export class ServicioUsuarios {
private usuarios: Map<string, Usuario> = new Map();
crear(dto: CrearUsuarioDto): Usuario {
const usuario: Usuario = {
...dto,
id: crypto.randomUUID(),
creadaEn: new Date(),
actualizadaEn: new Date(),
};
this.usuarios.set(usuario.id, usuario);
logger.info("Usuario creado:", usuario.nombre);
return usuario;
}
obtener(id: string): Usuario | null {
return this.usuarios.get(id) ?? null;
}
listar(): Usuario[] {
return Array.from(this.usuarios.values());
}
}
// ─────────────────────────────────────────
// src/features/usuarios/index.ts (barrel)
// ─────────────────────────────────────────
export { ServicioUsuarios } from "./servicio-usuarios";
export type { Usuario, CrearUsuarioDto } from "@models";
// ─────────────────────────────────────────
// src/app.ts — punto de entrada limpio
// ─────────────────────────────────────────
import { ServicioUsuarios } from "@features/usuarios";
import type { Usuario } from "@features/usuarios";
const svc = new ServicioUsuarios();
const ana: Usuario = svc.crear({
nombre: "Ana López",
email: "[email protected]",
rol: "admin",
});
const luis: Usuario = svc.crear({
nombre: "Luis Mendoza",
email: "[email protected]",
rol: "editor",
});
console.log("Usuarios:", svc.listar().map((u) => u.nombre));
// ["Ana López", "Luis Mendoza"]Resumen
| Concepto | Cuándo usarlo |
|---|---|
export / import ES |
Siempre en código TypeScript moderno |
import type |
Cuando solo necesitas tipos, para reducir bundle |
moduleResolution: bundler |
Con Vite, Webpack, esbuild |
| Path aliases | Proyectos con más de 2-3 niveles de carpetas |
| Barrel files | Para exponer APIs públicas de cada feature |
| Namespaces | Solo en archivos .d.ts o código legado |
Archivos .d.ts |
Tipar librerías JS sin tipos propios |
Con estos patrones tu base de código será escalable, sus importaciones serán legibles y el refactoring de rutas de archivos dejará de ser un problema. En la siguiente lección aprenderás a migrar un proyecto de JavaScript a TypeScript de forma gradual y sin romper nada.
Inicia sesión para guardar tu progreso