Si desarrollas aplicaciones web con alta carga de código en cliente, es posible que tengas que desarrollarte tus propias funciones JavaScript. Incluso tu propia librería o framework si es el caso.
Una opción es abrir tu editor de texto favorito, crear un archivo .js y empezar a teclear. Total, jQuery viene en un solo archivo .js, ¿verdad? Antes de hacer esto, párate a pensar un poco: ¿a qué nunca colocarías todo el código c# de tu proyecto en un solo fichero .cs? Tenerlo en varios ficheros permite localizar el código más rápidamente, tenerlo mejor organizado y evitar conflictos cuando se trabaja en equipo. Pues bien, eso mismo aplica a JavaScript.
Por supuesto los desarrolladores de jQuery (y de cualquier otra librería decente) no trabajan en un solo fichero. Tienen su código distribuido en ficheros separados que se combinan al final para crear la librería. Vamos a ver como podemos hacer esto de forma sencilla.
Lo primero que tenemos que hacer es precisamente definir los módulos de nuestra librería. Básicamente un módulo es un fichero .js, que define un conjunto de funciones que son accesibles a todos los clientes que usen el módulo. El código de dichos ficheros se organiza siguiendo un patrón que se conoce precisamente como patrón de módulo (revealing module pattern o alguna de sus variantes).
El problema está que en JavaScript no hay (de momento) ninguna manera de establecer las dependencias entre módulos: es decir, de indicar que el módulo A, depende del módulo B (lo que quiere decir que el módulo B debe estar cargad antes que el módulo A). Es ahí donde entra una librería llamada requirejs. Dicha librería permite especificar las dependencias entre módulos. Veamos un ejemplo de módulo con soporte para require:
- define([], function () {
- var rnd = function () {
- return 42;
- };
- var isOdd = function (i) {
- return (i % 2) != 0;
- };
- return {
- rnd: rnd,
- isOdd: isOdd
- };
- });
Lo importante ahí es la funcion define. Dicha función está definida en requirejs y suele tomar dos parámetros:
- Un array con las dependencias del módulo.
- Una función con el código del módulo. Dicha función puede recibir como parámetro los módulos especificados en las dependencias.
El módulo define dos funciones (rnd y isOdd) y luego las exporta. Exportarlas significa que las hace visibles al resto de módulos que dependan de este. La forma de exportar es devolviendo un resultado: todo lo que se devuelve, es exportado.
Ahora vamos a declarar otro módulo (llamémosle main.js) que hará uso de este módulo que hemos definido:
- define([‘math/rnd’], function (math) {
- var addOnlyIfOdd = function (a, b) {
- var result = 0;
- if (math.isOdd(a)) result += a;
- if (math.isOdd(b)) result += b
- return result;
- };
- return {
- addOnlyIfOdd: addOnlyIfOdd
- };
- });
Es lo mismo que antes con la diferencia de que ahora el array de dependencias contiene el módulo (math/rnd). Este es nuestro módulo anterior. El nombre de un módulo es el nombre del fichero js tal cual (incluyendo la ruta) (existen los llamados módulos AMD donde esto no tiene porque ser así, pero tampoco es relevante ahora). Por lo tanto, en este código, el módulo anterior lo habíamos guardado en math/rnd.js Dentro de este módulo podemos usar todo lo que el módulo anterior exportaba a través del parámetro math.
¿Como usaríamos eso desde una página web?
Lo primero es cargar requirejs desde la página web y usar data-main para indicarle a require el módulo “inicial”:
- <!DOCTYPE html>
- <head>
- <title>Demo</title>
- <script data-main=«./main.js» src=«http://requirejs.org/docs/release/2.1.14/minified/require.js»></script>
- </head>
- <body>
- </body>
Si ejecutamos este fichero html y miramos la pestaña Network veremos como no solo se carga el fichero requirejs si no que tanto main.js como también math/rnd.js se cargan: require ha cargado tanto el módulo inicial como todas sus dependencias.
Esto nos permite dividir nuestro código JavaScript con la tranquilidad de que luego los scripts se cargarán en el orden correcto, gracias a requirejs.
Vale, pero estarás diciendo que jQuery viene en un solo js único y para cargar jQuery no debes usar require para nada. Y tienes razón. La realidad es que la gente de jQuery (y tantos otros frameworks) tienen su código dividido en módulos pero luego los unen todos en el orden correcto en un solo js. Y eso lo hacen cuando “construyen” jQuery. Vamos a ver un ejemplo usando gulp (en jQuery se usa grunt que es similar).
Gulp es un sistema de build basado en JavaScript. Se basa en node (así que es necesario tener node instalado). Pero teniendo node instalado, instalar gulp es seguir dos pasos:
- Ejecutar npm install –g gulp. Eso instala gulp a nivel “global”. Debe ejecutarse una sola vez.
- Ejecutar npm install –save-dev gulp. Eso debe ejecutarse desde el directorio donde tenemos el código fuente de nuestra librería. Eso instala gulp a nivel local y debe hacerse una vez por cada librería instalada.
El tercer paso es crear un fichero javascript, llamado gulpfile.js que será el que tenga las distintas tareas para generar nuestro proyecto JavaScript.
Para el ejemplo vamos a suponer que tenemos nuestro fichero main.js y el fichero math/rnd.js dentro de una subcarpeta src. El fichero gulpfile.js está en la carpeta que contiene a /src. Es decir tenemos una estructura tal que así:
Ahora vamos a crear una tarea en gulp para combinar todos los módulos de nuestro proyecto en un .js final. Para ello tenemos que instalar requirejs como módulo de node mediante npm install –save-dev requirejs (desde el directorio de nuestro proyecto).
Finalmente podemos crear una tarea, llamada “make” en nuestro gulpfile:
- var gulp = require(‘gulp’);
- var requirejs = require(‘requirejs’);
- gulp.task(‘make’, function () {
- var config = {
- baseUrl: ‘src’,
- name: ‘main’,
- out: ‘dist/demo.js’,
- findNestedDependencies: true,
- skipSemiColonInsertion: true,
- optimize: ‘none’
- };
- requirejs.optimize(config, function (buildResponse) {
- console.log(buildResponse);
- });
- });
Cargamos los módulos de node que vamos a usar (gulp y require) y luego usamos requirejs.optimize para cargar el módulo incial, junto con todas sus dependencias y generar un solo .js. Esto es precisamente lo que hace la llamada a requirejs.optimize. El primer parámetro es la configuración. Existen muchas opciones de configuración, siendo las más importantes:
- baseUrl: Directorio base donde están los módulos
- name: Módulo inicial
- out: Fichero de salida
- optimize: Si debe minimificar el archivo de salida o no.
Una vez hemos modificado el fichero gulpfile.js ya podemos ejecutarlo, mediante gulp make. Esto invocará la tarea ‘make’ del fichero gulpfile:
Una vez ejecutado en el directorio dist tendremos un fichero (demo.js) con todos nuestros módulos combinados en el orden correcto. Este sería el fichero que distribuiríamos y que se usaría desde el navegador.
Eso nos permite tener nuestro código javascript bien separado y organizado, con la tranquilidad de que siempre podemos generar la versión unificada “final” usando gulp.
Por supuesto gulp sirve para mucho más que combinar los módulos. Puede minimificar salidas, pasar tests unitarios, realizar optimizaciones adicionales, tareas propias… en fin, cualquier cosa que uno esperaría de un sistema de builds.
¡Un saludo!