Módulos, introducción [Link]
info/modules-intro
Comprar EPUB/PDF
ES
→ El lenguaje JavaScript → Módulos
16 de octubre de 2023
Módulos, introducción
A medida que nuestra aplicación crece, queremos dividirla en múltiples archivos, llamados “módulos”. Un
módulo puede contener una clase o una biblioteca de funciones para un propósito específico.
Durante mucho tiempo, JavaScript existió sin una sintaxis de módulo a nivel de lenguaje. Esto no era un
problema, porque inicialmente los scripts eran pequeños y simples.
Pero con el tiempo los scripts se volvieron cada vez más complejos, por lo que la comunidad inventó una
variedad de formas de organizar el código en módulos, bibliotecas especiales para cargar módulos a pedido.
Para nombrar algunos (por razones históricas):
● AMD – uno de los sistemas de módulos más antiguos, implementado inicialmente por la biblioteca
[Link].
● CommonJS – el sistema de módulos creado para el servidor [Link].
● UMD – un sistema de módulos más, sugerido como universal, compatible con AMD y CommonJS.
Todo esto se va convirtiendo lentamente en parte de la historia, pero aún podemos encontrarlos en viejos
scripts.
El sistema de módulos a nivel de lenguaje apareció en el estándar en 2015, evolucionó gradualmente desde
entonces, y ahora es soportado por todos los navegadores importantes y por [Link]. Así que de ahora en
adelante estudiaremos los módulos de Javascript modernos.
Qué es un módulo?
Un módulo es simplemente un archivo. Un script es un módulo. Tan sencillo como eso.
Los módulos pueden cargarse entre sí y usar directivas especiales export e import para intercambiar
funcionalidad, llamar a funciones de un módulo de otro:
● La palabra clave export etiqueta las variables y funciones que necesitan ser accesibles desde fuera
del módulo actual.
● import permite importar funcionalidades desde otros módulos.
Por ejemplo, si tenemos un archivo [Link] que exporta una función:
1 de 11 24/3/24, 17:56
Módulos, introducción [Link]
1 // � [Link]
2 export function sayHi(user) {
3 alert(`Hello, ${user}!`);
4 }
…Luego, otro archivo puede importarlo y usarlo:
1 // � [Link]
2 import {sayHi} from './[Link]';
3
4 alert(sayHi); // function...
5 sayHi('John'); // Hello, John!
La directiva import carga el módulo por la ruta ./[Link] relativa a la del archivo actual, y asigna la
función exportada sayHi a la variable correspondiente.
Ejecutemos el ejemplo en el navegador.
Como los módulos admiten palabras clave y características especiales, debemos decirle al navegador que
un script debe tratarse como un módulo, utilizando el atributo <script type =" module "> .
Asi:
Resultado [Link] [Link]
1 <!doctype html>
2 <script type="module">
3 import {sayHi} from './[Link]';
4
5 [Link] = sayHi('John');
6 </script>
El navegador busca y evalúa automáticamente el módulo importado (y sus importaciones si es necesario), y
luego ejecuta el script.
Los módulos funcionan solo a través de HTTP(s), no localmente
Si intenta abrir una página web localmente a través del protocolo file:// , encontrará que las
directivas import y export no funcionan. Use un servidor web local, como static-server, o use la
capacidad de “servidor vivo” de su editor, como VS Code Live Server Extension para probar los
módulos.
Características principales de los módulos
¿Qué hay de diferente en los módulos en comparación con los scripts “normales”?
2 de 11 24/3/24, 17:56
Módulos, introducción [Link]
Estas son las características principales, válidas tanto para el navegador como para JavaScript del lado del
servidor.
Siempre en modo estricto
Los módulos siempre trabajan en modo estricto. Por ejemplo, asignar a una variable sin declarar nos dará un
error.
1 <script type="module">
2 a = 5; // error
3 </script>
Alcance a nivel de módulo
Cada módulo tiene su propio alcance de nivel superior. En otras palabras, las variables y funciones de nivel
superior de un módulo no se ven en otros scripts.
En el siguiente ejemplo, se importan dos scripts y [Link] intenta usar la variable user declarada en
[Link] . Falla, porque es un módulo separado (puedes ver el error en la consola):
Resultado [Link] [Link] [Link]
1 <!doctype html>
2 <script type="module" src="[Link]"></script>
3 <script type="module" src="[Link]"></script>
Los módulos deben hacer export a lo que ellos quieren que esté accesible desde afuera, y hacer
import de lo que necesiten.
● [Link] debe exportar la variable user .
● [Link] debe importarla desde el módulo [Link] .
En otra palabras, con módulos usamos import/export en lugar de depender de variables globales.
Esta es la variante correcta:
Resultado [Link] [Link] [Link]
1 import {user} from './[Link]';
2
3 [Link] = user; // John
3 de 11 24/3/24, 17:56
Módulos, introducción [Link]
En el navegador, hablando de páginas HTML, también existe el alcance independiente de nivel superior para
cada <script type="module"> :
Aquí hay dos scripts en la misma página, ambos type="module" . No ven entre sí sus variables de nivel
superior:
1 <script type="module">
2 // La variable sólo es visible en éste script de módulo
3 let user = "John";
4 </script>
5
6 <script type="module">
7 alert(user); // Error: user no está definido
8 </script>
Por favor tome nota:
En el navegador, podemos hacer que una variable sea global a nivel window si explícitamente la
asignamos a la propiedad window , por ejemplo [Link] = "John" .
Así todos los scripts la verán, con o sin type="module" .
Dicho esto, hacer este tipo de variables globales está muy mal visto. Por favor evítalas.
Un código de módulo se evalúa solo la primera vez cuando se importa
Si el mismo módulo se importa en varios otros módulos, su código se ejecuta solo una vez: en el primer
import. Luego, sus exportaciones se otorgan a todos los importadores que siguen.
Eso tiene consecuencias importantes para las que debemos estar prevenidos.
Echemos un vistazo usando ejemplos:
Primero, si ejecutar un código de módulo trae efectos secundarios, como mostrar un mensaje, importarlo
varias veces lo activará solamente una vez, la primera:
1 // � [Link]
2 alert("Módulo es evaluado!");
4 de 11 24/3/24, 17:56
Módulos, introducción [Link]
1 // Importar el mismo módulo desde archivos distintos
2
3 // � [Link]
4 import `./[Link]`; // Módulo es evaluado!
5
6 // � [Link]
7 import `./[Link]`; // (no muestra nada)
El segundo import no muestra nada, porque el módulo ya fue evaluado.
Existe una regla: el código de módulos del nivel superior debe ser usado para la inicialización y creación de
estructuras de datos internas específicas del módulo. Si necesitamos algo que pueda ser llamado varias
veces debemos exportarlo como una función, como hicimos con el sayHi de arriba.
Consideremos un ejemplo más avanzado.
Digamos que un módulo exporta un objeto:
1 // � [Link]
2 export let admin = {
3 name: "John"
4 };
Si este módulo se importa desde varios archivos, el módulo solo se evalúa la primera vez, se crea el objeto
admin y luego se pasa a todos los importadores adicionales.
Todos los importadores obtienen exactamente este mismo y único objeto admin :
1 // � [Link]
2 import {admin} from './[Link]';
3 [Link] = "Pete";
4
5 // � [Link]
6 import {admin} from './[Link]';
7 alert([Link]); // Pete
8
9 // Ambos, [Link] y [Link], hacen referencia al mismo objeto admin
10 // Los cambios realizados en [Link] son visibles en [Link]
Como puedes ver, cuando [Link] cambia la propiedad name en el admin importado, entonces [Link]
puede ver el nuevo [Link] .
Esto es porque el módulo se ejecuta solo una vez. Los exports son generados y luego compartidos entre
importadores, entonces si algo cambia en el objeto admin , otros importadores lo verán.
Tal comportamiento es en verdad muy conveniente, porque nos permite configurar módulos.
En otras palabras, un módulo puede brindar una funcionalidad genérica que necesite ser configurada. Por
5 de 11 24/3/24, 17:56
Módulos, introducción [Link]
ejemplo, la autenticación necesita credenciales. Entonces se puede exportar un objeto de configuración
esperando que el código externo se lo asigne.
Aquí está el patrón clásico:
1. Un módulo exporta algún medio de configuración, por ejemplo un objeto configuración.
2. En el primer import lo inicializamos, escribimos en sus propiedades. Los scripts de la aplicación de nivel
superior pueden hacerlo.
3. Importaciones posteriores usan el módulo.
Por ejemplo, el módulo [Link] puede proporcionar cierta funcionalidad (ej. autenticación), pero espera
que las credenciales entren al objeto admin desde afuera:
1 // � [Link]
2 export let config = { };
3
4 export function sayHi() {
5 alert(`Ready to serve, ${[Link]}!`);
6 }
Aquí [Link] exporta el objeto config (inicialmente vacío, pero podemos tener propiedades por
defecto también).
Entonces en [Link] , el primer script de nuestra app, importamos config de él y establecemos
[Link] :
1 // � [Link]
2 import {config} from './[Link]';
3 [Link] = "Pete";
…Ahora el módulo [Link] está configurado.
Importadores posteriores pueden llamarlo, y él muestra correctamente el usuario actual:
1 // � [Link]
2 import {sayHi} from './[Link]';
3
4 sayHi(); // Ready to serve, Pete!
[Link]
El objeto [Link] contiene la información sobre el módulo actual.
Su contenido depende del entorno. En el navegador, contiene la URL del script, o la URL de la página web
actual si está dentro de HTML:
6 de 11 24/3/24, 17:56
Módulos, introducción [Link]
1 <script type="module">
2 alert([Link]); // script URL
3 // para un script inline es la URL de la página HTML actual
4 </script>
En un módulo, “this” es indefinido (undefined).
Esa es una característica menor, pero para ser exhaustivos debemos mencionarla.
En un módulo, el nivel superior this no está definido.
Compárelo con scripts que no sean módulos, donde this es un objeto global:
1 <script>
2 alert(this); // window
3 </script>
4
5 <script type="module">
6 alert(this); // undefined
7 </script>
Funciones específicas del navegador
También hay varias diferencias específicas de los scripts del navegador con type =" module " en
comparación con los normales.
Es posible que desee omitir esta sección por ahora si está leyendo por primera vez o si no usa JavaScript en
un navegador.
Los módulos son diferidos
Los módulos están siempre diferidos, el mismo efecto que el atributo defer (descrito en el capítulo Scripts:
async, defer), para ambos scripts, externos y en línea.
En otras palabras:
● descargar módulos de script externos <script type="module" src="..."> no bloquea el
procesamiento de HTML, se cargan en paralelo junto con otros recursos.
● los módulos esperan hasta que el documento HTML esté completamente listo (incluso si son pequeños y
cargan más rápido que HTML), y luego lo ejecuta.
● se mantiene el orden relativo de los scripts: los scripts que van primero en el documento, se ejecutan
primero.
Como efecto secundario, los módulos siempre “ven” la página HTML completamente cargada, incluidos los
elementos HTML debajo de ellos.
Por ejemplo:
7 de 11 24/3/24, 17:56
Módulos, introducción [Link]
1 <script type="module">
2 alert(typeof button); // objeto: el script puede 'ver' el botón de abajo
3 // debido que los módulos son diferidos, el script se ejecuta después de q
4 </script>
5
6 Abajo compare con un script normal:
7
8 <script>
9 alert(typeof button); // button es indefinido, el script no puede ver los
10 // los scripts normales corren inmediatamente, antes de que el resto de la
11 </script>
12
13 <button id="button">Button</button>
Note que: ¡el segundo script se ejecuta antes que el primero! Entonces vemos primero undefined , y
después object .
Esto se debe a que los módulos están diferidos, por lo que esperamos a que se procese el documento. El
script normal se ejecuta inmediatamente, por lo que vemos su salida primero.
Al usar módulos, debemos tener en cuenta que la página HTML se muestra a medida que se carga, y los
módulos JavaScript se ejecutan después de eso, por lo que el usuario puede ver la página antes de que la
aplicación JavaScript esté lista. Es posible que algunas funciones aún no funcionen. Deberíamos poner
“indicadores de carga”, o asegurarnos de que el visitante no se confunda con eso.
Async funciona en scripts en línea
Para los scripts que no son módulos, el atributo async solo funciona en scripts externos. Los scripts
asíncronos se ejecutan inmediatamente cuando están listos, independientemente de otros scripts o del
documento HTML.
Para los scripts de módulo, esto también funciona en scripts en línea.
Por ejemplo, el siguiente script en línea tiene async , por lo que no espera nada.
Realiza la importación (extrae ./[Link] ) y se ejecuta cuando está listo, incluso si el documento
HTML aún no está terminado o si aún hay otros scripts pendientes.
Eso es bueno para la funcionalidad que no depende de nada, como contadores, anuncios, detectores de
eventos a nivel de documento.
1 <!-- todas las dependencias se extraen ([Link]), y el script se ejecut
2 <!-- no espera por el documento u otras etiquetas <script> -->
3 <script async type="module">
4 import {counter} from './[Link]';
5
6 [Link]();
7 </script>
8 de 11 24/3/24, 17:56
Módulos, introducción [Link]
Scripts externos
Los scripts externos que tienen type="module" son diferentes en dos aspectos:
1. Los scripts externos con el mismo src sólo se ejecutan una vez:
1 <!-- el script [Link] se extrae y ejecuta sólo una vez -->
2 <script type="module" src="[Link]"></script>
3 <script type="module" src="[Link]"></script>
2. Los scripts externos que se buscan desde otro origen ([Link]. otra sitio web) requieren encabezados CORS,
como se describe en el capítulo Fetch: Cross-Origin Requests. En otras palabras, si un script de módulo
es extraído desde otro origen, el servidor remoto debe proporcionar un encabezado Access-Control-
Allow-Origin permitiendo la búsqueda.
1 <!-- [Link] debe proporcionar Access-Control-Allow-Origin -->
2 <!-- si no, el script no se ejecutará -->
3 <script type="module" src="[Link]
Esto asegura mejor seguridad de forma predeterminada.
No se permiten módulos sueltos
En el navegador, import debe obtener una URL, sea relativa o absoluta. Los módulos sin ninguna ruta se
denominan módulos sueltos. Dichos módulos no están permitidos en import .
Por ejemplo, este import no es válido:
1 import {sayHi} from 'sayHi'; // Error, módulo suelto
2 // el módulo debe tener una ruta, por ejemplo './[Link]' o dondequiera que
Ciertos entornos, como [Link] o herramientas de empaquetado permiten módulos simples sin ninguna ruta,
ya que tienen sus propias formas de encontrar módulos y engancharlos. Pero los navegadores aún no
admiten módulos sueltos.
Compatibilidad, “nomodule”
Los navegadores antiguos no entienden type = "module" . Los scripts de un tipo desconocido
simplemente se ignoran. Para ellos es posible proporcionar una alternativa, utilizando el atributo nomodule :
9 de 11 24/3/24, 17:56
Módulos, introducción [Link]
1 <script type="module">
2 alert("Se ejecuta en navegadores modernos");
3 </script>
4
5 <script nomodule>
6 alert("Los navegadores modernos conocen tanto type=module como nomodule, a
7 alert("Los navegadores antiguos ignoran la secuencia de comandos, desconoc
8 </script>
Herramientas de Ensamblaje
En la vida real, los módulos de navegador rara vez se usan en su forma “pura”. Por lo general, los
agrupamos con una herramienta especial como Webpack y los implementamos en el servidor de producción.
Uno de los beneficios de usar empaquetadores es que dan más control sobre cómo se resuelven los
módulos, permitiendo módulos simples y mucho más, como los módulos CSS/HTML.
Las herramientas de compilación hacen lo siguiente:
1. Toman un módulo “principal”, el que se pretende colocar en <script type="module"> en HTML.
2. Analiza sus dependencias: las importa, y luego importa de esas importaciones, etcétera.
3. Compila un único archivo con todos los módulos (o múltiples archivos, eso es configurable),
reemplazando los llamados nativos de import con funciones del empaquetador. Los módulos de tipo
“especial” como módulos HTML/CSS también son soportados.
4. Durante el proceso, otras transformaciones y optimizaciones se pueden aplicar:
● Se elimina el código inaccesible.
● Se eliminan las exportaciones sin utilizar (“tree-shaking”, sacudir el árbol).
● Las sentencias específicas de desarrollo tales como console y debugger se eliminan.
● La sintaxis JavaScript demasiado moderna (con riesgo de no ser aún soportada) puede transformarse
en una sintaxis más antigua con una funcionalidad equivalente utilizando Babel.
● El archivo resultante se minimiza (se eliminan espacios, las variables se reemplazan con nombres
más cortos, etc).
Si utilizamos herramientas de ensamblaje, entonces, a medida que los scripts se agrupan en un solo archivo
(o pocos archivos), las declaraciones import/export dentro de esos scripts se reemplazan por funciones
especiales de ensamblaje. Por lo tanto, el script “empaquetado” resultante no contiene ninguna import/
export , no requiere type="module" , y podemos ponerla en un script normal:
1 <!-- Asumiendo que obtenemos [Link] desde una herramienta como Webpack --
2 <script src="[Link]"></script>
Dicho esto, los módulos nativos también se pueden utilizar. Por lo tanto no estaremos utilizando Webpack
aquí: tú lo podrás configurar más adelante.
Resumen
10 de 11 24/3/24, 17:56
Módulos, introducción [Link]
Para resumir, los conceptos centrales son:
1. Un módulo es un archivo. Para que funcione import/export , los navegadores necesitan <script
type="module"> . Los módulos tienen varias diferencias:
● Diferido por defecto.
● Async funciona en scripts en línea.
● Para cargar scripts externos de otro origen (dominio/protocolo/puerto), se necesitan encabezados
CORS.
● Se ignoran los scripts externos duplicados.
2. Los módulos tienen su propio alcance local de alto nivel y funcionalidad de intercambio a través de
‘import/export’.
3. Los módulos siempre funcionan en modo estricto.
4. El código del módulo se ejecuta solo una vez. Las exportaciones se crean una vez y se comparten entre
los importadores.
Cuando usamos módulos, cada módulo implementa la funcionalidad y la exporta. Luego usamos import
para importarlo directamente donde sea necesario. El navegador carga y evalúa los scripts automáticamente.
En la producción, se suelen usar paquetes como Webpack para agrupar módulos, para mejor rendimiento y
otras razones.
En el próximo capítulo veremos más ejemplos de módulos y cómo se pueden exportar/importar cosas.
Lección anterior Próxima lección
Compartir Mapa del Tutorial
Comentarios
● Si tiene sugerencias sobre qué mejorar, por favor enviar una propuesta de GitHub o una solicitud
de extracción en lugar de comentar.
● Si no puede entender algo en el artículo, por favor explique.
● Para insertar algunas palabras de código, use la etiqueta <code> , para varias líneas –
envolverlas en la etiqueta <pre> , para más de 10 líneas – utilice una entorno controlado
(sandbox) (plnkr, jsbin, codepen…)
© 2007—2024 Ilya Kantoracerca del proyecto
contáctenos
11 de 11 24/3/24, 17:56