Una breve historia de los módulos de JavaScript
Technicamente hablando, los desarrolladores han estado usando módulos en JavaScript por un tiempo. Soluciones como CommonJS (para Node.js), Browserify y Require.js han permitido a los desarrolladores separar su código en módulos reutilizables que hacen que el código sea más fácil de mantener.
CommonJS fue básicamente la base del sistema de gestión de paquetes de Node, npm. Esto permitió a los desarrolladores crear paquetes o módulos que podían agregar a un registro remoto, permitiendo que otros los usen. También, podían usar módulos en el registro de npm para sus propios proyectos. Pero esto era exclusivo de Node.js (JavaScript en el backend), por lo que los navegadores (JavaScript del lado del cliente) no tenían una forma de incorporar esta gestión de paquetes modular.
Por eso, los desarrolladores de JavaScript crearon herramientas como Browserify y Require.js para incorporar módulos y agruparlos para proyectos frontend. Más tarde, la comunidad introdujo soluciones de agrupación más potentes como Webpack y Parcel, permitiendo que el proceso sea una parte regular del desarrollo moderno de JavaScript.
Si quieres profundizar en los detalles de la historia de los módulos de JavaScript, consulta este artículo.
Módulos de JavaScript nativos
Soluciones como CommonJS eran necesarias para hacer posible los módulos en JavaScript (especialmente en Node.js). Pero JavaScript necesitaba algo mejor. En 2015, el estándar ECMAScript añadió oficialmente soporte para módulos nativos (o ES Modules) que ya no requerían herramientas de terceros para el desarrollo modular en el navegador.
Los módulos nativos son beneficiosos por varias razones, incluyendo:
- Los módulos de JavaScript permiten a los desarrolladores encapsular y organizar el código en partes más pequeñas y reutilizables. Esta modularidad hace que sea más fácil gestionar y mantener cualquier base de código.
- Puedes reutilizar los módulos de JavaScript, lo que evita la duplicación en un proyecto y ayuda a mantener el código DRY (es decir, “Don’t Repeat Yourself”).
- Los módulos garantizan que el navegador cargue solo las partes necesarias del código en cualquier momento, mejorando así el rendimiento de las aplicaciones web, incluidas las PWAs.
- El código modular evita en gran medida los conflictos de nombres, ya que la modularidad aísla variables y otras referencias, evitando conflictos con otros módulos y sin contaminar el ámbito global.
- Los módulos hacen mucho más fácil las pruebas unitarias, ya que puedes ejecutar pruebas en módulos aislados sin afectar a otras partes del código.
Con esa pequeña lección de historia y los beneficios claros, ahora profundicemos en el código para que puedas ver cómo utilizar los módulos de JavaScript en aplicaciones web modernas.
Sintaxis para incluir módulos de JavaScript
Puedes insertar cualquier módulo de JavaScript en una página web utilizando casi la misma sintaxis que cualquier otro script. Pero ten en cuenta una pequeña diferencia:
body>
...
script src="index.js" type="module">script>
body>
Language code: HTML, XML (xml)
En el código anterior, he añadido el elemento en la parte inferior de mi página HTML. Estoy referenciando el script como lo haría normalmente usando el atributo
src
. Pero, nota el atributo type
. En lugar del valor habitual de text/javascript
, he utilizado un valor de module
. Esto le indica al navegador que habilite las funciones de módulo de JavaScript en este script en lugar de tratarlo como un script normal.
¿Cómo difiere un script cuando se incluye como un módulo?
- El navegador retrasa automáticamente los scripts con
type="module"
, por lo que el uso del atributodefer
no afectará a este script. - Los módulos están sujetos a las reglas de CORS, lo que significa que solo puedes acceder a los módulos en el mismo dominio o aquellos en un dominio diferente al que hayas permitido el acceso a través de CORS. Esto es diferente de los scripts normales que puedes cargar desde cualquier lugar.
- El navegador interpreta los módulos individuales en modo estricto.
En el ejemplo anterior, puedo considerar el archivo index.js
como el punto de entrada de mi aplicación modular. Podría llamar a este archivo como quisiera, pero muchos desarrolladores suelen usar index.js
como punto de entrada de su aplicación.
Ahora, voy a crear algunos módulos que usaré dentro de index.js
. Echemos un vistazo a mi estructura de carpetas para que te hagas una idea de cómo estoy organizando mis módulos:
index.html
index.js
modules/
one.js
two.js
three.js
Tu propia estructura de carpetas de la aplicación y los nombres de archivos/carpetas pueden ser diferentes, pero lo anterior debería ser lo suficientemente fácil de entender para los propósitos de demostración aquí. El archivo index.html
sería el que incluye el elemento que inserta el punto de entrada de módulo inicial (
index.js
).
Observa que he añadido una carpeta llamada modules
, que almacenará todos mis módulos en archivos separados. En este caso, hay tres módulos.
Sintaxis para importar y exportar módulos de JavaScript
Ahora que tengo un punto de entrada de módulo y he configurado mi estructura de carpetas, te mostraré cómo puedes importar cualquiera de los módulos en el script principal.
El archivo one.js va a contener el siguiente script de módulo simple:
export function add(a, b) {
return a + b;
}
Language code: JavaScript (javascript)
Esta es simplemente una función add()
. La parte clave del script que hace que sea un módulo utilizable es el uso de la declaración export
. Esta sintaxis solo es utilizable dentro de un módulo. Con esto en su lugar, puedo importar el módulo dentro de main.js
de esta manera:
import { add } from './modules/one.js';
console.log(add(5, 8));
Language code: JavaScript (javascript)
La primera línea de código es donde importo la función add()
, envuelta en llaves. Luego defino de dónde quiero importar el módulo usando la palabra clave “from”, seguida por la ubicación del módulo entre comillas.
Una vez que tengo el módulo importado, puedo usar la función add()
como lo haría con cualquier función a la que tengo acceso. En este caso, llamo a la función y paso dos números, que se suman, y la función devuelve el valor.
No tengo que exportar un valor al mismo tiempo que lo defino. Por ejemplo, anteriormente, exporté la función add()
poniendo la palabra clave export
antes de la palabra clave function
. Alternativamente, podría hacer lo siguiente:
function add(a, b) {
return a + b;
}
export { add };
Language code: JavaScript (javascript)
En este caso, estoy exportando la referencia a add()
en lugar de la declaración de add()
. Toma nota del uso de llaves alrededor de la referencia add
, lo que es necesario. El mismo principio se aplicaría a cualquier cosa que esté exportando: funciones, variables o clases. Puedo exportar la referencia en lugar de la declaración.
Esa es la forma más simple de explicar la sintaxis de los módulos de JavaScript. Hay varios otros aspectos en el código, así que cubriré esos a continuación.
Sintaxis para la exportación de múltiples valores en un módulo de JavaScript
En un archivo de módulo de JavaScript, puedo exportar cualquier cantidad de valores, incluido cualquier cosa almacenada en una variable, una función, etc. Para demostrarlo, agregaré el siguiente código a mi módulo two.js
:
export let name = 'Sally';
export let salad = ['lettuce', 'tomatoes', 'onions'];
Language code: JavaScript (javascript)
He exportado una variable sencilla name
seguida de un arreglo llamado salad
. Ahora los importaré, por lo que mi archivo index.js
import { add } from './modules/one.js';
import { name, salad } from './modules/two.js';
console.log(add(5, 8));
console.log(name);
console.log(salad[2]);
Language code: JavaScript (javascript)
Observa la sintaxis requerida para importar múltiples valores. He colocado las referencias name
y salad
dentro de llaves y las he separado con una coma. Esta lista de importaciones separadas por comas podría ser cualquier cantidad de referencias, siempre y cuando las exporte correctamente desde el archivo two.js
.
Otra forma de importar múltiples exportaciones desde un solo archivo es usando el carácter asterisco al hacer la importación. Supongamos que tengo lo siguiente en mi módulo:
export function add(a, b) {
return a + b;
}
export let num1 = 6,
num2 = 13,
num3 = 35;
Language code: JavaScript (
Fuente