Skip to content

Lección 99: Módulos, Path Aliases y Buenas Prácticas

🎯 Objetivo: Organizar proyectos TypeScript con módulos ES6, barrel files, path aliases y configuración óptima de tsconfig.json.


💻 Ejemplo

src/models/usuario.ts
export interface Usuario {
id: string;
nombre: string;
email: string;
}
export type Rol = "admin" | "user" | "guest";
export function crearUsuario(nombre: string, email: string): Usuario {
return { id: crypto.randomUUID(), nombre, email };
}
// Export por defecto
export default class ServicioUsuarios { /* ... */ }
src/app.ts
import ServicioUsuarios, { Usuario, Rol, crearUsuario } from "./models/usuario";

export type — Export explícito de tipos

Section titled “export type — Export explícito de tipos”
src/types/shared.ts
export type ID = string;
export interface Respuesta<T> {
datos: T;
error: string | null;
}
// En tsconfig.json con isolatedModules: true, necesario para re-exportar tipos
export type { ID, Respuesta };
// Re-exportar desde otro archivo
export { Usuario } from "./usuario";
export type { Usuario } from "./usuario"; // Explícito como tipo

Un barrel file (index.ts) agrupa y re-exporta múltiples módulos de un directorio.

src/models/index.ts
export { Usuario, Rol, crearUsuario } from "./usuario";
export { Producto, Categoria } from "./producto";
export { Pedido, EstadoPedido } from "./pedido";
export type { RespuestaAPI } from "./api";
// src/app.ts — importación limpia
import { Usuario, Producto, Pedido, EstadoPedido } from "./models";
// En lugar de:
// import { Usuario } from "./models/usuario";
// import { Producto } from "./models/producto";

🧠 Ventajas: Importaciones más limpias, oculta la estructura interna del directorio, facilita refactors.


Controla cómo TypeScript resuelve los módulos.

{
"compilerOptions": {
"moduleResolution": "node"
}
}
OpciónDescripción
nodeResolución clásica de Node.js (busca index.ts, .js, .json)
classicLegado, no recomendado
bundlerPara proyectos con bundlers (Vite, esbuild) — más permisiva
node16 / nodenextPara ESM nativo de Node 16+
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler"
}
}

Para Node.js:

{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext"
}
}

Los aliases evitan rutas relativas profundas como ../../../utils/helpers.

{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"],
"@types/*": ["src/types/*"]
}
}
}
// En lugar de:
import { formatDate } from "../../../utils/date";
// Usamos:
import { formatDate } from "@utils/date";
import { Button } from "@components/Button";
import { Usuario } from "@/models/usuario";

Si usas Vite, también necesitas configurar los aliases en vite.config.ts:

vite.config.ts
import { defineConfig } from "vite";
import path from "path";
export default defineConfig({
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
"@components": path.resolve(__dirname, "./src/components"),
},
},
});

Cuando esta opción está activada, cada archivo se transpila de forma independiente (como hacen Vite, esbuild, Babel).

{
"compilerOptions": {
"isolatedModules": true
}
}

⚠️ Implicaciones:

  • No puedes usar const enum (necesitan información de otros archivos).
  • Debes usar export type para re-exportar tipos.
  • Las re-exportaciones ambiguas generan error.
// ❌ Error con isolatedModules
export { Usuario } from "./usuario";
// ✅ Correcto
export type { Usuario } from "./usuario";
export { crearUsuario } from "./usuario";

TypeScript 4.5+ permite diferenciar imports de tipo y de valor.

// Import de tipo — se borra en la compilación
import type { Usuario } from "./models";
import type { Response } from "express";
// Import de valor
import express from "express";
import { crearUsuario } from "./models";
// Mixto (una línea)
import { crearUsuario, type Usuario } from "./models";

🧠 Beneficios: Los imports de tipo no generan código en el JS final. Mejora el rendimiento de compilación con isolatedModules.


mi-proyecto/
├── src/
│ ├── components/
│ │ ├── Button.ts
│ │ └── index.ts
│ ├── models/
│ │ ├── usuario.ts
│ │ ├── producto.ts
│ │ └── index.ts
│ ├── services/
│ │ ├── api.ts
│ │ └── index.ts
│ ├── utils/
│ │ ├── date.ts
│ │ ├── validators.ts
│ │ └── index.ts
│ ├── types/
│ │ ├── index.ts
│ │ └── global.d.ts
│ └── index.ts (entry point)
├── tests/
├── tsconfig.json
├── package.json
└── vite.config.ts / webpack.config.js
// tsconfig.json completo
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"],
"@services/*": ["src/services/*"]
},
"strict": true,
"esModuleInterop": true,
"isolatedModules": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

// 1. Prefiere import type para tipos
import type { User } from "./types";
// 2. Usa barrel files para simplificar imports
// src/services/index.ts
export { apiService } from "./api";
export type { ApiResponse } from "./api";
// 3. Configura paths para evitar rutas relativas largas
// Con paths: import { formatDate } from "@utils/date";
// Sin paths: import { formatDate } from "../../../utils/date";
// 4. isolatedModules activado = código más robusto para bundlers
// 5. Usa export/import type cuando re-exportes solo tipos

📝 Resumen: Los módulos ES6 con barrel files simplifican la organización del proyecto. Los path aliases (paths + baseUrl) eliminan rutas relativas confusas. isolatedModules garantiza compatibilidad con bundlers modernos. import type optimiza la compilación separando claramente tipos de valores.