驴Qu茅 son los m贸dulos en Javascript? 驴Qu茅 es CommonJS, AMD y ECMAScript 6?
Tiempo de lectura: 7.73 minutos
Aprende qu茅 son los m贸dulos en JavaScript: qu茅 es CommonJS, AMD, System.js, require.js, ES2015, ECMAScript6 y Webpack.
A medida que JavaScript se usa con mayor frecuencia, los namespaces
(espacios de nombres donde conviven los identificadores de nuestra aplicaci贸n) y las dependencias se hacen m谩s dif铆ciles de manejar.
Teniendo en cuenta que antes de la llegada de ES6, Javascript no soportaba de forma nativa el uso de m贸dulos, los programadores se las ingenieron para desarrollar sus propios module systems
, aprovechando caracter铆sticas del mismo lenguaje.
Hoy veremos qu茅 alternativas son las m谩s usadas, y la diferencia entre ellas.
Antes de iniciar: 驴Por qu茅 son necesarios los m贸dulos en Javascript?
Si has desarrollado para otras plataformas, es probable que tengas noci贸n de los conceptos de encapsulaci贸n y dependencia.
A帽os atr谩s, la gran mayor铆a de aplicaciones se desarrollaban de forma aislada. Hoy en d铆a, es todo lo contrario.
Es com煤n que alguno de los requerimientos de un sistema que se est谩 desarrollando, se pueda implementar usando como base una soluci贸n ya existente.
En el instante en que se introduce un componente ya existente dentro de un nuevo proyecto, se crea una dependencia entre 茅ste proyecto y el componente utilizado.
Dado que estas piezas necesitan trabajar en conjunto, es importante que no existan conflictos entre ellas.
Entonces, si no realizamos ning煤n tipo de encapsulaci贸n, es cuesti贸n de tiempo para que 2 m贸dulos entren en conflicto.
Esta es una de las razones por las que bibliotecas de C usan un prefijo en sus componentes.
La encapsulaci贸n es esencial para prevenir conflictos y facilitar el desarrollo.
Cuando se trata de dependencias, en el desarrollo JavaScript de lado del cliente, 茅stas se han tratado de forma impl铆cita tradicionalmente.
Es decir, siempre ha sido tarea del desarrollador asegurar que las dependencias se satisfagan al momento de ejectar cada bloque de c贸digo. As铆 mismo, asegurar que estas dependencias se carguen en el orden correcto.
A medida que escribimos m谩s c贸digo Javascript en nuestras aplicaciones, la gesti贸n de dependencias resulta m谩s engorrosa.
Surgen preguntas como: 驴d贸nde debemos poner las nuevas dependencias a fin de mantener el orden apropiado?
Los sistemas de m贸dulos (module systems
) alivian este problema y otros m谩s.
Ellos nacen de la necesidad de "acomodar" el creciente ecosistema de JavaScript.
Veamos qu茅 es lo que aportan las distintas soluciones.
Una primera soluci贸n: The Revealing Module Pattern
Antes de la llegada de los module systems:
Un particular patr贸n de programaci贸n comenz贸 a usarse cada vez con mayor frecuencia en JavaScript: the revealing module pattern
o "el patr贸n del m贸dulo revelador".
var miModuloRevelador = (function () {
var nombre = "Juan Ramos",
saludo = "Hola !";
// Funci贸n privada
function imprimirNombre() {
console.log("Nombre:" + nombre);
}
// Funci贸n p煤blica
function asignarNombre(nuevoNombre) {
nombre = nuevoNombre;
}
// Revelar accesos p煤blicos (opcionalmente con otros nombres)
return {
setName: asignarNombre,
greeting: saludo
};
})();
miModuloRevelador.setName("Carlos");
Los 谩mbitos en Javascript siempre han trabajado a nivel de funci贸n (hasta antes de la aparici贸n de let
en ES2015).
Esto significa que todo lo que se declara dentro de una funci贸n no puede escapar de su 谩mbito.
Es por esta raz贸n que el patr贸n revealing module
se basa en funciones para encapsular el contenido privado (como muchos otros patrones de Javascript).
En el ejemplo anterior, las funciones y variables p煤blicas son expuestas en el objecto devuelto (al final con un return
).
Todas las otras declaraciones est谩n protegidas por el 谩mbito de la funci贸n que las contiene.
Debes tener en cuenta que la variable no est谩 recibiendo la funci贸n directamente, sino m谩s bien el resultado de ejecutar la funci贸n, es decir, el objeto que se devuelve a trav茅s del return
de la funci贸n an贸nima.
Esto se conoce como "Immediately-invoked function expression". Si llevas poco tiempo usando Javascript y te parece confuso, te recomiendo que antes de continuar leas este art铆culo sobre funciones que son invocadas inmediatamente luego de su creaci贸n.
PROS
- Lo suficientemente simple para ser usado donde sea (no requiere bibliotecas u otro soporte adicional).
- M煤ltiples m贸dulos se pueden definir en un solo archivo.
CONTRAS
- No hay forma de importar m贸dulos de forma programada (excepto usando
eval
). - La dependencias deben gestionarse manualmente.
- La carga as铆ncrona de m贸dulos no es posible.
- Las dependencias circulares pueden resultar problem谩ticas.
CommonJS
CommonJS es un proyecto que define una serie de especificaciones para el ecosistema de Javascript, fuera del navegador (por ejemplo, en el lado del servidor o para aplicaciones de escritorio).
Una de las 谩reas que el equipo de CommonJS intenta abordar son los m贸dulos en Javascript.
Los desarrolladores de Node.js originalmente intentaron seguir la especificaci贸n de CommonJS, pero luego cambiaron de decisi贸n.
En lo que se refiere a m贸dulos, la implementaci贸n en Node.js se vio influenciada:
// En circle.js
const PI = Math.PI;
exports.area = (r) => PI * r * r;
exports.circumference = (r) => 2 * PI * r;
// En otro archivo
const circle = require('./circle.js');
console.log('El 谩rea de 1 c铆rculo de radio 4 es: ' + circle.area(4));
Existen abstracciones sobre el sistema de m贸dulos de Node.js, en forma de bibliotecas, que act煤an como un puente entre los m贸dulos de Node.js y CommonJS. En este art铆culo solo vemos las caracter铆sticas b谩sicas.
Tanto en Node como en CommonJS, existen 2 palabras esenciales para interactuar con los m贸dulos: require
y exports
.
-
require
es una funci贸n que se puede usar para importar s铆mbolos desde otro m贸dulo al 谩mbito actual. El par谩metro pasado arequire
es el id del m贸dulo. En la implementaci贸n de Node, es el nombre del m贸dulo dentro de la carpetanode_modules
(o, en todo caso, la ruta hacia su ubicaci贸n). -
exports
es un objeto especial: todo lo que es puesto en 茅l se puede exportar como un elemento p煤blico (conservando el nombre de los elementos).
Los m贸dulos en CommonJS fueron dise帽ados teniendo en mente el desarrollo de lado del servidor. De forma natural, la API es s铆ncrona. Es decir, los m贸dulos son cargados en el momento y en el orden que se requieren dentro de un archivo de c贸digo fuente.
PROS
- Es simple. Un desarrollador puede comprender el concepto sin ver la documentaci贸n.
- Permite la gesti贸n de dependencias: Los m贸dulos requieren otros m贸dulos, y se cargan con el orden solicitado.
-
require
puede ser usado en todo lugar: los m贸dulos se pueden cargar mediante programaci贸n. - Soporta dependencias circulares.
CONTRAS
- Su API s铆ncrona hace que su uso no sea adecuado para ciertos casos (lado del cliente).
- Un archivo por m贸dulo.
- Los navegadores requieren una biblioteca para interpretarlo.
- No hay 1 funci贸n constructora para los m贸dulos (aunque Node lo admite).
Implementaciones
Ya hemos hablado de una implementaci贸n parcial: Node.js
Para el lado del cliente hay 2 opciones populares: webpack y browserify.
Asynchronous Module Definition (AMD)
AMD naci贸 de un grupo de desarrolladores que estaban descontentos con la direcci贸n adoptada por CommonJS. La principal diferencia entre AMD y CommonJS radica en su soporte para la carga as铆ncrona de m贸dulos.
// Llamamos a define y le pasamos 1 arreglo de dependencias y 1 funci贸n que fabrica al m贸dulo
define(['dependencia1', 'dependencia2'], function (dep1, dep2) {
// Devolvemos una definici贸n del m贸dulo
return function () {};
});
// Equivalente a:
define(function (require) {
var dependencia1 = require('dependencia1'),
dependencia2 = require('dependencia2');
return function () {};
});
La carga as铆ncrona en JS es posible usando closures
: una funci贸n es llamada cuando los m贸dulos requeridos terminan de cargar.
La definici贸n e importaci贸n de m贸dulos se lleva a cabo por la misma funci贸n: cuando se define un m贸dulo se indican sus dependencias de forma expl铆cita.
De esta forma, un cargador AMD puede tener una imagen completa del gr谩fico de dependencias para un proyecto determinado en tiempo de ejecuci贸n.
Las bibliotecas que no dependen de otras pueden ser cargadas al mismo tiempo. Esto es muy importante para los navegadores, donde el tiempo de carga inicial es un punto esencial para brindar una buena experiencia de usuario.
PROS
- Carga as铆ncrona (mejores tiempos de inicio).
- Soporta dependencias circulares.
- Es compatible con
require
yexports
. - Gesti贸n de dependencias totalmente integrada.
- Los m贸dulos se pueden separar en m煤ltiples archivos si es necesario.
- Soporta funciones constructoras.
- Soporta plugins (para personalizar los pasos de carga).
CONTRAS
- Sint谩cticamente es un poco m谩s complejo.
- Requiere de bibliotecas de carga, o bien de un proceso de transpilaci贸n.
Implementaciones
Las implementaciones m谩s conocidas de AMD son require.js y Dojo.
Usar require.js es relativamente sencillo. Basta con incluir la biblioteca en nuestro HTML y usar el atributo data-main
para indicar qu茅 m贸dulo debe cargarse primero. Dojo tiene una configuraci贸n similar.
M贸dulos en ES2015
Afortunadamente, el equipo de ECMA (encargado de la estandarizaci贸n de Javascript) decidi贸 abordar el tema de los m贸dulos.
El resultado se puede ver en la 煤ltima versi贸n del est谩ndar Javascript: ECMAScript 2015 (anteriormente conocido como ECMAScript 6). El resultado es sint谩cticamente agradable, y compatible con ambos modos de operaci贸n (de forma s铆ncrona y as铆ncrona).
//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
return x * x;
}
export function diag(x, y) {
return sqrt(square(x) + square(y));
}
//------ main.js ------
import { square, diag } from 'lib';
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5
La directiva import
permite traer m贸dulos al 谩mbito actual.
Esta directiva, en contraste con require
y define
es no din谩mica (es decir, no se puede llamar en cualquier lugar).
La directiva export
, por otro lado, puede usarse para expl铆citamente hacer p煤blicos los elementos.
La naturaleza est谩tica de import
y export
permite a los analizadores est谩ticos construir un 谩rbol completo de las dependencias sin ejecutar c贸digo.
PROS
- Soporta la carga s铆ncrona y as铆ncrona.
- Es sint谩cticamente simple.
- Est谩 integrado en el lenguaje mismo (eventualmente ser谩 soportado en todos lados sin necesidad de bibliotecas).
- Soporta dependencias circulares.
CONS
- A煤n no est谩 soportado en todos lados.
Implementaciones
Desafortunadamente no todos los int茅rpretes de JS soportan ES2015 en sus versiones estables.
Sin embargo, existen "transpiladores" (transpilers
) que a帽aden este soporte.
Un ejemplo es el preset ES2015 para Babel. Babel es un transpiler
, y ES2015 preset
es un plugin que permite transformar c贸digo ES2015 (ES6) en ES5 (la versi贸n t铆pica de Javascript soportada por todos los navegadores desde hace varios a帽os).
Un cargador universal: System.js
驴Deseas que tu proyecto funcione adecuadamente para todos los casos?
System.js es un cargador universal de m贸dulos, que soporta CommonJS, AMD y los m贸dulos de ES2015.
Una mejor alternativa
Hoy en d铆a, Webpack ofrece lo mismo que System.JS y mucho m谩s.
Webpack es un empaquetador de m贸dulos que adem谩s optimiza nuestros archivos para producci贸n, minific谩ndolos y uni茅ndolos seg煤n se requiera (de hecho permite usar loaders
para realizar m谩s tareas durante este proceso).
Usar SystemJS y conseguir lo mismo que permite Webpack implicar铆a usar adicionalmente Gulp, o "SystemJS builder" para empaquetar nuestro proyecto para producci贸n.
Conclusi贸n
Los sistemas de m贸dulos para Javascript surgen como una necesidad de los mismos programadores, de encapsular distintas funcionalidades en "bloques de c贸digo" reutilizables. Estos bloques son llamadas m贸dulos y es importante contar con un mecanismo para gestionar las dependencias entre estos m贸dulos.
Es as铆 como surgen especificaciones, que buscan definir un formato para la importaci贸n y exportaci贸n de m贸dulos, como CommonJS y AMD.
Estas especificaciones tienen sus correspondientes implementaciones con ligeras diferencias.
A fin de poner un poco de orden ante tanto caos, aparece una nueva versi贸n del est谩ndar Javascript: ES2015 (antes conocido como ES6).
Genial. 驴Entonces por qu茅 tanto l铆o?
Lo que pasa es que no todos los navegadores han terminado de implementar este est谩ndar de forma estable, y una gran cantidad de usuarios usa versiones antiguas.
La soluci贸n entonces est谩 en "transformar nuestro c贸digo" en c贸digo que todos los navegadores puedan entender, haciendo uso de transpilers
. O bien usar polyfills
para darle a los navegadores la capacidad de entender caracter铆sticas que aun no han implementado.
Existen muchas alternativas, pero una herramienta que ha tenido bastante acogida 煤ltimamente es Webpack. Esto es porque Webpack no solo soluciona este problema. Tambi茅n optimiza la ejecuci贸n de tareas, de empaquetar nuestro c贸digo y dejarlo listo para producci贸n.
Pero Javascript no est谩 煤nicamente en el lado del cliente. Las especificaciones tambi茅n aplican a Javascript en el lado del servidor. Es por eso que en este art铆culo hemos mencionado a NodeJS.
Llamado a la acci贸n
El ecosistema de Javascript cambia muy amenudo.
Esto se debe en gran parte a que las compa帽铆as tecnol贸gicas m谩s destacadas (entre ellas Facebook, Google, Twitter, Instagram) est谩n siempre en b煤squeda de mejores herramientas. Dejan de usar una para adoptar otra mejor, o crear su propia versi贸n propuesta.
Pero no hay que temerle a estos cambios. Al final, son muchas formas de hacer "casi" lo mismo.
Si deseas aprender Webpack puedes empezar viendo esta serie de videotutoriales.
Si te ha parecido interesante, por favor ay煤dame a compartir este art铆culo.
Cr茅ditos
脡ste art铆culo es una adaptaci贸n de este otro art铆culo publicado originalmente en ingl茅s, en el blog de Auth0.