====== 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:
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.
Demo RMP
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 '' -->