Foros del Web » Programando para Internet » Javascript »

Importación / Cargador de librerías o módulos

Estas en el tema de Importación / Cargador de librerías o módulos en el foro de Javascript en Foros del Web. Hola gente: Ayer me puse manos a la obra para crear un cargador de librerías, módulos, archivos JS o como sea que cada uno lo ...
  #1 (permalink)  
Antiguo 03/05/2008, 06:10
Avatar de Negora  
Fecha de Ingreso: agosto-2003
Mensajes: 122
Antigüedad: 21 años, 3 meses
Puntos: 5
Importación / Cargador de librerías o módulos

Hola gente:

Ayer me puse manos a la obra para crear un cargador de librerías, módulos, archivos JS o como sea que cada uno lo denomine. Desde un principio tenía claro que habría por ahí muchos ejemplos y cosas ya creadas, pero dado que no esperaba que se complicase el asunto, decidí ponerme por mi cuenta.

Como muchos sabéis, existen muchas maneras de lograrlo, todas ellas válidas. Sin embargo, ese pensamiento se borró rápido cuando se presentaba el siguiente escenario:

1 - Navegador Internet Explorer.
2 - Carga remota (fuera de "localhost").

Lo curioso es que buscando ejemplos y más ejemplos en Internet, comprobé que todos o casi todos caían en esa misma situación. Y los que no, se cargaban por completo la información tan valiosa de "debug" que uno obtiene a través de la consola de errores del navegador (porque usaban "eval").

Explico la situación... El plan más rápido es crear un archivo JS, enlazado en todas las páginas. Éste contiene una serie de instrucciones para cargar el resto de librerías JS (supongamos que todas están en la misma carpeta). El plan más lógico es que al ejecutarse se añadan objetos "script" al documento, sea por el método que sea:

A - Usando document.write ("<script ... ></script>").
B - Creando un objeto "script" mediante los métodos del DOM.
C - Usando document.getElementsByTagName ("<head>") [0].innerHTML += "<script ... ></script>".

Pues bien, Mozilla y Opera, aunque dispongas 20 sentencias así seguidas, esperan a la carga y validación de cada archivo JS para seguir con la página. Sin embargo, IE (al menos la versión 7), parece que no trata de forma síncrona esos elementos cargados en vivo y sigue con el resto de la página simultáneamente. Ignora por completo la propiedad "defer" de "script". Ya sea no escribiéndola en la etiqueta o establecíendola a false si usamos la creación por el DOM, no surte efecto alguno.

Que falle o no depende bastante del escenario debido precisamente a esa asincronía. Para probarlo lo mejor es pulsar rapídisimo la tecla de recarga (F5) con unos archivos con bastante código en un servidor remoto. ¿Qué ningún usuario haría eso? Quizá, pero bastaría una ralentización casual en la descarga para que saltasen los errores. Aparte, que mejor no dejar la funcionalidad al azar...

Por lo tanto, ese método queda descartado por completo si deseamos una compatibilidad 100% con TODOS los navegadores.

Pues bien, la única estrategia para forzar la carga síncrona de librerías viene a través de AJAX, mediante XMLHttpRequest. Lo que se suele hacer es descargar el archivo y emplear "eval (req.responseText)" para cargar los objetos en tiempo real. ¿Inconveniente? Que este método se carga por completo las ventajas de las consolas de error de los diferentes navegadores, ya que cualquier error de las librerías cargadas se asigna a la línea donde se encuentra "eval" y el tipo de error es interpretado como "EvalError".

Dándole vueltas y vueltas a este asunto hoy he llegado a una solución híbrida que funciona al menos en Mozilla, Opera, IE y Safari y que permite seguir recurriendo a la consola de errores.

Este ejemplo, totalmente personal, tiene en cuenta que mis librerías están en "sys/js" y que este JS se llama "load.js". Podéis modificarlo para cargar librerías por parámetro. En este caso yo las he especificado en el propio código y las cargo en el acto.


NOTA: CÓDIGO INCORRECTO. VER SOLUCIÓN MÁS ABAJO

Código:
	// Crea la clase. La uso porque me gusta tener todas las
	// funciones bien clasificadas. No requerirá instanciación.
	function JSLoader () {
	}
	
		// Método de carga.
		JSLoader.load = function () {
		
			// Matriz con los nombres de las librerías deseadas.
			var arr_lib = ["cssquery", "core", "client", "init", "flash", "check", "form", "vfx", "vfxds"];
			
			// Busca objetos "script" ya cargados en el documento, selecciona
			// aquel correspondiente al propio "load.js" y obtiene la ruta base
			// en la que se encuentra, ya que los archivos desde los que se llama
			// a "load.js" pueden estar en diferentes ubicaciones.
			var darr_script = document.getElementsByTagName ("script");
			var urlbase = "";
			for (var i = 0; i < darr_script.length; i++) {
				if (darr_script [i].src.indexOf ("sys/js/load.js") != -1) {
					urlbase = darr_script [i].src.replace ("load.js", "");
					break;
				}
			}
			
			// Cea un objeto para AJAX y uno script.
			var req = new XMLHttpRequest ();
			var script = document.createElement ("script");
			script.type = "text/javascript";
			
			// Recorre la matriz de librerías.
			for (var i = 0; i < arr_lib.length; i++) {
				
				// Descarga el contenido de la librería de forma síncrona.
				req.open ("GET", urlbase + arr_lib [i] + ".js", false);
				req.send (null);
				
				// Carga y valida el contenido de la librería en memoria. Emplea
				// una sentencia "try" para que, en caso de que haya un fallo en
				// el código de una de las librerías, la ejecución continúe hasta el
				// siguiente punto y no provoque un error.
				try {
					eval (req.responseText);
				} catch (_ex) {
				}
				
				// Crea un nuevo objeto "script" que enlaza a la librería. Esto no
				// es necesario para cargarla, pues ya lo está, pero nos permite
				// que en caso de fallo, el navegador obtenga la traza en la consola
				// de error, con su número de línea. No entra en conflicto con los objetos
				// ya creados, ya que si algo ya existe, se sobreescribe sin más.
				script.src = urlbase + arr_lib [i] + ".js";
				darr_script [0].parentNode.appendChild (script.cloneNode (true));

			}
			
		};
		
		// Ejecuta el método.
		JSLoader.load ();

Claves: importación, importador, cargador, librerías, módulos, loader, jsloader, ajax, script, import

Última edición por Negora; 03/05/2008 a las 09:23
  #2 (permalink)  
Antiguo 03/05/2008, 09:38
Avatar de Negora  
Fecha de Ingreso: agosto-2003
Mensajes: 122
Antigüedad: 21 años, 3 meses
Puntos: 5
Re: Importación / Cargador de librerías o módulos

Hola de nuevo:

Si alguien ha probado el código, habrá notado que falla. Se debe a que me olvidé de que "eval" ejecuta el código en el entorno de variables (scope) actual, por lo que todo el contenido de las librerías se carga dentro del objeto y no en window, como debería de ser.

Por todo ello, la solución pasa por ejecutar eval en window, o bien usar el método call (propio de toda función en JavaScript):

Código:
eval.call (window, req.responseText);
Esto funciona bien en Mozilla y Opera pero, para "variar", no en IE. Es absurdo, como siempre.


Para acabar con el problema, he rediseñado la clase, la he hecho instanciable, y la llamo desde window, ya que es la única manera de forzar ese "scope" de variables.

Os presento el nuevo código:

Código:
	// Clase y constructor
	function LibLoader () {
		
		// Matriz de librerías.
		this.arr_lib = ["cssquery", "core", "client", "init", "flash", "check", "form", "vfx", "vfxds"];
		
		// Ruta base de los scripts.
		this.urlbase = "";
		
		// Código absoluto de todas las librerías.
		this.code = "";

		// Matriz de objetos Script.
		this.arr_script = [];
		
		// Llama al método de inicialización.
		this.init ();
		
	}

		// Método de inicialización.
		LibLoader.prototype.init = function () {

			// Obtiene la ruta base de este script, ya que cada documento
			// XHTML se encuentra en diferente lugar y dicha ruta variará.
			var darr_script = document.getElementsByTagName ("script");
			for (var i = 0; i < darr_script.length; i++) {
				if (darr_script [i].src.indexOf ("sys/js/load.js") != -1) {
					this.urlbase = darr_script [i].src.replace ("load.js", "");
					break;
				}
			}
			
			// Crea un objeto para AJAX y otro de tipo Script.
			var req = new XMLHttpRequest ();
			var script = document.createElement ("script");
			script.type = "text/javascript";

			// Recorre la lista de nombres de librería.
			for (var i = 0; i < this.arr_lib.length; i++) {
				
				// Descarga la librería actual de forma síncrona y adjunta su
				// código.
				req.open ("GET", this.urlbase + this.arr_lib [i] + ".js", false);
				req.send (null);
				this.code += req.responseText;
				
				// Crea una copia del objeto Script con la URL de la librería
				// actual.
				script.src = this.urlbase + this.arr_lib [i] + ".js";
				this.arr_script.push (script.cloneNode (true));
				
			}

		};
	
		// Devuelve el código de todas las librerías.
		LibLoader.prototype.getCode = function () {
			return this.code;
		};
		
		// Devuelve la matriz de objetos Script.
		LibLoader.prototype.getScripts = function () {
			return this.arr_script;
		};
	

	// ESTO YA ES CÓDIGO QUE SE EJECUTA EN EL ENTORNO DE WINDOW,
	// NO EN LA CLASE.

	// Instancia la clase LibLoader.
	var $LIBLOADER = new LibLoader ();

	// Carga y evalua todo el código descargado. Se realiza una captura mediante
	// "try" para que en caso de error, la consola no indique que se debe a la
	// sentencia eval. Así, si se produce un error, lo que se hace es adjuntar
	// los objetos Script a la cabecera para que el navegador pueda detectar
	// el error y mostrar su traza en la consola, incluyendo número de línea,
	// lo cual es muy útil.
	try {
		eval ($LIBLOADER.getCode ());
	} catch (_ex) {
		for (var i = 0; i < $LIBLOADER.getScripts ().length; i++) {
			document.getElementsByTagName ("head") [0].appendChild ($LIBLOADER.getScripts () [i]);
		}
	}
Atención: Estás leyendo un tema que no tiene actividad desde hace más de 6 MESES, te recomendamos abrir un Nuevo tema en lugar de responder al actual.
Respuesta




La zona horaria es GMT -6. Ahora son las 18:36.