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_modules
no 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 enpackage.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 elbuild
que 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