Fundamentos de jQuery

10.3. Gestión de Dependencias

Nota Esta sección esta basada en la excelente documentación de RequireJS y es utilizada con el permiso de James Burke, autor de RequireJS.

Cuando un proyecto alcanza cierto tamaño, comienza a ser difícil el manejo de los módulos de una aplicación, ya que es necesario saber ordenarlos de forma correcta, y comenzar a combinarlos en un único archivo para lograr la menor cantidad de peticiones. También es posible que se quiera cargar código al vuelo luego de la carga de la página.

RequireJS es una herramienta de gestión de dependencias creada por James Burke, la cual ayuda a manejar los módulos, cargarlos en un orden correcto y combinarlos de forma fácil sin tener que realizar ningún cambio. A su vez, otorga una manera fácil de cargar código una vez cargada la página, permitiendo minimizar el tiempo de descarga.

RequireJS posee un sistema modular, que sin embargo, no es necesario seguirlo para obtener sus beneficios. El formato modular de RequireJS permite la escritura de código encapsulado, incorporación de internacionalización (i18n) a los paquetes (para permitir utilizarlos en diferentes lenguajes) e incluso la utilización de servicios JSONP como dependencias.

10.3.1. Obtener RequireJS

La manera más fácil de utilizar RequireJS con jQuery es descargando el paquete de jQuery con RequireJS ya incorporado en él. Este paquete excluye porciones de código que duplican funciones de jQuery.

10.3.2. Utilizar RequireJS con jQuery

Utilizar RequireJS es simple, tan solo es necesario incorporar en la página la versión de jQuery que posee RequireJS incorporado y a continuación solicitar los archivos de la aplicación. El siguiente ejemplo asume que tanto jQuery como los otros archivos están dentro de la carpeta scripts/.

Utilizar RequireJS: Un ejemplo simple

<!DOCTYPE html>
<html>
    <head>
        <title>jQuery+RequireJS Sample Page</title>
        <script src="scripts/require-jquery.js"></script>
        <script>require(["app"]);</script>
    </head>
    <body>
        <h1>jQuery+RequireJS Sample Page</h1>
    </body>
</html>

La llamada a require([.app"]) le dice a RequireJS que cargue el archivo scripts/app.js. RequireJS cargará cualquier dependencia pasada a require() sin la extensión .js desde el mismo directorio que en que se encuentra el archivo require-jquery.js, aunque también es posible especificar la ruta de la siguiente forma:

<script>require(["scripts/app.js"]);</script>

El archivo app.js es otra llamada a require.js para cargar todos los archivos necesarios para la aplicación. En el siguiente ejemplo, app.js solicita dos extensiones jquery.alpha.js y jquery.beta.js (no son extensiones reales, solo ejemplos). Estas extensiones están en la misma carpeta que require-jquery.js:

Un simple archivo JavaScript con dependencias

require(["jquery.alpha", "jquery.beta"], function() {
    //las extensiones jquery.alpha.js y jquery.beta.js han sido cargadas.
    $(function() {
        $('body').alpha().beta();
    });
});

10.3.3. Crear módulos reusables con RequireJS

RequireJS hace que sea fácil definir módulos reusables a través de require.def(). Un modulo RequireJS puede tener dependencias que pueden ser utilizadas para definir un módulo, además de poder devolver un valor — un objeto, una función, u otra cosa — que puede ser incluso utilizado otros módulos.

Si el módulo no posee ninguna dependencia, tan solo se debe especificar el nombre como primer argumento de require.def(). El segundo argumento es un objeto literal que define las propiedades del módulo. Por ejemplo:

Definición de un módulo RequireJS que no posee dependencias

require.def("my/simpleshirt",
    {
        color: "black",
        size: "unisize"
    }
);

El ejemplo debe ser guardado en el archivo my/simpleshirt.js.

Si el modulo posee dependencias, es posible especificarlas en el segundo argumento de require.def() a través de un array) y luego pasar una función como tercer argumento. Esta función será llamada para definir el módulo una vez cargadas todos las dependencias. Dicha función recibe los valores devueltos por las dependencias como un argumento (en el mismo orden en que son requeridas en el array) y luego la misma debe devolver un objeto que defina el módulo.

Definición de un módulo RequireJS con dependencias

require.def("my/shirt",
    ["my/cart", "my/inventory"],
    function(cart, inventory) {
        //devuelve un objeto que define a "my/shirt"
        return {
            color: "blue",
            size: "large"
            addToCart: function() {
                inventory.decrement(this);
                cart.add(this);
            }
        }
    }
);

En este ejemplo, el modulo my/shirt es creado. Este depende de my/cart y my/inventory. En el disco, los archivos están estructurados de la siguiente forma:

my/cart.js
my/inventory.js
my/shirt.js

La función que define my/shirt no es llamada hasta que my/cart y my/inventory hayan sido cargadas, y dicha función recibe como argumentos a los módulos como cart y inventory. El orden de los argumentos de la función debe coincidir con el orden en que las dependencias se requieren en el array. El objeto devuelto define el módulo my/shirt. Definiendo los módulos de esta forma, my/shirt no existe como un objeto global, ya que múltiples módulos pueden existir en la página al mismo tiempo.

Los módulos no tienen que devolver un objeto; cualquier tipo de valor es permitido.

Definición de un módulo RequireJS que devuelve una función

require.def("my/title",
    ["my/dependency1", "my/dependency2"],
    function(dep1, dep2) {
        // devuelve una función para definir "my/title".
        // Este devuelve o establece
        // el titulo de la ventana
        return function(title) {
            return title ? (window.title = title) : window.title;
        }
    }
);

Solo un módulo debe ser requerido por archivo JavaScript.

10.3.4. Optimizar el código con las herramientas de RequireJS

Una vez incorporado RequireJS para el manejo de dependencias, la optimización del código es muy fácil. Descargue el paquete de RequireJS y colóquelo en cualquier lugar, preferentemente fuera del área de desarrollo web. Para los propósitos de este ejemplo, el paquete de RequireJS esta ubicado en una carpeta paralela al directorio webapp (la cual contiene la página HTML y todos los archivos JavaScript de la aplicación). La estructura de directorios es:

requirejs/ (utilizado para ejecutar las herramientas)
webapp/app.html
webapp/scripts/app.js
webapp/scripts/require-jquery.js
webapp/scripts/jquery.alpha.js
webapp/scripts/jquery.beta.js

Luego, en la carpeta en donde se encuentran require-jquery.js y app.js, crear un archivo llamado app.build.js con el siguiente contenido:

Archivo de configuración para las herramientas de optimización de RequireJS

{
    appDir: "../",
    baseUrl: "scripts/",
    dir: "../../webapp-build",
    //Comentar la siguiente línea si se desea
    //minificar el código por el compilador
    //en su modo "simple"
    optimize: "none",

    modules: [
        {
            name: "app"
        }
    ]
}

Para utilizar la herramienta, es necesario tener instalado Java 6. Closure Compiler es utilizado para la minificación del código (en caso que optimize: none esté comentado).

Para comenzar a procesar los archivos, abrir una ventana de comandos, dirigirse al directorio webapp/scripts y ejecutar:

# para sistemas que no son Windows
$ ../../requirejs/build/build.sh app.build.js

# para sistemas Windows
c:\> ..\..\requirejs\build\build.bat app.build.js

Una vez ejecutado, el archivo app.js de la carpeta webapp-build/ contendrá todo el código de app.js más el de jquery.alpha.js y jquery.beta.js. Si se abre el archivo app.html (también en la carpeta webapp-build/) podrá notar que ninguna petición se realiza para cargar jquery.alpha.js y jquery.beta.js.