JavaScript

Actualizar expresslanets a ES Modules con tsx y Vitest

expresslanets es la plantilla de API REST con Express y TypeScript que empezó como una serie de diez entradas en 2022 y que desde entonces ha ido incorporando mejoras, como la documentación con OpenAPI/Swagger. En esta entrada aplico el razonamiento de la entrada anterior de esta serie a este proyecto en concreto: paso la plantilla a ES Modules, sustituyo ts-node/Nodemon por tsx, y añado Vitest para poder validar tipos y comportamiento en desarrollo sin depender solo de Sonar.

Al ser una aplicación, no una librería que otros instalan, no hay consumidores externos a los que romper. Eso simplifica mucho la decisión: no necesitamos un paquete dual, solo migrar limpiamente a ESM.

Punto de partida

La configuración actual de expresslanets es esta:

// tsconfig.json (antes)
{
  "compilerOptions": {
    "target": "es2016",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "module": "commonjs",
    "rootDir": "./src",
    "resolveJsonModule": true,
    "outDir": "./dist",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "strictPropertyInitialization": false,
    "skipLibCheck": true
  }
}
// package.json (antes, extracto)
"scripts": {
  "build": "npx tsc",
  "dev": "npx nodemon",
  "format": "npx prettier --parser typescript --write ./src",
  "lint": "npx eslint ./src --ext .ts"
},
"devDependencies": {
  "nodemon": "^3.1.10",
  "ts-node": "^10.9.2",
  "typescript": "^5.9.3"
  // ...
}

Dos detalles importantes de este proyecto: usa TypeORM, que depende de decoradores (experimentalDecorators + emitDecoratorMetadata), y no tenía ningún test runner configurado, ni Jest ni Vitest, solo Sonar para análisis estático. Eso significa que este cambio, además de la migración a ESM, es también la primera vez que el proyecto tiene tests de verdad.

Sustituir ts-node y Nodemon por tsx

npm uninstall ts-node nodemon
npm install --save-dev tsx

tsx incluye su propio modo watch, así que no hace falta Nodemon por separado:

"scripts": {
  "dev": "tsx watch src/index.ts"
}

Activar ES Modules en package.json

{
  "type": "module"
}

Actualizar tsconfig.json

Con "type": "module" activo, tsc debe generar salida ESM y resolver los módulos con las reglas de Node para ESM (NodeNext). Mantenemos los ajustes de decoradores que necesita TypeORM:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "rootDir": "./src",
    "resolveJsonModule": true,
    "outDir": "./dist",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "strictPropertyInitialization": false,
    "skipLibCheck": true
  }
}

Un detalle que suele pillar a contrapié la primera vez: bajo moduleResolution: "NodeNext", las importaciones relativas entre tus propios archivos .ts necesitan la extensión .js explícita ,aunque el archivo origen sea .ts, porque así es como Node resuelve módulos ESM en tiempo de ejecución:

// antes
import { UserController } from './controllers/user';

// después
import { UserController } from './controllers/user.js';

No perder el chequeo de tipos en desarrollo

Como comentamos al principio de esta serie, tsx no valida tipos ,solo transpila, y Sonar tampoco sustituye a tsc en eso. La solución es correr el chequeo de tipos como proceso aparte, en paralelo:

npm install --save-dev concurrently
"scripts": {
  "dev": "concurrently \"tsx watch src/index.ts\" \"tsc --noEmit --watch\"",
  "typecheck": "tsc --noEmit"
}

Así tienes errores de tipos en tiempo real en una terminal mientras tsx se encarga de ejecutar rápido en la otra, y typecheck queda disponible como paso de verificación en CI o en un hook de pre-commit.

Añadir Vitest

npm install --save-dev vitest @vitest/coverage-v8
// vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    include: ['src/**/*.test.ts'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'html', 'lcov'],
    },
  },
});
"scripts": {
  "test": "vitest run",
  "test:watch": "vitest",
  "coverage": "vitest run --coverage"
}

Un primer test sencillo, por ejemplo sobre un controlador o una utilidad del proyecto, ya te sirve para comprobar que Vitest arranca correctamente sobre ESM + TypeORM sin configuración adicional, algo que con Jest habría exigido activar flags experimentales de Node.

Verificar la compatibilidad con TypeORM

Tanto tsx como Vitest usan esbuild por debajo, y esbuild soporta los decoradores “legacy” (experimentalDecorators + emitDecoratorMetadata) que usa TypeORM. Aun así, conviene comprobarlo con una entidad real antes de dar la migración por cerrada: crea o edita una entidad con un par de columnas y una relación, y confirma que las metadatas de tipos se generan correctamente al arrancar con npm run dev y al ejecutar sus tests con Vitest.

Conclusiones

Con estos cambios, expresslanets pasa a ser ES Modules sin ningún consumidor externo al que romper, gana un ciclo de desarrollo más rápido con tsx, mantiene el chequeo de tipos gracias a tsc --noEmit --watch en paralelo, y estrena su primera suite de tests con Vitest. En la siguiente entrada de la serie aplico el mismo razonamiento, pero con un resultado bastante distinto, a tslane, donde al ser una librería la solución pasa por un paquete dual CommonJS/ESM en lugar de una migración directa.

Nota: La imagen de este artículo fue generada utilizando un modelo de inteligencia artificial.

¿Te ha parecido de utilidad el contenido?

Daniel Rodríguez

Share
Published by
Daniel Rodríguez
Tags: TypeScript

Recent Posts

Pareto/NBD: cuando el abandono silencioso cambia el CLV

En el artículo del Dashboard vimos que Pareto/NBD aparecía junto a BG/NBD con un ajuste…

2 días ago

Por qué migrar tus proyectos TypeScript a ES Modules en 2026

Durante años, CommonJS (require/module.exports) ha sido la forma por defecto de organizar módulos en Node.js,…

1 semana ago

Método del codo: interpretación correcta y limitaciones

Cuando usas k-means tienes que decidir algo incómodo de entrada: cuántos grupos, , vas a…

1 semana ago

Errores comunes al interpretar resultados estadísticos

La estadística no suele fallar en los cálculos: falla en la interpretación. El test está…

2 semanas ago

Dashboard CLV: cuatro modelos, un mismo dataset, conclusiones muy distintas

Los artículos anteriores de esta serie cubrieron cuatro formas de mirar el valor del cliente:…

2 semanas ago

Curiosidad: Por qué la criptografía habla siempre de Alice y Bob

Quien se acerca a la criptografía descubre enseguida a dos personajes que aparecen en casi…

3 semanas ago

This website uses cookies.