Herramientas de usuario

Herramientas del sitio


informatica:programacion:cursos:programacion_avanzada_javascript:modulos_javascript

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Ambos lados, revisión anteriorRevisión previa
Próxima revisión
Revisión previa
informatica:programacion:cursos:programacion_avanzada_javascript:modulos_javascript [2024/10/29 15:31] – [Importar todos los exports] tempwininformatica:programacion:cursos:programacion_avanzada_javascript:modulos_javascript [2024/10/30 16:07] (actual) – [Recursos] tempwin
Línea 611: Línea 611:
 Lamentablemente cuando salió ES2015, el cargador de módulos no estaba definido, ni tampoco la API que debía usarse para configurarlo. Esto significaba que **no había en el mercado ningún motor de ECMAScript que soportase los módulos de ES2015 de forma nativa**. Ningún navegador, ni Node.js en ninguna de sus versiones. Afortunadamente **eso ha cambiado y en la actualidad tenemos una especificación completa** del sistema de módulos, y todas las versiones modernas de navegadores //evergreen// ya podemos usarlo, tal y [[https://caniuse.com/#feat=es6-module-dynamic-import|como nos indica caniuse]]: Lamentablemente cuando salió ES2015, el cargador de módulos no estaba definido, ni tampoco la API que debía usarse para configurarlo. Esto significaba que **no había en el mercado ningún motor de ECMAScript que soportase los módulos de ES2015 de forma nativa**. Ningún navegador, ni Node.js en ninguna de sus versiones. Afortunadamente **eso ha cambiado y en la actualidad tenemos una especificación completa** del sistema de módulos, y todas las versiones modernas de navegadores //evergreen// ya podemos usarlo, tal y [[https://caniuse.com/#feat=es6-module-dynamic-import|como nos indica caniuse]]:
  
-<WRAP center round todo 60%> +{{ :informatica:programacion:cursos:programacion_avanzada_javascript:21-es6-dynamic-module-soporte-caniuse.png |}}
-soporte de módulos ES6 dinámicos +
-</WRAP>+
  
 La imagen anterior es para el soporte de módulos dinámicos, que son aquellos que se cargan usando el propio lenguaje ECMAScript, a través de la palabra clave ''import'' (de una forma similar a como usábamos ''require'' en módulos CommonJS). Pero el sistema de módulos de ES2015 va mucho más allá y **se soporta también la carga de módulos usando etiquetas ''<script>''**. De nuevo [[https://caniuse.com/#feat=es6-module|caniuse nos muestra el soporte]] de esta otra característica: La imagen anterior es para el soporte de módulos dinámicos, que son aquellos que se cargan usando el propio lenguaje ECMAScript, a través de la palabra clave ''import'' (de una forma similar a como usábamos ''require'' en módulos CommonJS). Pero el sistema de módulos de ES2015 va mucho más allá y **se soporta también la carga de módulos usando etiquetas ''<script>''**. De nuevo [[https://caniuse.com/#feat=es6-module|caniuse nos muestra el soporte]] de esta otra característica:
  
-<WRAP center round todo 60%> +{{ :informatica:programacion:cursos:programacion_avanzada_javascript:21-es6-script-module-soporte-caniuse.png |}}
-soporte de módulos ES6 con etiqueta script +
-</WRAP> +
 ==== Definiendo un módulo ECMAScript 2015 ==== ==== Definiendo un módulo ECMAScript 2015 ====
  
Línea 1058: Línea 1053:
 ==== DEMO: Uso de import con comodín ==== ==== DEMO: Uso de import con comodín ====
  
 +Tenemos el módulo ''math.js'' que exporta 2 elementos nombrados: ''sum'' y ''avg''.
 +
 +<code javascript>
 +export function sum(...a) {
 +    let accum = 0;
 + 
 +    for (let v of a) {
 +        accum += v;
 +    }
 + 
 +    return accum;
 +}
 + 
 +export function avg(...a) {
 +    if (a.length == 0) {
 +        return 0;
 +    }
 +    return sum(...a) / a.length;
 +}
 +</code>
 +
 +En el fichero ''main.js'' podremos hacer lo siguiente para importar los dos:
 +
 +<code javascript>
 +import * as m from './math';
 +
 +console.log(m.sum(1,2,3));
 +
 +console.log(m.avg(1,2,3));
 +</code>
 +
 +Creamos el //bundle//:
 +
 +<code bash>
 +npm run bundle
 +</code>
 +
 +Iniciamos un servidor web local (podemos usar [[https://www.npmjs.com/package/local-web-server|local-web-server]], de Node.js):
 +
 +<code bash>
 +ws -p 5000
 +</code>
 +
 +Y navegamos a ''%%http://localhost:5000%%''. Nos fijamos en la pestaña **Consola** de las herramientas para desarrolladores.
 ==== ¿Qué podemos exportar? ==== ==== ¿Qué podemos exportar? ====
  
 +**Un módulo ECMASCript 2015 puede exportar** (ya sea de forma nombrada o por defecto) **cualquier tipo de declaración**. Podemos exportar pues:
 +
 +  * Variables y/o constantes que pueden contener objetos, valores simples o funciones (incluyendo generadores)
 +  * Clases
 +  * Funciones
 +
 +==== Exports anónimos ====
 +
 +Los exports por defecto pueden ser anónimos. Eso significa que podemos exportar sin necesidad de colocar nombre alguno a lo que estamos exportando. Eso solo podemos hacerlo en los exports por defecto (ya que el módulo que importa siempre indica un nombre local para los exports por defecto). **Los exports nombrados siempre deben tener nombre**.
 +
 +Por lo tanto el siguiente código es correcto (supongamos que está en el fichero ''export''. Así en lugar de tener el siguiente código:
 +
 +<code javascript>
 +export function foo() {}
 +export class Bar {}
 +</code>
 +
 +Podemos tener el siguiente código:
 +
 +<code javascript>
 +function foo() {}    // No hay export
 +class Bar {}    // No hay export
 +// exportamos ambos exports nombrados de golpe
 +export {foo, Bar};
 +</code>
 +
 +En ambos casos el resultado es el mismo (dos exports nombrados (''foo'' y ''Bar'')). Usa la sintaxis que prefieras porque son totalmente equivalentes.
 +
 +La segunda sintaxis usa un enfoque parecido al //revealing module pattern//: se declara todo lo necesario dentro del módulo y al final se exporta todo lo público. En el caso del //revealing// se usa una sentencia ''return'' que devolvía un objeto conteniendo todos los elementos exportados del módulo. En este caso usamos la sentencia ''export''.
 +
 +Acostúmbrate a usar el sistema de módulos de ECMAScript. Recuerda que **hoy ya se soporta de serie en todos los navegadores modernos (e incluso en Node.js)** y en el caso de que debas soportar otros motores puedes usar un transpilador. Así que ¡no hay excusa para no utilizar módulos en tus aplicaciones! aumenta la legibilidad y mantenibilidad de nuestro código al permitir una mayor organización. El sistema de módulos de ES6 es lo suficientemente flexible y potente como para ser usado tanto en pequeños desarrollos como en grandes proyectos. No dudes en experimentar con él y en acostumbrarte a sus posibilidades... ¡pronto descubrirás que no puedes vivir sin él!
 ==== Utilizar módulos desde HTML ==== ==== Utilizar módulos desde HTML ====
 +
 +Hasta ahora has visto como cargar módulos **dinámicamente**, es decir a través del propio lenguaje. Pero los módulos se pueden cargar de forma estática a partir de una etiqueta ''<script>''. El uso más común de esto es utilizar dicha etiqueta para **cargar el módulo inicial desde una página web**. Dicho módulo inicial posteriormente cargará el resto de módulos usando ''import''.
 +
 +Si no existiese este soporte, el sistema de módulos se quedaría "cojo" para aplicaciones web, ya que no tendríamos ningún mecanismo para cargar dicho módulo inicial.
 +
 +=== Cargar un módulo usando <script> ===
 +
 +Para cargar un fichero como si se tratase de un módulo, basta con usar ''%%type="module"%%'' en la etiqueta ''<script>''. Al usar este valor de ''type'' todas las reglas de módulos se aplican a dicho fichero (ya sabes: modo estricto por defecto, ''this'' vale ''undefined'' y las declaraciones son locales al módulo). Por supuesto, eso implica que dicho código puede usar ''import'' y ''export'' para cargar dinámicamente otros módulos y exponer resultados respectivamente.
 +
 +No hay sintaxis definida para "importar" nada de lo que exporten los módulos cargados mediante ''<script>''. Es decir, los valores que exporte un módulo cargado de esta manera, "se pierden", ya que no pueden recogerse. Debes entender que la idea de cargar un módulo con ''<script>'' no es "importar" elementos de dicho módulo, sino **que dicho módulo tenga código ejecutable que interaccione con el DOM de la página**. Dado que estos módulos se están ejecutando en un navegador tienen acceso al contexto global de ese y por lo tanto a ''document''.
 +
 +<WRAP center round important 60%>
 +Importante: Los módulos cargados vía el tag ''<script>'' con el atributo ''%%type="module"%%'' se comportan como si tuvieran el [[https://www.jasoft.org/Blog/post/El-mejor-sitio-para-colocar-mis-scripts-191;Cabecera-cuerpo-carga-asincrona-o-carga-diferida.aspx|atributo ''defer'']] aplicado, es decir **no bloquean el //parser// de HTML mientras el script se carga**, y no se ejecutan hasta que el documento esté cargado por completo.
 +</WRAP>
 +
 +Por lo tanto dado el siguiente código:
 +
 +<code html>
 +<script type="module" src="1.js"></script>
 +<script src="2.js"></script>
 +<script defer src="3.js"></script>
 +</code>
 +
 +El orden de ejecución de los scripts es ''2.js'', ''1.js'' y ''3.js''.
 +
 +=== El atributo "nomodule" ===
 +
 +De igual modo que ahora disponemos de ''%%type="module"%%'' para indicar al navegador que cargue un determinado fichero ECMAScript como un módulo, también hay el atributo ''nomodule'', que se usa **para indicar que, si el navegador soporta módulos, entonces NO cargue dicho script**. De este modo podemos tener dos versiones de nuestro código:
 +
 +  * La versión con módulos, que cargaríamos via ''%%<script type="module">%%''
 +  * La versión sin módulos, que cargaríamos con ''<script nomodule>''. Los navegadores que soportan módulos, no cargarán esa versión.
  
 ==== DEMO: Cargar módulos desde HTML ==== ==== DEMO: Cargar módulos desde HTML ====
 +
 +Página ''index.html'' con un cuadro de texto:
 +
 +<code html>
 +<!DOCTYPE html>
 +<head>
 +    <title>Demo cargar módulos</title>
 +    <!-- Como queremos añadir un script JS como módulo, 
 +    usaremos el atributo "type" con el valor "module" para indicarlo
 +    -->
 +    <script type="module" src="./bootstrap.js"></script>
 +</head>
 +<body>
 +    <h1>Demo cargar modulos</h1>
 +    <input type="text" width="50" id="txtOne" value="0"/> <br />
 +    <input type="button" width="50" id="btnInc" />
 +</body>
 +</html>
 +</code>
 +
 +Como vemos, añadimos un módulo inicial, ''bootstrap.js'':
 +
 +<code javascript>
 +import {next} from './inc.js'
 +const txt = document.getElementById('txtOne');
 +const btn = document.getElementById('btnInc');
 +btn.addEventListener('click', () => {
 +    const value = parseInt(txt.value, 10);
 +    txt.value = next(value);
 +}, false);
 +</code>
 +
 +Como vemos, en ese módulo no se exporta nada. Esto pasa con los módulos que se cargan a través de la etiqueta ''<script>''.
 +
 +El módulo ''inc.js'':
 +
 +<code javascript>
 +import sum from './sum.js'
 +const next = i => sum(i,1);
 +export {sum, next};
 +</code>
 +
 +El módulo ''sum.js'':
 +
 +<code javascript>
 +export default (a,b) => a+b;
 +</code>
 +
 +
 +<WRAP center round info 60%>
 +Como hemos visto, ya no es necesario Babel o ningún otro transpilador en las últimas versiones de los navegadores porque soportan los módulos de forma nativa.
 +</WRAP>
  
 ==== DEMO: Soporte de módulos ES6 en NodeJS ==== ==== DEMO: Soporte de módulos ES6 en NodeJS ====
  
 +<WRAP center round important 60%>
 +Este código tiene varios años, es muy probable que ya no funcione igual o que las notas que acompañen esta sección estén desfasadas
 +</WRAP>
  
 +Fichero ''index.mjs'':
 +
 +<code javascript>
 +import {next} from './inc.mjs'
 +const value = 42;
 +console.log(next(value));
 +</code>
 +
 +<WRAP center round info 60%>
 +Node.js obliga que los ficheros de módulos tengan la extensión ''.mjs''.
 +</WRAP>
 +
 +Fichero ''.inc.mjs'':
 +
 +<code javascript>
 +import sum from './sum.mjs'
 +const next = i => sum(i,1);
 +export {sum, next};
 +</code>
 +
 +Fichero ''sum.mjs'':
 +
 +<code javascript>
 +export default (a,b) => a+b;
 +</code>
 +
 +Ejecutamos con Node.js indicando la característica experimental:
 +
 +<code bash>
 +node --experimental-modules index.mjs
 +</code>
  
 +===== Recursos =====
  
 +  * [[https://github.com/systemjs/systemjs|SystemJS]]
 +  * [[https://browserify.org/|browserify]]
 +  * [[https://webpack.github.io/|webpack]]
 +  * [[https://requirejs.org/|RequireJS]]
 +  * [[https://www.npmjs.com/package/local-web-server|NodeJS local web server]]
informatica/programacion/cursos/programacion_avanzada_javascript/modulos_javascript.1730212309.txt.gz · Última modificación: por tempwin