Geek #17

¿Y si nos juntáramos un grupo de expertos que trabajan en grandes sitios para crear una guía para el rendimiento front-end definitiva?

Y no sólo una de esas aburridas guías hechas para robots, ¿y si hiciéramos algo divertido? ¿Qué tal reunirse Briza Bueno (Americanas.com), Davidson Fellipe (Globo.com), Giovanni Keppelen (ex-Peixe Urbano), Jaydson Gomes (Terra), Marcel Duran (Twitter), Mike Taylor (Opera), Renato Mangini (Google) y Sérgio Lopes (Caelum) para crear la mejor referencia posible?

¡Eso es exactamente lo que hemos hecho! Y nosotros te guiaremos en esta batalla para crear sitios incluso más rápidos.

Zeno Rocha, líder del proyecto.

¿Realmente importa el rendimiento?

??

Desde luego importa y lo sabes. Entonces, ¿por qué seguimos haciendo sitios lentos que desembocan en una mala experiencia del usuario? Ésta es una guía práctica impulsada por la comunidad que te mostrará cómo hacer sitios web más rápidos. No desperdiciemos tiempo mostrando casos millonarios sobre rendimiento, ¡vayamos directos al grano!

HTML

Evita código en línea/incrustado

Hay tres formas básicas de incluir CSS o Javascript en tu página

1) En línea: El css es definido dentro de un atributo style, y el JavaScript en un atributo onclick por ejemplo, en cualquier etiqueta HTML.

2) Incrustado: El CSS es definido dentro de una etiqueta <style> y el JavaScript dentro de una etiqueta <script>.

3) Externo: El CSS es cargado desde una etiqueta <link> y el Javascript desde el atributo src de la etiqueta <script>.

Las dos primeras opciones, pese a que reducen el número de solicitudes HTTP, aumentan el tamaño de tu documento HTML. Pero esto puede ser útil cuando tienes archivos pequeños y el costo de hacer una solicitud es mayor. En este caso, haz tests para evaluar si hay alguna ganancia en velocidad. Asegúrate de evaluar el propósito de tu página y su audiencia: si esperas que la gente sólo visite tu página una vez, por ejemplo en alguna campaña temporal donde nunca esperas visitantes de vuelta, poner código en línea o incrustado ayudará a reducir el número de solicitudes HTTP.

> Evita escribir manualmente CSS o JS en el medio de tu HTML (es preferible automatizar este proceso con herramientas).

La tercera opción nó solo mejora la organización de tu código, sino que también hace posible al navegador cachearlo. Esta opción suele ser la mejor en la mayoría de los casos, sobre todo cuando estás trabajando con archivos grandes y una gran cantidad de páginas.

> Herramientas útiles / Referencias

Los estilos arriba, los scripts al fondo

Cuando ponemos hojas de estilo en el <head> permitimos a la página mostrarse progresivamente, lo que da la impresión a nuestros usuarios de que la página está cargando rápido.

<head>
  <meta charset="UTF-8">
  <title>Browser Diet</title>

  <!-- CSS -->
  <link rel="stylesheet" href="style.css" media="all">
</head>

Pero si los ponemos al fondo de la página, la página se renderizará sin estilos hasta que el CSS se descargue y aplique.

Geek #32

Por otro lado, cuando tratamos con JavaScript, es importante poner los scripts al fondo de la página ya que bloquean el renderizado mientras se están descargando y ejecutando.

<body>
  <p>Lorem ipsum dolor sit amet.</p>

  <!-- JS -->
  <script src="script.js"></script>
</body>

> Referencias

Prueba async

Para explicar cómo este atributo es útil para el rendimiento, es mejor entender qué pasa cuando no lo usamos.

Geek #20
<script src="example.js"></script>

De esta forma, la página tiene que esperar a que el script sea descargado, analizado y ejecutado completamente antes de poder seguir mostrando el HTML. Esto puede incrementar significativamente el tiempo de carga de tu página. A veces este comportamiento puede ser el deseado, pero no suele ser el caso.

<script async src="example.js"></script>

El script es descargado asíncronamente mientras el resto de la página sigue siendo analizada. El script se ejecutará en cuanto se acabe de descargar. Tenga en cuenta que varios scripts asincrónicos se ejecutará sin un orden específico.

> Referencias

CSS

Comprime tus hojas de estilo

Para mantener un código legible, es una buena idea escribir comentarios y usar indentación:

.center {
  width: 960px;
  margin: 0 auto;
}

/* --- Estructura --- */

.intro {
  margin: 100px;
  position: relative;
}

Pero para el navegador, eso no importa. Por eso, recuerda siempre comprimir tu CSS mediante herramientas automatizadas.

.center{width:960px;margin:0 auto}.intro{margin:100px;position:relative}

Esto quitará bytes al archivo, lo que resultará en un proceso de descarga, análisis y ejecución más rápido.

Para los que usan preprocesadores como Sass, Less, y Stylus, es posible configurarlos para que la salida sea comprimida.

> Herramientas útiles / Referencias

Combinando múltiples archivos CSS

Otra buena práctica para la organización y mantenimiento de tus estilos es separarlos en componentes modulares.

Geek #9
<link rel="stylesheet" href="structure.css" media="all">
<link rel="stylesheet" href="banner.css" media="all">
<link rel="stylesheet" href="layout.css" media="all">
<link rel="stylesheet" href="component.css" media="all">
<link rel="stylesheet" href="plugin.css" media="all">

Sin embargo, hace falta una solicitud HTTP para cada archivo (y sabemos que los navegadores sólo pueden descargar un número limitado de recursos en paralelo).

<link rel="stylesheet" href="main.css" media="all">

Así que combina tu CSS. Tener un menor número de archivos resultará en un menor número de solicitudes y una página más rápida.

¿Quieres tener lo mejor de ambos mundos? Automatiza este proceso mediante una herramienta de compilación.

> Herramientas útiles / Referencias

Usa <link> antes que @import

Hay dos formas de incluir hojas de estilo externas en tu página: mediante la etiqueta <link>:

<link rel="stylesheet" href="style.css">

O mediante la directiva @import (dentro de una hoja de estilos externa o una etiqueta <style>):

@import url('style.css');

Cuando usas la segunda opción dentro de una hoja de estilos externa, el navegador no es capaz de descargar el archivo en paralelo, lo que puede bloquear la descarga de otros archivos.

> Referencias

JavaScript

Carga contenido de terceros asíncronamente

¿Quién nunca ha cargado contenido de terceros para incrustar un video de YouTube o un botón de like/tweet?

Geek #46

El gran problema es que esos códigos no son siempre distribuidos eficientemente, ya sea por la conexión del usuario, o la conexión del servidor donde se alojan. O este servicio puede estar caído temporalmente o incluso estar bloqueado por el firewall del usuario o su compañía.

Para evitar que esto se convierta en un problema crítico, o peor, bloquee la carga de la página completamente, siempre carga estos códigos asíncronamente (o usa Friendly iFrames).

var script = document.createElement('script'),
    scripts = document.getElementsByTagName('script')[0];
script.async = true;
script.src = url;
scripts.parentNode.insertBefore(script, scripts);

Alternativamente, si quieres cargar varios widgets de terceros, puedes cargarlos asíncronamente con este script.

> Video / Referencias

Cachea las longitudes de las arrays

El loop es sin duda una de las partes más importantes relacionadas con el rendimiento de JavaScript. Trata de optimizar la lógica dentro de un loop para que cada interacción sea hecha eficientemente.

Una forma de hacer esto es almacenar el tamaño de la array sobre la que iteraremos, para que no tenga que ser recalculada cada iteración.

var arr = new Array(1000),
    len, i;

for (i = 0; i < arr.length; i++) {
  // Mal - arr.length necesita ser recalculada 1000 veces
}

for (i = 0, len = arr.length; i < len; i++) {
  // Bien - arr.length es calculada sólo una vez y entonces es almacenada
}

> Resultados en JSPerf

> Nota: Pese a que los navegadores modernos automáticamente optimizan este proceso, sigue siendo una buena práctica para adaptarse a los navegadores antiguos que aún perduran.

En iteraciones sobre colecciones en HTML como una lista de nodos (NodeList) generada por ejemplo por document.getElementsByTagName('a') esto es particularmente crítico. Estas colecciones son consideradas vivas, es decir, son automáticamente actualizadas cuando hay cambios en el elemento que les pertenece.

var links = document.getElementsByTagName('a'),
    len, i;

for (i = 0; i < links.length; i++) {
  // Mal - cada iteración la lista será recalculada para ver si hubo algún cambio
}

for (i = 0, len = links.length; i < len; i++) {
  // Bien - el tamaño de la lista es obtenido y almacenado, y entonces comparado cada iteración
}

// Terrible: ejemplo de loop infinito
for (i = 0; i < links.length; i++) {
  document.body.appendChild(document.createElement('a'));
  // cada iteración la lista de links aumenta, haciendo quel loop nunca se termine
  // esto no pasaría si el tamaño fuera almacenado y usado como una condición
}

> Referencias

Evita document.write

El uso de document.write hace que la página dependa de su valor de retorno para cargar completamente.

Esta (mala) práctica ha sido evitada por los desarrolladores, pero aún hay casos donde es necesaria, como un fallback para algún archivo Javascript.

HTML5 Boilerplate, por ejemplo, usa esta técnica para cargar jQuery localmente si la CDN de Google no responde.

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-1.9.0.min.js"><\/script>')</script>

> Atención: document.write ejecutado posteriormente o durante el evento window.onload reemplaza todo el contenido de la página actual.

<span>foo</span>
<script>
  window.onload = function() {
    document.write('<span>bar</span>');
  };
</script>

El resultado de la página final será sólo bar y no foobar como se esperaría. Lo mismo ocurre cuando se ejecuta tras el evento window.onload.

<span>foo</span>
<script>
  setTimeout(function() {
    document.write('<span>bar</span>');
  }, 1000);
  window.onload = function() {
    // ...
  };
</script>

El resultado será el mismo que el ejemplo anterior si la función programada por setTimeout es ejecutada tras window.onload.

> Referencias

Minimiza los Repaints y Reflows

Los repaints y reflows son causados cuando hay cualquier proceso de re-renderización del DOM cuando cierta propiedad o elemento cambia.

Los repaints se ejecutan cuando la apariencia de un elemento cambia sin cambiar su estructura. Nicole Sullivan describe esto como un cambio de estilos (como cambiar un background-color).

Reflows son los más costosos, ya que son causados por cambiar la estructura de la página, como cambiar el ancho de un elemento.

No hay duda de que excesivos reflows y repaints deberían de ser evitados, así que en vez de hacer esto:

Geek #55
var div = document.getElementById("to-measure"),
    lis = document.getElementsByTagName('li'),
    i, len;

for (i = 0, len = lis.length; i < len; i++) {
  lis[i].style.width = div.offsetWidth + 'px';
}

Haz esto:

var div = document.getElementById("to-measure"),
    lis = document.getElementsByTagName('li'),
    anchoAConfigurar = div.offsetWidth,
    i, len;

for (i = 0, len = lis.length; i < len; i++) {
  lis[i].style.width = anchoAConfigurar + 'px';
}

Cuando estableces style.width, el navegador necesita recalcular la estructura. Normalmente, cambiar los estilos de varios elementos sólo resulta en un reflow. Sin embargo, en el primer ejemplo, usamos continuamente offsetWidth, que fuerza al navegador a recalcular la estructura cada iteración.

Si necesitas leer datos sobre la estructura de la página, hazlo todo junto antes de establecer algún valor que cambie la estructora, tal y como en el segundo ejemplo

> Demo / Referencias

Evita manipulaciones del DOM innecesarias

Cada vez que tocas el DOM sin necesidad muere un panda.

No, ahora en serio, navegar por los elementos del DOM es costoso. Aunque los motores de JavaScript son cada vez más potentes y rápidos, siempre es preferible optimizar al máximo las consultas al DOM tree.

Una de las alternativas más simples a adoptar es guardar un elemento en una variable cuando tiene que ser usado más de una vez, por lo que sólo se consulta al DOM una vez.

// fatal!
for (var i = 0; i < 100; i++) {
  document.getElementById("mi-elemento").innerHTML += "<span>" + i + "</span>";
}
// mucho mejor :)
var miLista = "";

for (var i = 0; i < 100; i++) {
  miLista += "<span>" + i + "</span>";
}

document.getElementById("mi-elemento").innerHTML = miLista;

```js // mucho mucho mejor :) var myListHTML = document.getElementById("myList").innerHTML;

for (var i = 0; i < 100; i++) { myListHTML += "" + i + ""; }

> Resultados en JSPerf

Comprime tu script

Al igual que con el CSS, para mantener un código legible, es una buena idea escribir comentarios y usar indentación:

BrowserDiet.app = function() {

  var foo = true;

  return {
    bar: function() {
      // do something
    }
  };

};

Pero para el navegador, eso no importa. Por eso, recuerda siempre comprimir tu JavaScript mediante herramientas automatizadas.

BrowserDiet.app=function(){var a=!0;return{bar:function(){}}}

Esto quitará bytes al archivo, lo que resultará en un proceso de descarga, análisis y ejecución más rápido.

> Herramientas útiles / Referencias

Combina los archivos JS en uno sólo

Otra buena práctica para la organización y el mantenimiento de los scripts es separarlos en componentes modulares.

Geek #47
<script src="navbar.js"></script>
<script src="component.js"></script>
<script src="page.js"></script>
<script src="framework.js"></script>
<script src="plugin.js"></script>

Sin embargo, una solicitud HTTP es necesaria para cada archivo (y sabemos que los navegadores sólo pueden descargar un número limitado de recursos en paralelo).

<script src="main.js"></script>

Así que combina tu JavaScript. Tener un menor número de archivos resultará en un menor número de solicitudes y una página más rápida.

¿Quieres tener lo mejor de ambos mundos? Automatiza este proceso mediante una herramienta de compilación.

> Herramientas útiles / Referencias

jQuery

Usa siempre la última versión de jQuery

El equipo de jQuery siempre está buscando mejorar la librería, mediante mejor legibilidad del código, nuevas funcionalidades, y optimización de algoritmos existentes.

Geek #36

Por eso, usa siempre la última versión, que siempre se encuentra aquí, si quieres copiarla en un archivo local:

http://code.jquery.com/jquery-latest.js

Pero nunca uses esa url en una etiqueta <script>, puede crear problemas en el futuro ya que nuevas versiones son automáticamente servidas a tu sitio antes de que puedas testarlas. En vez de eso, usa la última versión de jQuery que necesites específicamente.

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>

Como el sabio Barney Stinson dice, "Lo nuevo es siempre mejor" :P

> Referencias

Selectors

Los selectores son unos de los asuntos mas importantes en el uso de jquery. Hay muchas vias de seleccionar un elemento del DOM, pero no significa que todas tengan el mismo comportamiento, puedes seleccionar un elemento usando clases, IDs or methodos(methods) como 'find()', 'children()'.

Entre todos ellos, seleccionar un ID es la via mas rapida porque esta basada en una operacion nativa del DOM.

$("#foo");

> Resultados en JSPerf

Usa for en lugar de each

El uso de funciones de JavaScript nativas resulta casi siempre en una ejecución más rápida que usando las de jQuery. Por eso, en vez de usar el método jQuery.each usa el loop for de JavaScript.

Pero presta atención, pese a que for in es nativo, en muchos casos es más lento que jQuery.each.

Y el loop for nos da otra oportunidad de acelerar las cosas cacheando el tamaño (length) de las colecciones sobre las que estamos iterando.

for ( var i = 0, len = a.length; i < len; i++ ) {
    e = a[i];
}

El uso de los loops while y for de forma inversa es un tema caliente de discusión en la comunidad, y son citados frecuentemente como la forma más rápida de iteración. Sin embargo suele ser evitado por ser menos legible.

// while inverso
while ( i-- ) {
  // ...
}

// for inverso
for ( var i = array.length; i--; ) {
  // ...
}

> Resultados en JSPerf / Referencias

No uses siempre jQuery...

A veces JavaScript a secas puede ser más fácil y rápido que jQuery.

Geek #6

Considera el siguiente html:

<div id="text">Vamos a cambiar este texto!</div>

En lugar de hacer esto:

$('#text').html('El texto está cambiado').css({
  backgroundColor: 'red',
  color: 'yellow'
});

Usa JavaScript a secas

var text = document.getElementById('text');

text.innerHTML = 'El texto está cambiado';
text.style.backgroundColor = 'red';
text.style.color = 'yellow';

Es sencillo y mucho más rápido. Échale un vistazo a los resultados en JSPerf

> Resultados en JSPerf / Referencias

Imagenes

Usa sprites

Ésta técnica es sobre agrupar varias imágenes en un sólo archivo.

CSS Sprite Example

Y después posicinarlas con CSS.

.icon-foo {
  background-image: url('mySprite.png');
  background-position: -10px -10px;
}

.icon-bar {
  background-image: url('mySprite.png');
  background-position: -5px -5px;
}

Como resultado, has reducido dramáticamente el número de solicitudes HTTP y evitado cualquier retraso potencial de otros recursos en tu página.

Cuando estás usando tu sprite, evita dejar demasiado espacio en blanco entre imágenes. Esto no afectará al tamaño del archivo, pero tendrá efecto en el consumo de memoria.

Y pese a que casi todo el mundo conoce las sprites, esta técnica no está demasiado extendida—tal vez debido a que los desarrolladores no usan herramientas automatizadas para generar sprites. Hemos seleccionado algunas que pueden ayudarte con esto.

> Herramientas útiles / Referencias

Data URI

Esta técnica es una alternativa a usar sprites.

Una Data-URI es una forma de poner en línea el contenido de la URI a la que apuntarías normalmente. En este ejemplo estamos usándolo para poner en línea el contenido de un background-image para reducir el número de solicitudes HTTP requeridas para cargar la página.

Antes:

.icon-foo {
  background-image: url('foo.png');
}

Después:

.icon-foo {
  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII%3D');
}

Todos los navegadores desde IE8 soportan esta técnica.

Tanto este método como el uso de sprites requieren herramientas para ser mantenibles. Éste método tiene la ventaja de no requerir el posicionamiento manual de las sprites ya que mantiene tus imágenes en archivos individuales durante el desarrollo.

Sin embargo, tiene la desventaja de aumentar el tamaño de tu HTML/CSS considerablemente si tienes imágenes grandes. Éste método no es recomendado si no estás comprimiendo con gzip tu HTML/CSS durante las solicitudes HTTP, ya que el aumento de tamaño puede sobrepasar a la ganancia en velocidad.

> Herramientas útiles / Referencias

No cambies el tamaño de las imágenes en el HTML

Siempre define el atributo width y height de una imagen. Esto ayudará a evitar repaints y reflows durante el renderizado.

<img width="100" height="100" src="logo.jpg" alt="Logo">

Sabiendo esto, Pepe, desarrollador que tiene una imagen de 700x700px decide usar esta técnica para mostrar la imágen como 50x50px.

De lo que Pepe no se da cuenta es que se enviarán docenas de kilobytes innecesarios.

Siempre ten en mente: sólo porque puedas definir el alto y el ancho de una imágen en HTML, no significa que debas usarlo para reducir el tamaño de imágenes grandes.

> Referencias

Optimiza tus imágenes

Los archivos de imágenes contienen muchísima información inútil en la Web. Por ejemplo una foto JPEG puede tener metadatos Exif sobre la cámara (fecha, modelo de cámara, ubicación, etc.). Una imagen PNG contiene información sobre colores, metadatos, y a veces incluso una miniatura incrustada. Nada de esto es usado por el navegador y contribuye al aumento del tamaño del archivo.

Hay herramientas para la optimización de imágenes que borrarán estos datos innecesarios y te darán un archivo más ligero sin afectar a la calidad. Decimos que hacen una compresión sin pérdida.

Otra forma de optimizar imágenes es comprimirlas a cambio de calidad visual. Llamamos a esto una optimización con pérdida. Cuando exportas un JPEG, por ejemplo, puedes escoger el nivel de calidad (un número del uno al 100). Pensando en el rendimiento, siempre escoge el menor número posible mientras la calidad visual sea aceptable. Otra forma común de optimizar con pérdida es reducir la paleta de colores en un PNG, o convertir archivos PNG-24 en PNG-8.

Para mejorar la percepción de rendimiento del usuario, deberías también transformar todos tus archivos JPEG en JPEGs progresivos. Los JPEGs progresivos son soportados por una gran cantidad de navegadores, son muy simples de crear, y no conllevan ningúna pérdida de rendimiento significativa. La ventaja es que la imagen aparecerá más pronto en la página (ver demo).

> Herramientas útiles / Referencias

Bonus

Herramientas de diagnóstico: tus mejores amigas

Geek #43

Si quieres aventurarte en este mundo, es imprescindible insalar la extension YSlow en tu navegador Chrome o Firefox. Ellas serán tus mejores amigas a partir de ese momento.

O si prefieres herramientas online, visite WebPageTest, HTTP Archive o PageSpeed.

En general analizan el rendimiento de tu sitio, y generan un informe que califica a tu sitio con una nota además de darte consejos invaluables para ayudarte a resolver los problemas potenciales.

¡Eso es todo por hoy!

Geek #31

Esperamos que tras leer esta guía puedas poner a tu sitio en forma. :)

Y recuerda, como todo en la vida, no hay una solución milagrosa. La optimización del rendimiento de tu aplicación es un riesgo que vale la pena, pero no debería ser la única base de todas tus decisiones como desarrollador—A veces necesitarás sopesar los costos y los beneficios.

¿Quiéres aprender más? Échale un ojo a las Referencias que usamos para escribir esta guía.

¿Alguna sugerencia? Envía un tweet a @BrowserDiet o una pull request en Github.

No te olvides de compartir con tus amigos, hagamos una web más rápida para todos. \o/