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?

    10 comentarios:

    Socrates dijo...

    Eres un genio!!! gracias por tu artículo! :)

    Daniel Jimenez dijo...

    Muchas gracias, desde hace tiempo vengo trabajando en una barra de botones sociales, y ahora que la tengo...toca optimizarla, seguiré cuidadosamente los pasos que recomiendas. Saludos

    WebManagerService dijo...

    Me alegra que mis articulos os hayan servido.
    Gracias por vuestros comentarios.
    Saludos

    Sebastian dijo...

    Muchisimas gracias es algo que estaba buscando y esta muy bien explicado, una duda que tengo querido amigo, Por que poner el script en el head y no el footer ?

    WebManagerService dijo...

    Hola Sebastian,
    Poner el script en el footer hace que el tiempo de carga de la web sea más largo.
    Me explico:
    En el footer, la carga del script se activa cuando el HTML ya está finalizando su carga, por tanto, ya no habrá (o habrá muy poca) carga asíncrona del script (es decir, en paralelo con la carga del HTML).
    Básicamente, si lo pones al final, acaba el HTML y empieza la carga del script, en vez de cargar el script al mismo tiempo que se carga el HTML.
    Hay programas (firebug, etc..) donde puedes tracear el tiempo de carga y ver "este fenómeno".
    Espero haber respondido a tu pregunta.

    Anónimo dijo...

    muchisimas gracias por la información!! Jose.

    Anónimo dijo...

    Hola, fantástico post!!
    yo tengo un problema que no se si sabrías ayudarme. el caso es que tengo la web con un proveedor de plataforma de tienda online por lo que no puedo meterme mucho con el código. no obstante, el otro día estuvimos haciendo una actualización pero nos daba un problema que no supimos resolver. si entras en alguno de nuestros productos en nuestra web: http://www.theindianface.com veras que tenemos un botón "me gusta" en la parte superior, al lado del titulo del producto y mas abajo un complemento tipo addthis que se llama Shareaholic. el caso es que se interfieren el uno al otro. en muchas ocasiones, cuando haces clic en el "me gusta" superior, no se contabiliza en el contador. unas veces si y otras veces no.
    además, tenemos un problema. el botón superior de "me gusta" teóricamente al hacer clic en el, se comporta como un mix entre "compartir" y el clásico "me gusta" de facebook (según los últimos cambios de facebook) de tal manera que al hacer clic en "me gusta" se publica en nuestro muro de facebook una pequeña descripción, titulo e imagen (como el clásico compartir de siempre.
    El caso, es que si además de tener este botón, tenemos el complemento socia shareaholic o addthis como teníamos antes, deja de compartir titulo, descripción y foto y se comporta como un simple "me gusta". Aunque a veces si se comporta como "compartir". El caso es que por algún motivo, al tener a la vez "me gusta" y "shareaholic", el "me gusta" no funciona correctamente. Si tenemos solo el "me gusta" sin "shareaholic" todo funciona correctamente.
    Bueno, menudo rollo acabo de contar... No tendrás alguna idea sobre como solucionar esto?
    mil gracias!!

    Unknown dijo...

    Notable tu aporte, agudo y la solución muy inteligente. Lo probaré en mis webs.
    Saludos

    Test dijo...

    Gracias mi hermano, genial!

    Top Script dijo...

    Muchisimas gracias webmaster.
    Andaba buscando alguna guia de sacripts y por el camino me encontré con el tuyo. Esta excelentemente explicado, cualquiera con esto puede colocar un script.

    Publicar un comentario