require vs import en TypeScript: tutorial paso a paso para quienes se confunden

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

require(x) vs import x: por qué es un dolor de cabeza eterno para el desarrollador de TypeScript

\n\n

Si alguna vez has escrito en TypeScript y has visto un error como Cannot use import statement outside a module o require is not defined — no estás solo. En Stack Overflow, la pregunta "The difference between 'require(x)' and 'import x'" ha acumulado decenas de miles de visitas. Y no es casualidad: la confusión entre los dos sistemas de módulos es una de las trampas más comunes en las que caen incluso desarrolladores experimentados.

\n\n

En este tutorial no solo enumeraremos las diferencias. Recorreremos el camino desde la total incomprensión hasta la elección segura de la sintaxis correcta. Aprenderás cómo TypeScript compila tu código, por qué require sigue vivo y cuándo deberías usar import.

\n\n

1. Contexto histórico: de dónde surgieron los dos sistemas

\n\n

Para entender la diferencia, hay que mirar la historia de JavaScript. Originalmente, Node.js no tenía un sistema de módulos incorporado. Los desarrolladores inventaron CommonJS — una forma síncrona de conectar módulos mediante require(). Este enfoque funciona del lado del servidor, pero no era adecuado para navegadores — allí se necesita carga asíncrona.

\n\n

Más tarde surgió el estándar ES Modules (ESM) — la sintaxis de módulos oficial de ECMAScript. TypeScript, como superconjunto de JS, soporta ambas opciones. Pero aquí es donde comienza la confusión: escribes import, y el compilador lo convierte en require — o viceversa.

\n\n

2. Sintaxis: diferencias principales

\n\n

Vamos a desglosar cómo se ven ambos enfoques.

\n\n

CommonJS (require)

\n\n
// Importar un módulo completo\nconst fs = require('fs');\n\n// Importar parte de un módulo (desestructuración)\nconst { readFile, writeFile } = require('fs');\n\n// Exportar\nmodule.exports = { myFunction, myVariable };\nexports.myFunction = () => { ... };
\n\n

ES Modules (import)

\n\n
// Importar un módulo completo\nimport fs from 'fs';\n\n// Importar exportaciones nombradas\nimport { readFile, writeFile } from 'fs';\n\n// Importar todo como un objeto\nimport * as fs from 'fs';\n\n// Exportar\nexport const myVariable = 42;\nexport function myFunction() { ... }\nexport default class MyClass { }
\n\n

A primera vista, la diferencia es solo en la sintaxis. Pero profundicemos.

\n\n

3. Importación estática vs dinámica

\n\n

Esta es la diferencia clave que afecta cómo funciona tu código.

\n\n
    \n
  • require — dinámico. Puedes llamarlo en cualquier lugar, dentro de una condición o función. El módulo se carga en el momento de la llamada.
  • \n
  • import — estático. Las importaciones se resuelven en la etapa de compilación, antes de ejecutar el código. Siempre deben estar en el nivel superior del archivo.
  • \n
\n\n

Ejemplo de require dinámico:

\n\n
let myModule;\nif (process.env.NODE_ENV === 'production') {\n  myModule = require('./production-config');\n} else {\n  myModule = require('./dev-config');\n}
\n\n

Ese truco no funcionará con import — dará un error de sintaxis. Pero para eso existe import() dinámico (devuelve una Promise):

\n\n
if (condition) {\n  const module = await import('./some-module');\n  module.doSomething();\n}
\n\n

4. Cómo compila TypeScript los módulos

\n\n

Todo depende de la configuración module en tsconfig.json. Estas son las opciones principales:

\n\n
    \n
  • "module": "commonjs" — tu import se convertirá en require. El código funcionará en Node.js sin configuraciones adicionales.
  • \n
  • "module": "esnext" — import se mantendrá tal cual. Adecuado para navegadores modernos o empaquetadores (Webpack, Vite).
  • \n
  • "module": "node16" o "nodenext" — TypeScript determina automáticamente
  • \n

Blogs

Book Recommendations