¿Qué son los Módulos?
Clase 1 de 45 min de lectura
Imagina un proyecto con miles de líneas de código en un solo archivo. Encontrar algo es una excursión, y cada cambio puede romper otra cosa a quinientas líneas de distancia. Una pesadilla.
Los módulos son la salida: te permiten dividir el código en archivos separados, cada uno con una responsabilidad clara, que comparten entre sí solo lo que haga falta.
El problema sin módulos#
En los inicios de JavaScript, todo el código se cargaba con etiquetas <script> en el HTML. Y todos esos archivos compartían un mismo espacio global:
<!-- Todo está en el scope global --><script src="utils.js"></script><script src="api.js"></script><script src="app.js"></script>¿Qué puede salir mal? Fíjate en la variable nombre de estos tres archivos:
// utils.jsvar nombre = 'Utilidades'function formatear(texto) { return texto.toUpperCase()}// api.jsvar nombre = 'API' // ⚠️ ¡Sobrescribe la variable de utils.js!function obtenerDatos() { // ...}// app.jsconsole.log(nombre) // -> 'API' (no 'Utilidades')Como comparten el scope global, api.js ha machacado la variable de utils.js. Sin error ni aviso: el valor es otro y encontrar el motivo puede llevarte horas.
app.js importa exactamente lo que necesita: las dependencias quedan a la vista.Otros problemas del scope global
- Colisiones de nombres: dos archivos pueden definir la misma variable o función.
- Dependencias ocultas: al leer un archivo, no sabes de dónde viene cada función que usa.
- Orden de carga: si cambias el orden de los
<script>, las cosas dejan de funcionar. - Sin encapsulación: todo es público, no puedes tener funciones «privadas».
Los primeros intentos de solución#
El lenguaje no ofrecía módulos, así que la comunidad se las ingenió con patrones y sistemas propios.
El patrón IIFE (Immediately Invoked Function Expression)
Una IIFE es una función que se ejecuta en el mismo momento en que se define. Como toda función, crea su propio scope, así que lo que declares dentro no sale al scope global.
Fíjate en los paréntesis del final, que son los que la ejecutan:
// utils.jsvar MisUtils = (function() { // Variables privadas (no accesibles desde fuera) var version = '1.0' // Solo exponemos lo que queremos return { formatear: function(texto) { return texto.toUpperCase() }, getVersion: function() { return version } }})()// app.jsconsole.log(MisUtils.formatear('hola')) // -> 'HOLA'console.log(MisUtils.getVersion()) // -> '1.0'console.log(MisUtils.version) // -> undefined (¡es privada!)Solo sale fuera el objeto que devuelve la función; version se queda dentro, a salvo.
Funcionaba, pero era verboso e incómodo. Cada librería seguía creando su variable global (jQuery, _, React...) y tú seguías dependiendo del orden de los <script>.
CommonJS (Node.js)
Cuando nació Node.js en 2009, llegó con él CommonJS, el primer sistema de módulos de uso generalizado en JavaScript. Usa require para importar y module.exports para exportar:
// utils.js (CommonJS)function formatear(texto) { return texto.toUpperCase()}module.exports = { formatear }// app.js (CommonJS)const { formatear } = require('./utils')console.log(formatear('hola')) // -> 'HOLA'CommonJS tiene una pega: es síncrono. Carga los archivos uno a uno y espera a cada uno antes de seguir. Para un servidor, que los tiene en su disco, es perfecto. Para el navegador, que debe descargarlos por la red, no sirve.
ES Modules: módulos nativos de JavaScript#
En 2015, con ES6 (ECMAScript 2015), JavaScript incorporó por fin su propio sistema de módulos: los ES Modules (ESM). Es el estándar, y funciona tanto en navegadores como en Node.js.
Sus ventajas frente a todo lo anterior:
- Estándar del lenguaje: no necesitas herramientas externas.
- Funciona en todas partes: navegadores y Node.js.
- Análisis estático: las herramientas pueden saber qué importa cada archivo sin ejecutar el código.
- Carga asíncrona: no bloquean el navegador.
- Encapsulación real: cada módulo tiene su propio scope.
Cómo usar ES Modules en el navegador
Basta con añadir type="module" a la etiqueta <script>:
<script type="module" src="app.js"></script>Ese pequeño atributo cambia cuatro cosas en app.js:
- Tiene su propio scope: sus variables ya no son globales.
- Puede usar
importyexport. - Se carga sin bloquear la página (como si llevara
defer). - Se ejecuta en modo estricto (
'use strict') automáticamente.
Cómo usar ES Modules en Node.js
En Node.js tienes dos opciones.
Opción 1: usa la extensión .mjs en el archivo.
// utils.mjsexport function formatear(texto) { return texto.toUpperCase()}Opción 2: añade "type": "module" en tu package.json.
{ "name": "mi-proyecto", "type": "module"}Con la segunda opción, todos los archivos .js del proyecto se tratan como ES Modules.
Resumen#
| Sistema | Año | Sintaxis | Dónde funciona |
|---|---|---|---|
| Scripts globales | 1995 | <script> | Navegador |
| IIFE | ~2000 | (function(){})() | Navegador |
| CommonJS | 2009 | require / module.exports | Node.js |
| ES Modules | 2015 | import / export | Navegador + Node.js |
Los ES Modules son el estándar actual y lo que deberías usar en tus proyectos. En las próximas clases aprenderás a manejarlos a fondo.
En resumen
- Sin módulos, todos los scripts comparten el scope global y pueden pisarse variables entre sí.
- Las IIFE y CommonJS fueron soluciones anteriores a los módulos nativos.
- Los ES Modules (
import/export) son el estándar: funcionan en navegadores y en Node.js. - Cada módulo tiene su propio scope y solo comparte lo que exporta.
- Se activan con
type="module"en el<script>, o con.mjso"type": "module"en Node.js.