Módulos#

En JavaScript, los módulos son colecciones de datos y funciones útiles para el programa principal. Los módulos permiten encapsular el código, lo que ayuda a mantenerlo organizado y fácil de mantener. Además, siguen el Principio de Menor Exposición (POLE), lo que significa que solo exponen una API pública y pueden contener detalles privados. Los módulos pueden ser stateful, es decir, pueden guardar información a lo largo del tiempo.

Problemas sin módulos#

Cuando el código está en varios archivos .js sin usar módulos, el espacio de nombres global se llena de variables y funciones. Esto puede causar conflictos y errores difíciles de depurar. Además, el orden de los archivos importa, ya que se interpretan como si todo el código estuviera en un único archivo.

Espacios de nombres y estructuras de datos que no son módulos#

Ejemplo de espacio de nombres (no es un módulo):

var Utils = {
   cancelEvt(evt) {
       evt.preventDefault(); 
       evt.stopPropagation(); 
       evt.stopImmediatePropagation();
   },
   wait(ms) {
       return new Promise(function c(res){ setTimeout(res,ms); });
   },
   isValidEmail(email) {
       return /[^@]+@[^@.]+\.[^@.]+/.test(email);
   }
};

En este ejemplo, Utils es un objeto que agrupa funciones relacionadas. Sin embargo, no es un módulo porque no encapsula el código ni oculta detalles internos.

Ejemplo de estructura de datos (no es un módulo):

var Student = {
   records: [
      { id: 14, name: "Kyle", grade: 86 }, 
      { id: 73, name: "Suzy", grade: 87 }, 
      { id: 112, name: "Frank", grade: 75 }, 
      { id: 6, name: "Sarah", grade: 91 }
   ],
   getName(studentID) {
       var student = this.records.find(student => student.id == studentID);
       return student.name;
   }
};
Student.getName(73); // Suzy

Aquí, Student es una estructura de datos que contiene un array de registros y una función para obtener el nombre de un estudiante. Similar al ejemplo anterior, no encapsula ni protege los datos.

Módulos antes de ES6#

Antes de ES6, se usaban diferentes estándares para crear módulos en JavaScript, como CommonJS, Asynchronous Module Definition (AMD) y Universal Module Definition (UMD). Sin embargo, ninguno de estos era nativo de JavaScript y requerían bibliotecas adicionales. También se utilizaron herramientas como Babel o Webpack para traducir el código en módulos a código sin módulos.

Módulos manuales (con estado y control de acceso)#

Antes de ES6, se podían crear módulos manualmente para encapsular código y controlar el acceso a los datos.

Ejemplo de módulo manual:

function defineStudent() {
   var records = [
      { id: 14, name: "Kyle", grade: 86 }, 
      { id: 73, name: "Suzy", grade: 87 }, 
      { id: 112, name: "Frank", grade: 75 }, 
      { id: 6, name: "Sarah", grade: 91 }
   ];
   var publicAPI = { getName };
   return publicAPI;

   function getName(studentID) {
       var student = records.find(student => student.id == studentID);
       return student.name;
   }
}
var fullTime = defineStudent();
fullTime.getName(73); // Suzy

En este ejemplo, defineStudent es una función que define un módulo. El array records y la función getName están encapsulados dentro del módulo, y solo getName se expone públicamente.

Módulos ES6#

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules

Con la introducción de ES6, JavaScript nativo ahora admite módulos a través de las palabras clave import y export. Los módulos ES6 no agregan nada al ámbito global, siempre están en modo estricto y requieren un servidor para cargar los archivos a través de HTTP.

Definición y exportación de módulos ES6:

// students.js
export function getName(studentID) {
   var records = [
      { id: 14, name: "Kyle", grade: 86 }, 
      { id: 73, name: "Suzy", grade: 87 }, 
      { id: 112, name: "Frank", grade: 75 }, 
      { id: 6, name: "Sarah", grade: 91 }
   ];
   var student = records.find(student => student.id == studentID);
   return student.name;
}

Importación de módulos ES6:

// main.js
import { getName } from "/path/to/students.js";
console.log(getName(73)); // Suzy

Exportar múltiples funciones:

// students.js
export function getName(studentID) { /*...*/ }
export function getGrade(studentID) { /*...*/ }

Importar múltiples funciones:

// main.js
import { getName, getGrade } from "/path/to/students.js";

Exportación e importación por defecto:

// students.js
export default function getName(studentID) { /*...*/ }

// main.js
import getName from "/path/to/students.js";

Importación con alias:

// main.js
import { getName as getStudentName } from "/path/to/students.js";

Importación de todo el módulo:

// main.js
import * as Student from "/path/to/students.js";
Student.getName(73); // Suzy

Uso de <script> con módulos#

Se pueden cargar módulos en un archivo HTML usando el atributo type="module" en las etiquetas <script>.

Ejemplo:

<!DOCTYPE html>
<html>
<head>
   <title>Módulos ES6</title>
</head>
<body>
   <script type="module" src="functions.js"></script>
   <script type="module" src="script.js"></script>
</body>
</html>

Importación asíncrona#

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import

  • Devuelve una promesa (es asíncrono)

  • Se puede importar Javascript en “tiempo real”

  • No está limitado a usar el import al inicio

  • Puede ejecutar código sólo en ciertos casos

Importar en Bare Names#

En Vite y otros entornos, no es necesario especificar la extensión de los archivos al importar, incluso si están en node_modulesno hace falta ni la ruta. Esto es por dos razones:

  • Vite usa un sistema de resolución de módulos que resuelve automáticamente las extensiones de archivos. Esto se puede configurar con resolve.extensions

  • Las bibliotecas instaladas en node_modules tienen una referencia en package.json y su propio package.json indica cómo se exporta. Vite y otros como Webpack siguen el estándar ESM, pero se necesita un proceso como el que hacen al hacer el buildque las resuelva.

Si queremos imitar esto sin usa Vite o similar, necesitaríamos import maps https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#importing_modules_as_bare_names