====== Módulos JavaScript ====== Módulo perteneciente al curso [[informatica:programacion:cursos:programacion_avanzada_javascript|Programación avanzada con JavaScript y ECMAScript]]. ===== Introducción a los módulos ===== A medida que la cantidad de código JavaScript de una aplicación va creciendo, se empieza a hacer evidente que **necesitamos organizar nuestro código y separarlo en distintos ficheros**. Cuando empezamos a separar nuestro código empiezan a surgir algunas cuestiones: * Si definimos una función en un fichero y la usamos en otro, ¿cómo aseguramos que el primer fichero (que define la función) se carga antes del segundo (que la usa)? * ¿Cómo comunicamos los distintos ficheros JavaScript? ¿Cómo pueden compartir datos? Muchos desarrolladores no buscan una respuesta real a esas dos preguntas: la primera la solucionan colocando los //tags// '' El problema está en que la variable ''_value'' no es privada, por lo que cualquiera puede acceder a ella. Eso rompe la encapsulación de ''lib.js''. Muchos desarrolladores usan la convención de que las variables "privadas" empiecen por un subrayado, pero eso no deja de ser una convención: es fácil que por error alguien acceda a una variable (o función) a la que no debería acceder y termine generando algún error. Además las aplicaciones se vuelven más costosas en mantenimiento al no haber una encapsulación real. **Necesitamos un mecanismo para ocultar la parte privada (la variable ''_value'')**. Una solución es usar el ámbito local. En JavaScript las variables locales, definidas dentro de una función, solo son visibles desde la propia función. Y además en JavaScript podemos declarar funciones dentro de funciones. Podemos crear un ámbito privado en ''lib.js'' añadiendo una función que envuelva el código: (function() { let _value = 0; const inc = function() { _value++; } const value = function() { return _value; } })(); Observa como hemos creado una **función anónima autoejecutable**. Fíjate que en la última línea ejecutamos la función (por ello la llamamos autoejecutable). Vale, realmente ahora hemos pasado de un extremo a otro. Si incluyes ''lib.js'' y luego intentas llamar a ''_value'' verás que ya no es accesible (lo esperado). El problema es que ni ''inc()'' ni ''value()'' lo son tampoco. ¿Cómo podemos exponer las dos funciones "hacia afuera", para que sean accesibles, a la vez que mantenemos ''_value'' privada? Hay varias maneras de hacer esto, pero por regla general todas son variaciones de la misma técnica fundamental: se trata de colocar en el contexto global un objeto que contenga solo lo que se quiera hacer público. Veamos un ejemplo de cómo podría quedar nuestro fichero ''lib.js'': let l$ =(function() { let _value = 0; const inc = function() { _value++; } const value = function() { return _value; } return { inc, value }; })(); Observa las dos diferencias. Por un lado, ahora la función anónima autoejecutable devuelve un valor. Este valor es un objeto con todo lo que deseamos hacer público (en este caso las funciones ''inc()'' y ''value()''). Y por otro lado, en el mismo fichero ''lib.js'' declaramos una variable global (''l$'') y le asignamos el valor de retorno de la función anónima autoejecutable. Así pues, el resultado de incluir el script ''lib.js'' en nuestro código será que se cree la variable ''l$'', la cual contendrá los métodos (y variables) públicos que deseamos: A este patrón lo llamamos comúnmente "**el patrón de módulo**". Dicho patrón nos permite que nuestros ficheros JavaScript tengan un estado privado y expongan hacia el exterior solo la parte pública que deseemos. La idea fundamental es que los métodos privados se definen dentro de la función anónima autoejecutable y los métodos públicos se exponen a través de un objeto que se devuelve en dicha función anónima autoejecutable. Existen varias variantes del patrón de módulo. La que hemos visto aquí es una de las más usadas y se conoce con el nombre de **Revealing Module Pattern** (RPM). Un RMP es un módulo que cumple las siguientes características adicionales: * Todos los miembros públicos y privados se definen dentro de la función anónima autoejecutable. * El objeto que se devuelve contiene simplemente referencias a algunos de los métodos (o variables) definidos en la función anónima autoejecutable. El //Revealing Module Pattern// es quizá, el más usado de los patrones de módulo, pero no es el único. Cada uno tiene sus peculiaridades y se diferencian, básicamente, en cómo se exponen las funciones (y variables) públicas "hacia afuera". ===== DEMO: Revealing Module Pattern ===== La idea de un módulo es tener un ámbito donde el módulo pueda tener sus variables y métodos privados y exponer solo lo que quiera. function MathModule() { // Esta será la función privada. function _sumar(a, b) { return a + b; } // Queremos que esta sea la función pública function inc(a) { return _sumar(a, 1); } } No podríamos usar tal cual este ''MathModule'' porque no devuelve nada ni podemos acceder a sus funciones. Vamos a exportar la función ''inc'' que es la que nos interesa: function MathModule() { // Esta será la función privada. function _sumar(a, b) { return a + b; } // Queremos que esta sea la función pública function inc(a) { return _sumar(a, 1); } return { inc } } Seguimos sin poder usar la función ''inc'' directamente, pero sí lo podemos usar así: var m$ = MathModule(); m$.inc(10); // -> 11 Lo normal es tener la función anterior como función anónima autoejecutable asignada a una variable global: var m$ = (function () { // Esta será la función privada. function _sumar(a, b) { return a + b; } // Queremos que esta sea la función pública function inc(a) { return _sumar(a, 1); } return { inc } })(); Ahora podemos usarlo así: m$.inc(10); // -> 11 Otra forma anotación que se suele encontrar es: (function () { function MathModule() { // Esta será la función privada. function _sumar(a, b) { return a + b; } // Queremos que esta sea la función pública function inc(a) { return _sumar(a, 1); } return { inc } } window.m$ = MathModule(); })(); Y lo podríamos usar igual que antes. En un navegador, al declarar algo en ''window'', es equivalente a declarar una variable de ámbito global. Podríamos también escribirlo así: (function (wnd) { function MathModule() { // Esta será la función privada. function _sumar(a, b) { return a + b; } // Queremos que esta sea la función pública function inc(a) { return _sumar(a, 1); } return { inc } } wnd.m$ = MathModule(); })(window || global); El ámbito global en NodeJS se llama ''global'', así que el código anterior valdría tanto para navegador como para NodeJS. ===== DEMO: Gestión de las dependencias ===== El principal problema que tiene el sistema de módulos visto anteriormente es que no se puede saber que un módulo depende de otro para su funcionamiento ni forzar que el motor de ejecución de JavaScript ejecute un módulo antes que otro. Supongamos un fichero ''sumar.js'' con el siguiente contenido: let s$ = (function () { function sumar(a, b) { return a + b; } return {sumar} })(); Y tenemos otro módulo en un fichero llamado ''math.js'' que usará la función sumar anterior: (function (wnd) { function MathModule() { function inc(a) { // Aquí usamos la función 'sumar' del otro módulo return s$.sumar(a, 1); } return { inc } } wnd.m$ = MathModule(); })(window || global); Existe ahora una dependencia entre los dos módulos. Si no se carga ''sumar.js'', ''s$'' no existirá y dará error al usar ''inc'', será ''undefined''. Manualmente, donde carguemos los ficheros JavaScript, podemos establecer el orden necesario: Demo RMP

Demo RMP

Este método no es mantenible para aplicaciones grandes. Un sistema de módulos real debe proporcionar soporte para: * Definir un módulo que tiene parte privada y parte pública. * Definir que un módulo depende de otro módulo. * Asegurar que si un módulo A depende de B, el módulo B será cargado automáticamente antes que el módulo A. ECMAScript 2015 da soporte a esas necesidades. ===== El sistema de módulos CommonJS ===== **CommonJS es un sistema de módulos** que hizo su aparición con NodeJS y es el sistema de módulos usado por éste. Recuerda que un sistema de módulos además de proporcionar una sintaxis para definir nuestros módulos, tiene también un sistema para que **los módulos expresen sus dependencias**. De esta manera el cargador de módulos sabe en qué orden debe cargar los módulos para que funcionen correctamente. Un módulo de CommonJS tiene una sintaxis muy simple: * Un módulo CommonJS se corresponde a un fichero de código fuente JavaScript. * Todas las variables declaradas en el módulo son privadas a ese módulo, a no ser que se exporten. * El módulo puede **exportar un solo elemento**, que puede ser una función, una variable o un objeto cualquiera. En la práctica si un módulo CommonJS quiere exportar dos funciones, exporta un objeto que contiene a ambas funciones. * El módulo puede **requerir otros módulos para su funcionamiento**. Cuando requiere un módulo, este módulo es cargado antes (por el cargador de módulos, de forma automática) y el valor exportado por el módulo requerido puede ser usado dentro del módulo que lo requiere. Nota: Lo explicado en esta lección se aplica explícitamente a NodeJS, aunque es posible usar CommonJS también en navegadores, como veremos enseguida. ==== Declarando un módulo CommonJS ==== Para exportar un módulo se coloca en ''module.exports'' el valor a exportar. Así, el módulo que vimos anteriormente, declarado en CommonJS quedaría de la siguiente manera (el siguiente código podría estar en un fichero llamado ''libcj.js''): let _value = 0; const inc = function() { _value++; } const value = function() { return _value; } module.exports = {inc, value}; Observa la simplicidad del código: no necesitamos ninguna función anónima autoejecutable. La variable ''_value'' ya es privada: todo es privado al módulo por defecto. Al final, en lugar de devolver algo lo que hacemos es colocar en ''module.exports'' el dato a exportar, es decir lo que es público. En este caso queremos hacer públicas las funciones ''inc()'' y ''value()'', así que exportamos un objeto que contiene ambas funciones. ==== Importando módulos CommonJS ==== Lo que nos falta es ver cómo podemos requerir, o importar, un módulo CommonJS. En NodeJS no tenemos etiquetas ''

Demo RMP

===== El sistema de módulos AMD ===== **AMD (//Asynchronous Module Definition//) es un sistema de módulos** pensado para que los módulos se carguen de forma asíncrona. Está pues diseñado pensando en los navegadores, donde es más lógico ir cargando los distintos módulos de forma asíncrona. Es cierto que en muchos casos verás que se usan //bundles// y es debido a que en HTTP/1.1 se intenta minimizar las conexiones al servidor (teóricamente es mejor cargar un //bundle// con todos los módulos de golpe, que realizar varias conexiones para ir cargando los módulos uno a uno). De todos modos es posible crear //bundles// a partir de módulos AMD y usar AMD te da la libertad de, en cualquier momento, decidir si quieres usar un //bundle// o bien ir cargándolos de forma asíncrona. Con total seguridad cuando HTTP/2 se use masivamente, este sistema se popularizará (aunque, lo más deseable sería que se popularizara el sistema de módulos de ES2015 que también soporta carga asíncrona). **Esa es la principal diferencia con CommonJS, que solo soporta carga síncrona de módulos**. ==== Declarando un módulo AMD ==== Declarar un módulo AMD es muy simple ya que usa una sintaxis parecida a la del patrón de módulo clásico de JavaScript. Toda definición de módulo empieza con una llamada a la función ''define'' que recibe dos parámetros: * Un array con **las dependencias del módulo**. Esto es, los módulos que deseamos cargar antes de nuestro módulo y que son necesarios para su funcionamiento. Dicho array está formado por cadenas con el nombre de los módulos a cargar. * Una función **con el código de nuestro módulo**. Esta función vendría a ser la equivalente de la función anónima auto-ejecutable del patrón de módulo clásico. Por cada módulo que hayamos indicado como prerrequisito en el array de dependencias, esta función recibe un parámetro. Este parámetro es el valor exportado por el módulo. Así, si el array tiene dos elementos, la función que contiene nuestro módulo aceptará dos parámetros, que serán los dos valores exportados por los dos módulos indicados en el array de requisitos. Para exportar un elemento basta con devolverlo desde la función que contiene el código de nuestro módulo. Es decir, **lo que devolvamos en la función que contiene el código del módulo** (la que pasamos como segundo parámetro a ''define'') **es lo que exportamos**. Al igual que en CommonJS solo podemos exportar una sola cosa (una función, un valor, un objeto, ...). El siguiente código define un módulo AMD: define([], function() { let _value = 0; const inc = function() { _value++; } const value = function() { return _value; } return {inc, value}; }); Observa la llamada a ''define'', con el array de dependencias (vacío, este módulo no depende de ningún otro) y la función con el código de nuestro módulo que exporta un objeto simplemente devolviéndolo. ==== Importando un módulo AMD ==== El concepto de "importar" un módulo no existe directamente en AMD: simplemente hemos de declarar dicho módulo en el array de dependencias (y añadir opcionalmente el parámetro en la función que contiene el código de nuestro módulo): define(['./lib_cj'], function(counter) { let x = counter.value(); // x == 0 counter.inc(); let y = counter.value(); // y == 1 }); Al contener el array de dependencias un valor (''lib_cj'') la función que define nuestro módulo acepta un parámetro (que es el valor exportado por el módulo ''libcj.js''). El sistema de módulos se encarga de asegurar que el módulo ''lib_cj.js'' esté cargado antes de ejecutar nuestro módulo. ==== Usando AMD ==== Si quieres usar módulos AMD desde un navegador debes usar un cargador de módulos que soporte AMD, ya que los navegadores no los soportan directamente. Un "cargador de módulos" no es nada más que una librería JavaScript que contiene la función ''define'' y toda la infraestructura necesaria para cargar los módulos según la especificación de AMD. Hay dos que suelen usarse: [[http://requirejs.org/|RequireJS]] y [[https://github.com/systemjs/systemjs|SystemJs]]. Básicamente la configuración mínima consiste en cargar la librería, configurar el cargador (p. ej. indicarle cuál es la ruta base de los módulos) y cargar explícitamente el módulo inicial. Los detalles de configuración no están definidos por AMD por lo que dependen de la librería usada, aunque son muy parecidos. El siguiente código hace una configuración básica de SystemJS: Consulta la página web del cargador que utilices para ver todas las opciones disponibles. ==== DEMO: AMD ==== A diferencia de CommonJS, donde los módulos se cargan de forma síncrona, AMD está pensado para carga **asíncrona**. Convertiremos los módulos que teníamos con CommonJS a la sintaxis AMD. Fichero ''sumar.js'': define([], function() { function sumar(a, b) { return a + b; } // Lo que devolvamos, es lo que el módulo exporta return sumar; }); Vamos al fichero ''math.js'': define(['./sumar.js'], function(sumar) { function inc(a) { return sumar(a, 1); } return {inc: inc}; }); Vamos con ''main.js'': define(['./math.js'], function(math) { console.log(math.inc(10)); }); Vamos con la transformación de la página HTML. Como la carga de módulos se hará de forma asíncrona, necesitamos de un **cargador** de módulos. Aquí veremos **System.JS**. Lo instalamos mediante **[[https://bower.io/|bower]]** (gestor de bibliotecas de JavaScript): npm install -g bower Una vez lo tenemos instalado, lo usamos para instalar [[https://github.com/systemjs/systemjs|System.JS]]: bower install system.js Se habrá creado la carpeta ''bower_components'' con un directorio ''system.js''. Nos interesa el directorio ''dist'' donde veremos los ficheros de ''system.js''. Demo RMP

Demo RMP

===== Módulos ECMAScript 2015 ===== Una de las grandes novedades de ECMAScript 2015 fué precisamente que definía un sistema de módulos estándar para JavaScript. Puede parecer superfluo e innecesario existiendo ya CommonJS o AMD, pero la idea es tener un único sistema de módulos que permita compartir éstos de forma sencilla, tanto en escenarios de servidor (Node.js) como de cliente (navegadores). Cuando apareció ES2015 el sistema de módulos estaba perfectamente definido: se establecieron tanto su sintaxis como sus posibilidades. Pero eso es solo una parte de lo necesario para tener un "sistema de módulos" funcional: necesitamos disponer también de un **cargador de módulos**, es decir la parte del //runtime// que se encargará de cargar los distintos módulos desde los ficheros de disco. 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]]: {{ :informatica:programacion:cursos:programacion_avanzada_javascript:21-es6-dynamic-module-soporte-caniuse.png |}} 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 ''

Test

Si no tenemos un servidor web, podemos usar ''[[https://github.com/websockets/ws|ws]]'': ws -p 5000 Accederíamos entonces a ''%%http://localhost:5000%%''. Si queremos que browserify nos añada también un fichero //source maps//: ./node_modules/.bin/browserify c.js -t babelify --outfile bundle.js --debug Si no queremos teclear tanto, podemos añadir el comando al ''package.json'', en la sección de ''scripts'': { // ... "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "bundle": "browserify c.js -t babelify --outfile bundle.js --debug" }, // ... } Entonces lo usaríamos de la siguiente manera: npm run bundle ==== DEMO: Módulos ES6 ==== Ahora veremos la conversión de los módulos ''sumar.js'', ''math.js'' y ''main.js'' a módulos ECMAScript 2015 (ES6). Empezaremos con ''sumar.js'': export default function sumar(a, b) { return a + b; } // Antes teníamos lo siguiente: /* define([], function() { function sumar(a, b) { return a + b; } return sumar; }) */ Otra manera de exportar el módulo, sería guardando la función en una constante: const sumar = (a, b) => a + b; export default sumar; Vamos con ''math.js'': import s from './sumar'; function inc(a) { return s(a, 1); } export default {inc: inc}; // Antes teníamos: /* define([], function(sumar) { function inc(a) { return sumar(a, 1); } return {inc: inc} }); */ El fichero principal, ''main.js'': import math from './math'; console.log(math.inc(10)); // Antes teníamos: /* define(['./math.js'], function(math) { console.log(math.inc(10)); }); */ Vamos a crear el //bundle//: npm run bundle Y ya podríamos usarlo en el fichero HTML. ==== Exports nombrados ==== Una de las limitaciones existentes tanto en CommonJS como en AMD es la imposibilidad de exportar más de un valor. En CommonJS se exporta siempre el valor de ''module.exports'' y en AMD se exporta lo que se devuelva en la función que contiene el módulo. Por supuesto puedes exportar un objeto que contenga tres funciones pero no puedes exportar las tres funciones por separado. El sistema de módulos de ECMAScript 2015 sí que lo permite. Lo habitual es que cada módulo exporte un valor por defecto, ya hemos visto como: usando ''export default''. Pero un módulo puede **exportar otros valores además del valor por defecto** o incluso no exportar valor por defecto y exportar otros valores. A los otros valores exportados por un módulo se les llama "**exports nombrados**". Quizá te puede parecer innecesario que un módulo pueda exportar varios elementos. Pero tiene mucha utilidad si adoptas un punto de vista de desarrollo más funcional. En este paradigma tus módulos pueden ser colecciones de funciones relacionadas, puedes exportarlas todas y luego cada módulo cliente puede elegir exactamente cuáles quiere importar. Vamos a ver un ejemplo de un módulo que exporte dos funciones. Asumamos que el código está en ''math_functions.js'': export function add(...a) { let accum = 0; for (let v of a) {accum += v;} return accum; } export function substract(start, ...a) { let accum = start; for (let v of a) {accum -= v;} return accum; } Observa cómo se usa ''export'' sin ''default'' para importar las dos funciones. A esos exports los llamamos "exports nombrados". Recuerda: un módulo puede exportar tan solo un valor por defecto y todos los nombrados que desee. ==== Importación de exports nombrados ==== ¿Cómo podemos importar un export nombrado? Pues seleccionamos todos aquellos que queremos exportar y aplicamos la desestructuración: import {add} from './math_functions'; // importamos el export nombrado 'add' let r1 = add(1,2,3); let r2 = substract(10,1,2,3); // Error: substract no está definido Observa ahora por un lado que la función ''substract'' no está definida dentro del segundo módulo ya que no la hemos importado. Y por otro lado, ahora la función ''add'' se ha importado dentro del //namespace// del propio módulo. Si quisiéramos importar tanto ''add'' como ''substract'' podemos hacerlo en una sola sentencia ''import'': import {add, substract} from './math_functions'; ==== Asignar un nombre local a los exports nombrados ==== Al importar un ''export'' nombrado el nombre se preserva, por lo que en el módulo que importa el elemento se llamará tal y como se llamaba en el módulo importado (en nuestro ejemplo ''add''). ¿Hay alguna manera de cambiarlo? Pues la respuesta es que sí, y se debe usar esa sintaxis especial: import {add as sum} from './math_functions'; // importamos el export nombrado 'add' let r1 = sum(1,2,3); let r2 = add(1,2,3); // Error: add no está definido La clave es el ''as sum''. Eso asigna el nombre ''sum'' al ''export'' nombrado ''add''. Así en nuestro módulo ahora debemos usar siempre ''sum'' ya que a todos los efectos ''add'' no existe. ==== DEMO: Exports nombrados ==== Usaremos el entorno que creamos antes para ejecutar módulos ES6. Fichero ''math.js'': // Función que suma todos los parámetros export function sum(...a) { let accum = 0; for (let v of a) { accum += v; } return accum; } // Cálculo de la media de los valores export function avg(...a) { if (a.length == 0) { return 0; } return sum(...a) / a.length; } Ya tenemos listos los módulos para importarlos. Creamos ahora el fichero ''main.js'' con: import {sum, avg} from './math'; console.log(sum(1, 2, 3)); console.log(avg(1, 2, 3)); Creamos el //bundle//: npm run bundle Iniciamos un servidor web local (podemos usar [[https://www.npmjs.com/package/local-web-server|local-web-server]], de Node.js): ws -p 5000 Y navegamos a ''%%http://localhost:5000%%''. Nos fijamos en la pestaña **Consola** de las herramientas para desarrolladores. Podríamos cambiar el nombre de lo que importamos de los módulos. Por ejemplo, cambiando el nombre de ''avg'' a ''average'': import {sum, avg as average} from './math'; console.log(sum(1, 2, 3)); console.log(avgerage(1, 2, 3)); ==== Importar todos los exports ==== Cuando un módulo de ECMAScript 2015 tiene varios "exports nombrados" podemos importarlos todos de golpe usando el comodín ''*'' (asterisco) en el ''import'': import * as mf from './math_functions'; let v1 = mf.add(1,2,3,4); let v2 = mf.substract(10,1,2,3); **Cuando se usa el asterisco debe indicarse forzosamente un espacio de nombres**, mediante el uso de ''as''. En este ejemplo **todos los exports nombrados del módulo ''math_functions.js'' se incorporarán en el espacio de nombres ''mf''**. De ahí que usemos ''mf.add'' y ''mf.substract'' en lugar de ''add'' y ''substract'' directamente. ==== Exports nombrados y por defecto ==== Un módulo ECMAScript **puede tener un export por defecto y tantos exports nombrados como desee**. Cuando sea el caso debemos tener presente que: * ''import m from './module''' importará el export por defecto del modulo ''e2'' * ''import m, {e1 as y1,e2 as y2} from './module''' importará el export por defecto del modulo ''e2'' con el nombre local ''y2'' * ''import m, * as x from './module''' importará el export por defecto del modulo ''module.js'' y le asignará el nombre local ''m'' y también importará todos los exports nombrados (uso de ''*'') dentro del espacio de nombres ''x'' Como se puede ver, ¡la sentencia ''import'' es realmente flexible y potente! ==== DEMO: Uso de import con comodín ==== Tenemos el módulo ''math.js'' que exporta 2 elementos nombrados: ''sum'' y ''avg''. 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; } En el fichero ''main.js'' podremos hacer lo siguiente para importar los dos: import * as m from './math'; console.log(m.sum(1,2,3)); console.log(m.avg(1,2,3)); Creamos el //bundle//: npm run bundle Iniciamos un servidor web local (podemos usar [[https://www.npmjs.com/package/local-web-server|local-web-server]], de Node.js): ws -p 5000 Y navegamos a ''%%http://localhost:5000%%''. Nos fijamos en la pestaña **Consola** de las herramientas para desarrolladores. ==== ¿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: export function foo() {} export class Bar {} Podemos tener el siguiente código: function foo() {} // No hay export class Bar {} // No hay export // exportamos ambos exports nombrados de golpe export {foo, Bar}; 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 ==== 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 '' 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 ''%%

Demo cargar modulos


Como vemos, añadimos un módulo inicial, ''bootstrap.js'': 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); 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 ''