martes, 28 de agosto de 2012

Los scripts externos pueden bloquear una web

Cuando cargamos un fichero JavaScript externo mediante una etiqueta <script> incluida en nuestro HTML, estamos corriendo un riesgo importante. Ese script externo a nuestra aplicación, probablemente un banner de publicidad o un widget, puede bloquear nuestra página y dejarla en blanco durante varios minutos o incluso evitar que llegue a cargarse. Steve Souders explica el problema en su presentación “Your script just killed my site” y proporciona una demostración online.

En la siguiente imagen podemos ver al menos tres elementos que se cargan mediante javascript externo en una página web real:



Si el código que incluimos para cargar estos elementos (normalmente proporcionado también por la empresa externa) no es apropiado, puede bloquear nuestra web.

¿Porqué un simple script puede bloquear nuestra página?

Un archivo JavaScript puede cargarse de manera síncrona (bloqueante) o asíncrona (no-bloqueante). Veamos que implica cada una:


Carga síncrona (bloqueante)

Decimos que se carga de forma síncrona cuando el navegador tiene que esperar a que el archivo se haya descargado y ejecutado para continuar presentando elementos en la página. Esto ocurre cuando utilizamos una etiqueta <script> incluida en el HTML. Al encontrarla, el navegador no continua calculando y mostrando los siguientes elementos de la página, este proceso se para hasta la ejecución del fichero. Esto ocurre para todos los navegadores.

La razón por la que se bloquea es que este script, al estar incluido de forma estática en el HTML, se considera parte de la página que se está presentando y, por lo tanto, el javascript que contiene puede crear nuevos elementos en la página ( o modificarlos si se carga en el body). Se espera a ejecutarlo completo para poder presentar la página correctamente.

Carga asíncrona (no-bloqueante)

La carga es asíncrona cuando el proceso de carga se realiza sin bloquear la presentación de otros elementos de la página.

Podemos conseguir la carga no-bloqueante mediante la inserción dinámica de la etiqueta <script> o utilizando atributos como  “defer” o “async”  (HTML5). Veremos después en detalle estas opciones.

El problema con los scripts externos que pueden "matar" nuestra página, es que a veces se incluyen de forma síncrona. Si el archivo es grande va a retrasar siempre la carga de la web que lo incluye. Aunque el archivo no sea pesado, su servidor puede ser lento, estar caído, o incluso estar bloqueado ( en China, por ejemplo ). Hasta que el script no se reciba o se produzca un timeout (si lo hay) nuestra página aparecerá en blanco.


¿Qué significa exactamente no-bloqueante?


Puesto que javascript no es multi-hilo, es imposible que dos scripts se ejecuten en paralelo. Este thread es además el mismo que se encarga de renderizar los elementos de la página. Cuando hablamos de un script no-bloqueante significa que no bloquea la página (puede seguir con el renderizado de elementos del DOM) mientras se descarga, la ejecución siempre se va a realizar como tarea única, nunca en paralelo.

Tenemos entonces que el navegador tiene que realizar dos tareas con el archivo:
  1.  Descargarlo de la URL que se le indique 
  2.  Ejecutarlo
El punto 1 es normalmente el que más tiempo requiere y es el único que podemos optimizar mediante estas técnicas. La ejecución siempre va a ser bloqueante puesto que tenemos un solo hilo que se encarga de ejecutar y de presentar elementos del UI.

Formas de carga no-bloqueante


Etiqueta <script> generada dinámicamente

Lo más habitual es utilizar una etiqueta <script> que se genera dinámicamente con JavaScript. Cuando se crean de este modo, la descarga se realiza inmediatamente pero no bloquea el renderizado de otros elementos. El fichero se ejecuta cuando se ha descargado completamente:
var script = document.createElement("script");
script.type = "text/javascript";
script.src = “/path/to/script.js";
document.getElementsByTagName("head")[0].appendChild(script);
Hay que tener en cuenta que esta técnica no respeta el orden de inclusión de varios ficheros JavaScript (en algunos navegadores). No se garantiza que el fichero que se incluye primero sea el primero en ejecutarse.

Atributo ‘defer’ en <script>

Incluyendo el atributo defer la descarga comienza inmediatamente pero de forma no-bloqueante. La ejecución se retrasa hasta que la página se ha parseado completamente.


Atributo ‘async’ en <script> ( HTML5 )

En HTML5 se ha creado un atributo nuevo precisamente para este propósito:
de esta forma la etiqueta <script> se comporta igual que si la hubiéramos generado dinámicamente. El script de descarga inmediatamente sin bloquear la presentación de la página y se ejecuta en cuanto está descargado (esta es la principal diferencia respecto a defer).

Es importante señalar que todas estas técnicas retrasan el evento onload hasta que los scripts se han ejecutado. Existen otras técnicas que utilizan XHR (AJAX) o iframes, pero no aportan ventajas con respecto a las anteriores y prácticamente han sido desplazadas por estas.


Referencias:
Best way to load external javascript
Cargar JavaScript. Blocking vs non-blocking
Loading Scripts Without Blocking
What is a non-blocking script?


No hay comentarios:

Publicar un comentario