"El buen diseño es obvio. El gran diseño es transparente"
- Joe Sparano

Módulos en Javascript

6 minutos de lectura
Fecha: 3/4/2018

Los módulos en Javascript se han convertido en uno de los patrones de diseño más usados actualmente. El uso de módulos es el resultado de aplicar la encapsulación de código al lenguaje.

Cuando tenemos una aplicación muy grande el código también será muy extenso lo que lo hace difícil de mantener y propenso a errores.

Uno de los errores más comunes es el de declarar variables con el mismo nombre en distintos puntos y provocar colisiones, o el de definir distintas funciones para hacer tareas similares o idénticas.

Gracias a los módulos podemos encapsular el código y por tanto evitar colisiones, ya que la declaración de variables con el mismo nombre en distintos módulos no provoca el error ya que cada uno tiene su propio scope o dominio.

Es más fácil mantener pequeños módulos que realizan funciones concretas a grandes ficheros donde todo esta mezclado, y con ello a los programadores se nos hace también más sencillo tener una visión general de lo que tenemos para de esta manera evitar repetir código al reutilizar los módulos.

Dicho esto, parece que el tema de usar módulos es maravilloso y todos queremos empezar con ello, pero a veces nos echa para atrás el tema de que existen bastantes maneras de implementar este patrón, distintas librerías o conceptos que hacen que nos preguntemos por donde empezar.

Vamos a ver por encima cuales son estos, como se usan y que ventajas tiene cada uno…

CommonJS

CommonJS es más que un sistema de módulos en Javascript, se trata de un proyecto que marca una serie de especificaciones para trabajar en este lenguaje (entre ellas los módulos) pero orientado al lado del servidor.

De manera que la especificación de módulos que tenemos con CommonJS nos va a servir para aplicaciones de escritorio o para la parte del servidor de las aplicaciones web, lo cual va a ser un problema si queremos enfocarlos en el cliente.

Por supuesto, para usarlo necesitamos de proyectos que implementen estas normas que establece CommonJS y existen algunos.

NodeJs, el más famoso entorno de servidor de Javascript, esta fuertemente influenciado por CommonJS en cuanto a su sistema de módulos, de manera que si ya conoces NodeJS, aunque sea de manera parcial ya sabes también como lo especifica CommonJS.

Como la mejor manera de aprender como funcionan las cosas es preguntarle al que la hizo os dejo la documentación oficial de NodeJS para quien quiera profundizar.

Pero para abreviar, CommonJS define dos palabras para trabajar con módulos, require para importarlo y exports para exportarlo.

Como características:

  1. Podemos usarlos en cualquier punto de nuestra aplicación.
  2. Permiten las dependencias circulares.
  3. Se cargan de forma síncrona (de ahí que este enfocado hacia el servidor).

El ejemplo de uso oficial es este…

//Fichero circle.js

var PI = Math.PI;

exports.area = function (r) {
  return PI * r * r;
};

exports.circumference = function (r) {
  return 2 * PI * r;
};

Como se puede observar con exports que es la manera abreviada de escribir module.exports, que es el objeto que se expone de manera pública cuando el módulo es importado, aunque no es el tema de este artículo, el objeto module.exports puedes ser substituido por:

  • Otro objeto
  • Un constructor
  • Una función

Comento esto porque posiblemente muchos módulos que veamos estén asignando el module.exports de esta manera.

Veamos ahora como se hace la importación.

//Cualquier fichero

var circle = require('./circle.js');
console.log( 'El área de un círculo con radio 4 es ' + circle.area(4));

No hace falta explicación.

AMD (Asynchronous Module Definition)

Viendo la necesidad de que los módulos en Javascript también corrieran en el cliente sin problemas, surgió AMD, que básicamente es lo mismo pero con carga asícrona.

Mediante el uso closures que son funciones que se disparan cuando el módulo ha sido cargado, podemos cargar distintos módulos de forma paralela (reduciendo el tiempo de carga del navegador considerablemente).

Obviamente esto no aplica en todos los casos, aquellas que dependan unas de otras deben ser cargadas en orden.

Para ver un caso práctico veamos un fragmento de código…

define(['dependencia1', 'dependencia2'], function (dep1, dep2) {
    return function() {
      console.log("Las dependencias 1 y 2 han sido cargadas");
    };
});

define(['dependencia3', 'dependencia4'], function (dep3, dep4) {
    return function() {
      console.log("Las dependencias 3 y 4 han sido cargadas");
    };
});

Hemos definido dos procesos que se llevan a cabo de forma asícrona, de manera que las descargas de módulos ocurren en paralelo ganando en velocidad.

Sin embargo en cada uno de los procesos se llaman a dos módulos, estos al ser dependientes entre ellos se cargan por orden, o lo que es lo mismo, de forma síncrona, por ello en el primer proceso, primero se carga la dependencia1 y después la dependencia2.

Las closures son esas funciones que pasan como parámetros los propios módulos, para que de esta manera estén inyectados.

Como características:

  1. Podemos usarlos en cualquier punto de nuestra aplicación.
  2. Permiten las dependencias circulares.
  3. Se cargan de forma asíncrona (válido para cliente y servidor).
  4. Es compatible con require y exports.

La implementación más conocida del modelo AMD es seguramente requireJS, que se define a el mismo de esta manera.

RequireJS es un archivo JavaScript y un cargador de módulos. Está optimizado para uso en el navegador, pero se puede usar en otros entornos de JavaScript, como Rhino y Node. El uso de un gestor de scripts modular como RequireJS mejorará la velocidad y la calidad de su código.

Si quieres aprender como se usar aquí tienes la página oficial de este proyecto.

Módulos ES6

Los módulos en Javascript también pueden ser usados de manera nativa con la especificación ES2015, sin embargo como siempre que hablamos de ES6, tenemos que tener cuidado de que este soportado en nuestros navegadores.

Si antes vimos que para importar el modulo se usaban las palabras clave require o define, en los módulos de ES6 se usa import, en el caso de la exportación es parecido, usamos export (en singular, en las otras soluciones teníamos exports).

Vamos a ver como sería el código del primer ejemplo en ES6.

//Fichero circle.js

var PI = Math.PI;

export function area (r) {
  return PI * r * r;
};

export function circumference (r) {
  return 2 * PI * r;
};

Y la importación ahora se hace así…

//Cualquier fichero

import { area, circumference } from './circle';
console.log( 'El área de un círculo con radio 4 es ' + area(4));

Es muy sencillo y adaptar el código no supone casi esfuerzo. En caso de que nuestro fichero tuviera muchas funciones y quisiéramos importarlas todas, pero no tener que declararlas una a una, podemos usar un alias.

//Cualquier fichero

import * as Circle from './circle';
console.log( 'El área de un círculo con radio 4 es ' + Circle.area(4));

Como características:

  1. No podemos usarlos en cualquier punto de nuestra aplicación, se importa solo al comienzo.
  2. Permiten las dependencias circulares.
  3. Se cargan de forma síncrona y asíncrona (válido para cliente y servidor).
  4. Es nativo, así que podemos prescindir de librerías, pero hay que esperar a que los navegadores lo soporten.

En cualquier caso podemos transpilar el código con Babel si queremos que sea compatible con todos los navegadores, para ello debemos usar algún task-runner o empaquetador de código.

System.js

SystemJS es también un cargador de módulos, como pudiera ser requireJS, pero este tiene la particularidad de que es universal, y por tanto nos sirve para trabajar con módulos que sigan las especificaciones de CommonJS, AMD o ES6.

La gran ventaja de esta librería es que nos permite abstraernos de como estén implementados nuestros módulos y simplemente trabajar de manera sencilla con ellos independientemente de como los construyeran.

Sin duda es una gran librería que nos lo pone fácil para el trabajo con módulos en Javascript, aquí tenéis la página oficial.

Empaquetadores de módulos

Actualmente existen distintos empaquetadores de módulos disponibles para los desarrolladores, por citar algunos, tenemos Browserify, ParcelJS y Webpack.

Para resumir lo que hacen, generan un único archivo con todos los módulos que necesita la aplicación para funcionar, transpila, corre tareas…

Al igual que en el caso de systemJS con los empaquetadores no tenemos que preocuparnos de bajo que especificación se haya implementado el módulo, podemos usar todas las variantes arriba vistas y a mayores hacer las tareas que mencionamos.

De manera que no solo es más fácil desarrollar una aplicación modular sino que la optimización va un paso más allá:

  • Un solo fichero significa una sola llamada
  • Usa estrategias de carga para solo utilizar el código necesario en cada momento.
  • Transpila desde distintos lenguajes como sass, typescript, less, coffescript…
  • Corre tareas de minificado, postprocesado (postcss)

De todas las opciones posibles usar un empaquetador es la más completa, y la que yo considero más práctica.