miércoles, 29 de junio de 2011

Optimización de código javascript de los botones sociales. Mejorando tiempo de carga en web

Este es mi primer post, espero que le sucedan muchos más y que todos sirvan de utilidad a la comunidad que los lea.
Desde ahora mismo, mi agradecimiento por leerlos, así como por vuestras opiniones y comentarios.
Bien, vamos allá:
De entrada difícil ponerle título, yo le llamaría:
Mejora del tiempo de carga de una web, optimizando el código javascript de los diversos "botones sociales",  haciéndolo re-utilizable.

Objeto del post:
Cuando se añaden "botones sociales" a una web, se incluye código javascript que poco a poco retrasa su tiempo carga.
Esto puede llevar a una penalización en el SEO y en las posibles campañas SEM.
Recordemos que en ambos (SEO y SEM), el Tiempo de carga de una web es un factor a tener en cuenta.

El problema:
El otro dia, programando los llamados "botones sociales" (Buzz, Digg, Follow de Twitter, Like de Facebook, Google +1, etc) en la web de un cliente, nos dimos cuenta de un problema.
Todos los fabricantes de "botones sociales" (la mayoria), suministran un código javascript para "inyectarlo" allí donde se quiera ubicar el botón social correspondiente.

Veamos un par de ejemplos:
Código Buzz:
<a title="Publicar en Google Buzz" class="google-buzz-button"
href="http://www.google.com/buzz/post"
data-button-style="small-count" data-locale="es"></a>
<script type="text/javascript" src="http://www.google.com/buzz/api/button.js">
</script>
Resultado:



Código Follow de Twitter:
<a href="http://twitter.com/WManagerService"
class="twitter-follow-button" data-lang="es">Follow @WManagerService</a>
<script type="text/javascript" src="http://platform.twitter.com/widgets.js" >
</script>
Resultado:


Pero si lo programas/añades "tal cual" a la web, enseguida aparecen los problemas:
  1. Código javascript "desparramado, desordenado y duplicado" por dentro de la web en cantidad importante.
  2. El código javascript se ejecuta en el navegador del cliente cuando se detecta y eso relentiza el tiempo de carga de la web.
Hacia la solución, por partes:
Primero ponemos todos los scripts al principio de la página (en el <head>), ya que con sólo cargarlos una vez es suficiente, además, los tendremos ordenados y minimizaremos el problema 1.
<head>
...
...
<script type="text/javascript" src="http://www.google.com/buzz/api/button.js">
</script>
<script type="text/javascript" src="http://platform.twitter.com/widgets.js" >
</script>
...
...
</head>
Siguiendo con el ejemplo de Buzz y Twitter, el resto de código, si que se debe ubicar allí donde tenga que aparecer el botón, repitiéndose en cuantas ocurrencias se deseen del mismo.
<a title="Publicar en Google Buzz" class="google-buzz-button"  
href="http://www.google.com/buzz/post"
data-button-style="small-count" data-locale="es">
</a>



y
<a href="http://twitter.com/WManagerService"
class="twitter-follow-button data-lang="es">
Follow @WManagerService</a>


Vale, genial, lo hacemos para todos los botones que queremos poner, que son muchos..... y...Problema 1 minimizado, pero el problema 2, principal objeto de este post, sigue allí.

Nota: Recordemos que el código de los botones sociales se componen de "2 partes", la carga del javascript (".js") y el código de ubicación, que se debe poner allí donde se desee aparezca el botón. Aquí vamos a tratar de optimizar el proceso de carga del ".js".

Buscando en Google como solucionar el problema del tiempo de carga de ficheros javascript (".js"), encontramos el sistema de carga asíncrona, es decir, el navegador lanza un proceso paralelo de carga del fichero javascript (".js"), mientras sigue con el proceso de carga general de la página web, al final todos confluyen y la página se carga completa. Perfecto, es lo que andamos buscando.

Veamos como es el código de carga asíncrono que aporta el propio Twitter:
<script type="text/javascript">
  (function(){
    var twitterWidgets = document.createElement('script');
    twitterWidgets.type = 'text/javascript';
    twitterWidgets.async = true;
    twitterWidgets.src = 'http://platform.twitter.com/widgets.js';
    document.getElementsByTagName('head')[0].appendChild(twitterWidgets);
  })();
</script>
Para Buzz seria algo así:
<script type="text/javascript">
  (function(){
    var buzzWidgets = document.createElement('script');
    buzzWidgets.type = 'text/javascript';
    buzzWidgets.async = true;
    buzzWidgets .src = 'http://platform.twitter.com/widgets.js';
    document.getElementsByTagName('head')[0].appendChild(buzzWidgets);
  })();
</script>
y así para cada "botón social"... bufff!, cuanto código!!!

Pero espera... este código me suena...

¿Esto no es lo mismo que el codigo asíncrono para Google analytics, que ya tenemos en la web?
<script type="text/javascript">
  var _gaq = _gaq || [];
  _gaq.push(['_setAccount', 'UA-XXXXXX-X']);
  _gaq.push(['_trackPageview']);

  (function() {
    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  })();
</script>

No, no es lo mismo, pero se le parece....mmmmmm,  procedamos a su estudio:
Esta es la parte del código de Google Analytics que activa el proceso asíncrono de carga de sus rutinas ".js": 

  (function() {
    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  })();
Y esta la de Twitter:
<script type="text/javascript">
  (function(){
    var twitterWidgets = document.createElement('script');
    twitterWidgets.type = 'text/javascript';
    twitterWidgets.async = true;
    twitterWidgets.src = 'http://platform.twitter.com/widgets.js';
    document.getElementsByTagName('head')[0].appendChild(twitterWidgets);
  })();
</script>

No son iguales, pero, son muy parecidas, al fin y al cabo deben hacer lo mismo: Activar un proceso asíncrono para la carga de un fichero javascript (".js").

Entonces, si logramos hacer una función en lenguaje javascript para la "carga asíncrona de un fichero javascript" y la utilizamos para todos....podríamos:
  1. Limpiar/reducir código javascript .
  2. Minimizar el problema 2 (tiempo de carga).
Vamos a intentarlo:
<head>
...
...
<!-- Funcion general de carga asincrona  -->

<script type="text/javascript">
    function CreateAsyncProcess(url_process) {
         var AsyncProcess = document.createElement('script');
        AsyncProcess.type = 'text/javascript';
        AsyncProcess.async = true; 
        AsyncProcess.src = url_process;   
        document.getElementsByTagName('head')[0].appendChild(AsyncProcess); 
    }
</script>
...
...
</head>

Bien !!!. Veamos que tenemos:
  • Tenemos una sola función que podemos llamar cuantas veces se quiera (código re-utilizable).
  • Tenemos un parámetro por donde nos llega la url del ".js" a cargar desde el servidor que sea.
  • "Enganchamos" el proceso asíncrono que lanzamos, con el 'head' (última línea de la función), que es donde la tenemos definida, y es una etiqueta que siempre existe en el html.
Ahora debemos utilizar esta función y que "cuadre" con todos los "botones sociales" y ya de paso, con Google Analytics, Skype Contact button, Google Translator, etc.. (sí, ya lo sé, me gusta complicarme la vida).

Vamos a ello: 
<head>
...
...
<script type="text/javascript">

    function CreateAsyncProcess(url_process) {
         var AsyncProcess = document.createElement('script');
        AsyncProcess.type = 'text/javascript';
        AsyncProcess.async = true; 
        AsyncProcess.src = url_process;   
        document.getElementsByTagName('head')[0].appendChild(AsyncProcess); 
    }
//
-- Analytics --
    var _gaq = _gaq || [];  
    _gaq.push(['_setAccount', 'UA-XXXXX-X']); 
    _gaq.push(['_trackPageview']);
    CreateAsyncProcess((('https:' == document.location.protocol) ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js');

//!-- Post on Digg --

   CreateAsyncProcess('http://widgets.digg.com/buttons.js');

//-- Post on Buzz --

  CreateAsyncProcess('http://www.google.com/buzz/api/button.js');
 

//-- Share on LinkedIn --
   CreateAsyncProcess('http://platform.linkedin.com/in.js');

//-- Like on Facebook -- 

  CreateAsyncProcess('http://connect.facebook.net/es_ES/all.js#xfbml=1');

//-- Follow on Twitter --

   CreateAsyncProcess('http://platform.twitter.com/widgets.js');

//-- Google +1 --

   CreateAsyncProcess('https://apis.google.com/js/plusone.js');

//-- Contact Skype --
 CreateAsyncProcess('http://download.skype.com/share/skypebuttons/js/skypeCheck.js');

//-- Google Translator-- 
   function googleTranslateElementInit() {
       new google.translate.TranslateElement({
           pageLanguage: 'es',
           includedLanguages: 'ca,es,en,de,fr,it,pt',
           gaTrack: true,
           gaId: 'UA-XXXXXX-X',
           floatPosition: google.translate.TranslateElement.FloatPosition.TOP_RIGHT
       }, 'google_translate_element');
    }
   CreateAsyncProcess('http://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit');

</script>
...
...
</head>

Nota: Volver a recordar que el código de los "botones sociales" (y los otros procesos) se componen de "2 partes", la carga del javascript (".js") y el código de ubicación, que se debe poner allí donde se desee aparezca el botón. Aquí estamos optimizando el proceso de carga del ".js".

Vamos a ver que tenemos...
  1. Código re-utilizable creando un proceso asíncrono para cada llamada que ejecutamos.
  2. La función de carga asíncrona de Analytics que ya teniamos implementada en la web, la hemos retocado y hemos comprobado que siga funcionando con Analytics.
  3. Hemos extendido su uso a la carga de todos los botones sociales, y otros procesos, que requieren una carga extra de ficheros ".js" desde sus servidores.
Nos ahorramos código javascript asíncrono duplicado y sigue funcionando todo.... ¡perfecto!.

Ahora, si lo que queremos es ir a por nota (SEO y SEM), lo mejor seria coger todo ese código javascript del <head>, y ponerlo en un archivo .js propio a parte del código html.
Para ello:
  1. Creamos un sudirectorio /js para ubicar allí el fichero (eso será bueno para SEO y SEM).
  2. En robots.txt ponemos un Disallow: /js/, ya que no nos interesa que los robots revisen este directorio.
  3. Todo el código javascript anterior, lo ponemos en un fichero llamado "scripts.js" (puede llamarse como se quiera). Lo sistematizamos de manera parecida a los CSS, es decir, con un fichero a parte (styles.css).
  4. Hay que recordar que a un fichero javascript (".js"), NO le hacen falta las etiquetas <script>...</script>,..  y que su sistema de comentarios es distinto al de html. 
  5. Hemos de preveer un posible fallo de carga del fichero, por lo que programamos la instrucción alert. La instrucción alert, sólo se ejecutará si la carga del fichero falla, por tanto, será correcto avisar...
Una vez hechos los pasos anteriores, se procede a su carga directamente con la instrucción:
<head>
...
...
<script type='text/javascript' language="javascript"
src='http://www.miweb.com/js/scripts.js'>
alert ('Error en fichero /js/scripts.js'); //Sólo se ejecuta si falla la carga.
</script>
...
...
</head>
Ya lo tenemos !!! 
Problema 2 solucionado (o al menos, minimizado)!!!
(y de "rebote" el problema 1 eliminado)

Finalmente, si quereis ver esto funcionando sólo teneis que entrar en nuestra web WebManagerService, y si quereis ver el código, desde el navegador pulsar el botón derecho y "Ver código fuente de la página" .

Espero que os haya gustado el artículo. Sólo me queda daros las gracias, ya que si habeis leido hasta aquí, es lo mínimo que puedo daros.

No dudeis en comentar, espero vuestros aportes.
¿Solucionariais el problema del tiempo de carga de los botones sociales de otra manera?