esModuleInterop en tsconfig: Guía completa para TypeScript

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: Por qué esModuleInterop no es solo una casilla en tsconfig

\n\n

Si alguna vez has escrito en TypeScript e importado una librería como express o lodash, seguramente te has encontrado con el momento en que el compilador se queja de import * as express from 'express'. O, por el contrario, usas tranquilamente import express from 'express', pero en el código JavaScript generado ves una extraña construcción module.exports.default. ¿Qué magia es esta? Pues la magia (o su ausencia) es la bandera esModuleInterop en tu tsconfig.json.

\n\n

Este parámetro es una de las preguntas más populares en Stack Overflow (¡miles de visitas!), y no es de extrañar: sin entenderlo, es fácil caer en la trampa de la incompatibilidad de módulos. En este tutorial no solo explicaremos la teoría, sino que recorreremos el camino desde el problema hasta la solución con ejemplos prácticos. ¡Allá vamos!

\n\n

El problema: Conflicto de dos mundos de módulos

\n\n

TypeScript es una extensión de JavaScript, y JavaScript históricamente tiene dos sistemas principales de módulos:

\n
    \n
  • CommonJS (CJS) — el estándar de Node.js, usa require() y module.exports. Ejemplo: const express = require('express');
  • \n
  • ES Modules (ESM) — el estándar moderno, usa import y export. Ejemplo: import express from 'express';
  • \n
\n

El problema es que estos sistemas manejan de manera diferente la exportación por defecto (default export). En ESM, un módulo puede tener un export default, y se importa como import name from 'module'. En CommonJS no existe el concepto de "exportación por defecto" — solo existe el objeto module.exports, que puede ser cualquier cosa.

\n\n

Cuando TypeScript compila tu código a CommonJS (lo que a menudo se necesita para Node.js), debe decidir: ¿cómo representar import x from 'y' en forma de require()? Sin esModuleInterop, TypeScript lo hace literalmente: crea module.exports.default, lo que rompe la importación de librerías escritas para CommonJS.

\n\n

¿Qué es esModuleInterop?

\n\n

esModuleInterop es una bandera en tsconfig.json que habilita compatibilidad adicional entre CommonJS y ES Modules. Cuando se establece en true, TypeScript genera código auxiliar que permite escribir import foo from 'bar' incluso para módulos CommonJS que no tienen una exportación default.

\n\n

Por defecto en TypeScript 4.x y superiores, esta bandera es true (si usas --init), pero en proyectos antiguos a menudo se desactivaba. ¿Por qué? Porque cambia el comportamiento de la importación y, sin entenderlo, provoca errores.

\n\n

Cómo se ve en tsconfig.json

\n
{\n  "compilerOptions": {\n    "esModuleInterop": true,\n    // otras configuraciones...\n  }\n}\n
\n\n

Activar esta bandera también activa automáticamente allowSyntheticDefaultImports, que permite la importación por defecto desde módulos sin una exportación default real a nivel de tipos.

\n\n

Tutorial paso a paso: del problema a la solución

\n\n

Analicemos la situación con un ejemplo real. Supongamos que tenemos una librería simple en CommonJS:

\n\n
// myLib.js (CommonJS)\nmodule.exports = function greet(name) {\n  return `¡Hola, ${name}!`;\n};\n
\n\n

Queremos usarla en un proyecto TypeScript. Creemos un archivo index.ts:

\n\n
// index.ts\nimport greet from './myLib';\nconsole.log(greet('Mundo'));\n
\n\n

Ahora veamos qué sucede al compilar con diferentes configuraciones de esModuleInterop.

\n\n

Paso 1: Sin esModuleInterop (false)

\n\n

Ejecutemos la compilación con "esModuleInterop": false. TypeScript mostrará un error:

\n
El módulo '"myLib"' no tiene ninguna exportación por defecto.                

Blogs

Book Recommendations