Requirejs: El cargador de m贸dulos para JavaScript

Dentro del mundo de la programaci贸n existe un alto porcentaje de desarrolladores que detestan trabajar con JavaScript 馃檨 Existen diferentes motivos y hoy me quiero centrar en uno de los comunes, para contaros despu茅s una estupenda soluci贸n 馃檪

JavaScript, archivos y dependencias entre ellos.

Para poder explicarlo he creado un peque帽o ejemplo con una funcionalidad de lo m谩s b谩sica: He creado una p谩gina que me permite lanzar una serie de mensajes por la consola de desarrollo. Este ser铆a el ejemplo de lo que espero:

ejemplo requirejs

El objeto encargado de hacer las llamadas a la funci贸n console ser谩 el siguiente:

var log = function () {
    var
        info = function (msg) {
            console.log(msg);
        },
        warning = function (msg) {
            console.warn(msg);
        },
        error = function (msg) {
            console.error(msg);
        };

    return {
        info: info,
        warning: warning,
        error: error
    };
}();

Pero no quiero utilizar el objeto log directamente, sino que me gustar铆a a帽adir un objeto m谩s que me permita indicar el tipo de mensaje que quiero transmitir (obviamente no es un caso real, es para entender el escenario con algo muy b谩sico :)). Para ello hemos creado un objeto trace que determina el tipo de mensaje y utiliza el objeto log para mostrarlo.

var trace = function () {

    return {
        message: function (msg, type) {

            switch (type) {
                case "info":
                    log.info(msg);
                    break;
                case "warning":
                    log.warning(msg);
                    break;
                case "error":
                    log.error(msg);
                    break;
                default:
                    return;
            }
        }
    };
}();

Para poder a帽adir el evento al bot贸n y hacer uso de trace, tenemos un 煤ltimo archivo llamado bootstrapper.js:

(function () {
    
    var btn = document.getElementById("btn");

    btn.addEventListener("click", function () {

        var msg = document.getElementById('msg'),
            type = document.getElementById('type');

        trace.message(msg.value, type.value);

    });

})();

La p谩gina HTML que utilizamos para ejecutar este c贸digo JavaScript es la siguiente:

<!DOCTYPE html>
<html>
<head>
    <title>Bad solution</title>
</head>
<body>
    <section>
        <input type="text" id="msg" />
        <select id="type">
            <option>info</option>
            <option>warning</option>
            <option>error</option>
        </select>
        <button id="btn">Send</button>
    </section>
    <script src="scripts/3.log.js"></script>
    <script src="scripts/2.trace.js"></script>
    <script src="scripts/1.bootstrapper.js"></script>
</body>
</html>

He enumerado los archivos para que se vea claramente que los mismos deben tener un orden descendente. El motivo es bastante obvio: boostrapper no puede llamar a trace si no ha sido declarado previamente y trace no puede utilizarse hasta que exista el objeto log. Para comprobarlo basta con cambiar el orden de los scripts y comprobar desde la consola el error que supone el orden incorrecto en la carga:

ejemplo de error no definido-no requirejs

RequireJS

Debido a este caso en concreto, en aplicaciones con cierta carga en el cliente puede complicar tanto el desarrollo como la colaboraci贸n con otros desarrolladores, lo que hace que en ocasiones se descarte este lenguaje para muchos proyectos.
RequireJS est谩 aqu铆 para solucionar este inconveniente 馃榾 Se trata de una librer铆a que nos permite cargar archivos en el orden correcto y bajo demanda. La aplicaci贸n se vuelve modular, aparecen dependencias entre unos archivos y otros, con lo que adem谩s mejora la calidad de nuestro c贸digo. Veamos el mismo ejemplo con Requirejs:

En este caso, vamos a comenzar por bootstrapper.js, donde hemos a帽adido informaci贸n adicional:

(function () {

    requirejs.config({
        baseUrl: 'scripts2'
    });


    var btn = document.getElementById("btn");

    btn.addEventListener("click", function () {
        require(['2.trace'], function (trace) {

            var msg = document.getElementById('msg'),
                type = document.getElementById('type');

            trace.message(msg.value, type.value);
            
        });

    });

})();

En primer lugar, hemos indicado a trav茅s de la configuraci贸n de requirejs d贸nde est谩n nuestros archivos JavaScript dentro de nuestra aplicaci贸n (en este caso en una carpeta llamada scripts2). Seguimos haciendo uso del bot贸n pero hemos decorado el contenido con require, pas谩ndole entre corchetes ‘2.trace’ y una funci贸n an贸nima donde queda ubicado el c贸digo que ya ten铆amos. 驴Qu茅 significa? Si leemos a la inversa, lo que estamos diciendo es que para poder lanzar esa petici贸n a trace.message es necesario (require) el m贸dulo llamado 2.trace y, una vez que el mismo se cargue se podr谩 ejecutar dicho c贸digo. Como pod茅is imaginar, 2.trace es el archivo js que contiene nuestro objeto trace visto anteriormente, con algunas mejoras :):

define('2.trace', //Module name
    ['3.log'], //Dependencies
    function (log) { //Module implementation

        return {
            message: function (msg, type) {

                switch (type) {
                    case "info":
                        log.info(msg);
                        break;
                    case "warning":
                        log.warning(msg);
                        break;
                    case "error":
                        log.error(msg);
                        break;
                    default:
                        return;
                }
            }
        };
    });

Si comparamos este c贸digo con el anterior, lo que hemos hecho ha sido encapsular el contenido dentro de define, donde debemos indicar:

  • Nombre del m贸dulo: Debe de coincidir con el nombre del archivo que lo contiene. De no ser as铆 requirejs no lo encontrar谩. De hecho, se aconseja no indicar el nombre del modulo para que el mismo sea m谩s portable y f谩cil de mover a otras ubicaciones.
  • Dependencias de este m贸dulo: Podemos indicar el nombre de otros m贸dulos que sean necesarios para la correcta ejecuci贸n del que estamos definiendo.
  • Implementaci贸n del m贸dulo: Contenido del m贸dulo. Para poder hacer uso de las dependencias tendremos tantos argumentos como dependencias hayamos indicado.

En este caso, tenemos como dependencia el archivo/modulo 3.log, donde la nueva implementaci贸n es similar que la de 2.trace:

define('3.log', //Module name
    [], //Dependencies
    function () { //Module implementation
        var
            info = function (msg) {
                console.log(msg);
            },
            warning = function (msg) {
                console.warn(msg);
            },
            error = function (msg) {
                console.error(msg);
            };

        return {
            info: info,
            warning: warning,
            error: error
        };
    });

En el caso de que un m贸dulo no tenga dependencias de otros, simplemente indicamos un array vac铆o.

Por 煤ltimo el c贸digo HTML se ha visto reducido con este cambio:

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
    <section>
        <input type="text" id="msg" />
        <select id="type">
            <option>info</option>
            <option>warning</option>
            <option>error</option>
        </select>
        <button id="btn">Send</button>
    </section>
    <script data-main="scripts2/1.bootstrapper" src="scripts2/require.js"></script>
</body>
</html>

Si os dais cuenta, en el mismo s贸lo se est谩 referenciando a require.js con una particularidad: utiliza el atributo data- de HTML 5 para poder indicar cu谩l es el punto de entrada de la aplicaci贸n (en este caso bootstrapper.js). Tambi茅n es posible referenciar los dos archivos por separado, requirejs y bootstrapper, pero creo que esta forma es m谩s elegante 馃檪

Tambi茅n es importante recalcar que los archivos se descargan bajo demanda, lo que supone una mejora en la carga de la p谩gina. Para poder probar este 煤ltimo punto, podemos ejecutar la aplicaci贸n con la pesta帽a de Red abierta y veremos que inicialmente se carga require.js y bootstrapper.js, pero trace.js y log.js s贸lo ser谩n cargados cuando sean necesarios, es decir cuando se ha clic en el bot贸n 馃槈

Espero que haya sido de utilidad.

隆Saludos!

Proyecto de ejemplo