Teoría

Página se editó por última vez hace 3837 días
De alegisdria
Ir a la navegación Ir a la búsqueda

RequireJS[editar | editar código]

Arquitectura Frontend

=========[editar | editar código]

Uso de RequireJS en el Frontend


RequireJS es una librería que permite encapsular en módulos nuestro código JavaScript y cargarlos bajo demanda (llamada `require`).

Para ello es necesario cargar la librería RequireJS en el código HTML de la página en la que se va a utilizar:

   <html>
   <head>
   <script src="require.js" type="text/javascript" 
       data-main="modules/main"></script>
   </head>
   <body>
   </body>
   </html>

En el tag de carga se especifica el módulo inicial que RequireJS cargará y ejecutará. En el ejemplo "modules/main.js".

      1. Árbol de dependencias

Típicamente los módulos consisten en una llamada a `define`, que toma una lista de los módulos y una función de inicialización que se ejecuta cuando las dependencias están satisfechas:

   define([ "map" ], function(map) {
       alert("este módulo depende del módulo" 
           + "map y en este punto map ha sido"
           + "cargado e inicializado");
   });

La función obtiene recibe como parámetros referencias a los módulos que se definen como dependencias, en el mismo orden:

   define([ "layer-list", "map" ], function(layerList, map) {
       // layerList apunta al módulo "layer-list"
       // map apunta al módulo "map"
   });

pero el orden de carga puede variar, ya que por ejemplo, en el caso anterior otro módulo puede haber cargado ya `layer-list`. RequireJS analiza las llamadas define y crea un árbol de dependencias que se cargan en cualquier orden que garantice que las dependencias de un módulo estén todas cargadas e inicializadas cuando el módulo que depende de ellas se inicializa.

De forma más visual:

![image](images/requirejs-transitive.png) para cargar A requireJS realiza una secuencia similar a esta:

- Se detecta la dependencia de A en B y C por lo que pasa a intentar

   cargarlos.

- Se intentar cargar B pero B depende de E, por lo que se intenta

   cargar E.

- Como E no tiene ninguna dependencia se cargar directamente y se

   llama a su código de inicialización.

- Ahora que B tiene todas sus dependencias satisfechas se ejecuta su

   código de incialización.

- RequireJS pasa a cargar C, pero detecta la dependencia en D y en E

   y, como E ya ha sido cargada, pasa a cargar D.

- Como D no tiene ninguna dependencia se cargar directamente y se

   llama a su código de inicialización.

- Ahora C tiene sus dos dependencias ya cargadas, por lo que se llama

   a su código de incialización.

- Con B y C ya cargados se pasa a ejecutar el código de inicialización

   de A.

En la secuencia anterior se ilustra por una parte el hecho de que los módulos en los que depende un módulo son cargados e inicializados previamente. Por otra parte, se muestra que el orden de carga de las dependencias de un módulo no corresponde con el orden en el que están definidas en el código. Por ejemplo, en el caso de C primero se cargó E antes que D porque la dependencia fue cargada por B previamente.

      1. Dependencias de módulos no-RequireJS

En el caso de un módulo que dependa de librerías que no son módulos RequireJS (jquery, openlayers, etc.) es todavía necesario especificar las dependencias, ya que de lo contrario RequireJS puede cargarlas arbitrariamente antes o después. En caso de hacerlo después, el módulo intentará utilizar una librería que no está cargada y se producirá un error.

La forma de especificar la dependencia un modulo tal es idéntica a la de los modulos RequireJS:

   define([ "jquery" ], function($) {
       alert("este módulo depende de jQuery" 
           + "y en este punto jQuery ha sido"
           + "cargado e inicializado");
   });

pero ¿de dónde saca RequireJS el código de jQuery? No lo puede obtener de ningún sitio, por lo que hay que especificárselo. El módulo `main`, que es el módulo de entrada, es el encargado de realizar esta configuración:

   require.config({
       baseUrl : "modules",
       paths : {
           "jquery" : "http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min",
           "jquery-ui" : "http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min",
           "fancy-box" : "../js/jquery.fancybox.pack",
           "openlayers" : "../js/OpenLayers/OpenLayers.debug"
       },
       shim : {
           "fancy-box" : [ "jquery" ]
       }
   });

En el código de `main` se puede observar cómo hay una llamada al método `require.config` que incluye un elemento cuya propiedad `paths` incluye las direcciones de las cuales puede descargar RequireJS cada librería.

También se puede observar cómo en la propiedad `shim` se establecen las dependencias entre módulos no-RequireJS.

      1. Valores de retorno

Se ha mostrado anteriormente cómo las dependencias de un módulo son recibidas como parámetro en la función de inicialización:

   define([ "layer-list", "map" ], function(layerList, map) {
       // layerList apunta al módulo "layer-list"
       // map apunta al módulo "map"
   });

pero, ¿qué contienen exactamente estas referencias?

Contienen el valor que retorna la función de inicialización del módulo dependencia. Por ejemplo, el siguiente módulo `map` en su función de inicialización crea un mapa OpenLayers, añade una capa y la devuelve:

   define([ "openlayers" ], function() {
       var mimapa = new OpenLayers.Map("map");
       mimapa.addLayer(new OpenLayers.Layer.WMS("OpenLayers WMS", "http://vmap0.tiles.osgeo.org/wms/vmap0?", {
           layers : "basic"
       }));
       return mimapa;
   });

Por tanto, cuando el siguiente módulo establezca la dependencia:

   define([ "map" ], function(map) {
       map.setCenter(new OpenLayers.LonLat(-84, 0), 6);
   });

la variable `map` que se pasará a la función de inicialización de éste será el mapa OpenLayers que se creó en el módulo `map` anterior.

      1. Funciones públicas

A veces un módulo debe permitir que los módulos que dependen de él realicen algunas operaciones sobre el mismo. Más adelante se verá que existe otra manera que frecuentemente es mejor, pero de momento mostramos otra manera que también puede ser útil en un momento dado.

Por ejemplo, si queremos hacer un módulo `error` para manejar los errores de la aplicación mostrando un diálogo al usuario y escribiendo el problema en la consola tendríamos una función como ésta:

   function(errorMsg) {
       console.log(errorMsg);
       alert(errorMsg);
   });

Para poder utilizar este módulo tendremos que importarlo en las dependencias de nuestro módulo y cuando se produzca la condición del error hacer una llamada a dicho módulo:

   define([ "error" ], function(error) {
       if (condicion) {
           error.showMessage("Se ha cumplido una condición de error");
       }
   });

Pero para esto tendríamos que devolver un objeto en el módulo `error` que tenga una función `showMessage`. Esto podría hacerse teniendo un módulo `error` como el siguiente:

   define([], function() {
       return {
           showMessage : function(errorMsg) {
               console.log(errorMsg);
               alert(errorMsg);
           }
       };
   });

cuyo valor de retorno tiene una propiedad showMessage que es la función de error.

      1. Funciones privadas

Es posible definir funciones privadas dentro del módulo

      1. Modulo modelo

A continuación se presenta un módulo ejemplo que puede ser utilizado como plantilla para la creación de nuevos módulos:

   define([ "dependencia1", "dependencia2" ], function(modulo1, modulo2) {
       // variables privadas


       // funciones privadas
       // inicialización
       // valor de retorno
       return null;
   });

Valores de retorno de los módulos

Message bus