Combinar Scripts y CSS

Con el fin de optimizar el rendimiento de nuestra web, nos valdremos de la técnica de minimizar las peticiones al servidor, en este caso agrupando nuestros scripts en uno solo (CSS por un lado, Javascript por el otro).

Este Tip mezcla herramientas y prácticas al momento de llevar a cabo nuestros desarrollos, veamos como.

Todos sabemos la importancia de organizar bien nuestros proyectos, carpetas para imágenes, hojas de estilos, Javascript, etc. Y a su vez dentro de cada una de ellas podemos tener varios archivos o incluso subcarpetas.

Esta práctica es muy buena y debemos mantenerla; pero podriamos hacer una pequeña modificación a la hora de subir nuestro sitio al servidor de producción: juntar todas las hojas de estilos en una sola, todos nuestros Javascripts en una solo y asi con todos los archivos que lo permitan (para las imágenes podriamos usar la técnica de CSS Sprites).

De esta manera reduciremos la cantidad de peticiones al servidor, por ejemplo en nuestro sitio tenemos la libreria Scriptaculous y un Javascript propio llamado "misfunciones.js". En este caso tendriamos 2 peticiones al servidor, pero ¿qué pasa si copiamos ambos códigos y los pegamos en un único archivo llamado "scripts.js"? A nivel funcionalidad no perdemos nada, ya que se mantiene todo intacto. A nivel organizativo si perjudica, ya que "desarmariamos" nuestra estructura de directorios, por eso se recomienda hacerlo en el momento de pasar los archivos a producción y NO en etapa de desarrollo.

La mejora importante es que reducimos a la mitad las peticiones la servidor con una técnica muy sencilla. Si a esto lo multiplicamos por la cantidad de scripts que componen nuestra Web veremos que podemos mejorar mucho con muy poco.

Contamos con algunas herramientas para automatizar estas tareas, como por ejemplo CSS Merge.

Aqui les dejo un script en PHP (clic en "expandir" para verlo) tomado del blog de Eliot para hacerlo y como detalle adicional podemos utilizar JSmin para comprimir el Javascript resultante.

// Written by Ed Eliot (www.ejeliot.com) - provided as-is, use at your own risk
   
   /****************** start of config ******************/
   define('FILE_TYPE', 'text/javascript'); // type of code we're outputting
   define('CACHE_LENGTH', 31356000); // length of time to cache output file, default approx 1 year
   define('CREATE_ARCHIVE', true); // set to false to suppress writing of code archive, files will be merged on each request
   define('ARCHIVE_FOLDER', 'js/archive'); // location to store archive, don't add starting or trailing slashes
   
   // files to merge
   $aFiles = array(
      'js/yahoo.js',
      'js/event.js',
      'js/connection.js',
      'js/blog-search.js'
   );
   /****************** end of config ********************/
   
   // this is prepended to all file / folder paths so files and archive folder should be specified relative to this
   $sDocRoot = $_SERVER['DOCUMENT_ROOT'];
   
   /*
      if etag parameter is present then the script is being called directly, otherwise we're including it in 
      another script with require or include. If calling directly we return code othewise we return the etag 
      representing the latest files
   */
   if (isset($_GET['version'])) {
      $iETag = (int)$_GET['version'];     
      $sLastModified = gmdate('D, d M Y H:i:s', $iETag).' GMT';
      
      // see if the user has an updated copy in browser cache
      if (
         (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $_SERVER['HTTP_IF_MODIFIED_SINCE'] == $sLastModified) ||
         (isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] == $iETag)
      ) {
         header("{$_SERVER['SERVER_PROTOCOL']} 304 Not Modified");
         exit;
      }
      
      // create a directory for storing current and archive versions
      if (CREATE_ARCHIVE && !is_dir("$sDocRoot/".ARCHIVE_FOLDER)) {
         mkdir("$sDocRoot/".ARCHIVE_FOLDER);
      }
      
      // get code from archive folder if it exists, otherwise grab latest files, merge and save in archive folder
      if (CREATE_ARCHIVE && file_exists("$sDocRoot/".ARCHIVE_FOLDER."/$iETag.cache")) {
         $sCode = file_get_contents("$sDocRoot/".ARCHIVE_FOLDER."/$iETag.cache");
      } else {
         // get and merge code
         $sCode = '';
         $aLastModifieds = array();
         foreach ($aFiles as $sFile) {
            $aLastModifieds[] = filemtime("$sDocRoot/$sFile");
            $sCode .= file_get_contents("$sDocRoot/$sFile");
         }
         // sort dates, newest first
         rsort($aLastModifieds);
         
         if (CREATE_ARCHIVE) {
            if ($iETag == $aLastModifieds[0]) { // check for valid etag, we don't want invalid requests to fill up archive folder
               $oFile = fopen("$sDocRoot/".ARCHIVE_FOLDER."/$iETag.cache", 'w');
               if (flock($oFile, LOCK_EX)) {
                  fwrite($oFile, $sCode);
                  flock($oFile, LOCK_UN);
               }
               fclose($oFile);
            } else {
               // archive file no longer exists or invalid etag specified
               header("{$_SERVER['SERVER_PROTOCOL']} 404 Not Found");
               exit;
            }
         }
      }
   
      // send HTTP headers to ensure aggressive caching
      header('Expires: '.gmdate('D, d M Y H:i:s', time() + CACHE_LENGTH).' GMT'); // 1 year from now
      header('Content-Type: '.FILE_TYPE);
      header('Content-Length: '.strlen($sCode));
      header("Last-Modified: $sLastModified");
      header("ETag: $iETag");
      header('Cache-Control: max-age='.CACHE_LENGTH);
   
      // output merged code
      echo $sCode;
   } else {
      // get file last modified dates
      $aLastModifieds = array();
      foreach ($aFiles as $sFile) {
         $aLastModifieds[] = filemtime("$sDocRoot/$sFile");
      }
      // sort dates, newest first
      rsort($aLastModifieds);
      
      // output latest timestamp
      echo $aLastModifieds[0];
   }


Saludos y que lo disfruten!

Bookmark and Share

Usar CSS Sprites

Haciendo mucho hincapié en reducir al máximo las peticiones al servidor nos podremos valer de esta técnica muy antigua y adaptada al Desarrollo Web.

La idea es agrupar la mayor cantidad de imágenes posibles en una sola y utilizar un "enmascaramiento" para mostrar la que necesitemos en cada caso.

Por ejemplo la página principal de Google utiliza la siguiente imágen:

Sprite Google

Alli podemos ver todos los iconos que componen a esta web y la forma de utilizarla resulta muy sencilla. Consiste en valernos de las propiedades "background-image" y "background-position" para producir el "enmascaramiento". Podemos verlo de una forma gráfica:

Sprite Google

Para utilizar la flecha debemos ocultar el resto. Podemos automatizar esta tarea siguiendo estos pasos:

  1. Creamos un .zip con las imágenes que vamos a utilizar.
  2. Ingresamos a alguna web que nos genere el código en base a este conjunto de imágenes, por ejemplo: CSS Sprites Generator.
  3. Luego de procesar el archivo que enviamos nos generará el código CSS que tendremos que utilizar en cada caso y la única imágen que deberemos subir al servidor.

Esta es simplmente una explicación muy básica de esta técnica, ya que los usos pueden ser muy diversos y la mejora en el rendimiento es muy notoria.


Saludos y que lo disfruten!

Bookmark and Share

Usar Imágenes Embebidas

En estos sucesivos Tips para Optimización de Sitios Web hacemos hincapié en reducir las peticiones al servidor para alivianar la carga del mismo y las transferencias.

Otra buena práctica es Usar Imágenes Embebidas. La idea es sencilla, se "codifica" la imágen que vayamos a utilizar y en lugar de hacer referencia a ella mediante una ruta (relativa o absoluta) en el atributo "src" del tag "img", directamente enviemos la imágen "embebida" en el código, en formato de texto plano gracias a la Base 64.

Lo que deberiamos hacer es convertir una imágen a este formato, para eso podemos valernos de diversas herramientas, como por ejemplo Base 64 Online. Cargamos una imágen que tengamos en nuestra PC, le damos a "Convert the source data" y listo!.

Una vez que tengamos la imágen codificada sólo tenemos que reemplazar en el atributo "src" del tag "img" la ruta a la imagen por el texto generado; teniendo en cuenta que antes de colocar el código generado debemos poner el tipo MIME y la base, de esta manera: data:image/jpg;base64,.

Yo convertí una imágen y me devolvió el siguiente código (aunque no lo crean el texto siguiente es una imágen):

iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz
AAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAT7SURB
VFiFtZddbBRVFMd/Z2b2o93d7i6FbtuUbin9kFbZLjVtpV0tdAWkBEgpUSKxgmLRaDCASsAommiM
BhMS3nzywSefNDXwpC+EBPBBoiQm0oAVI5ZqIbbdbffj+jCz7bb0gy70JP/cmXPPnPM/5557Zwal
FLkCKAfOA605+3iA4B5d1/tbWlqUx+OJAY/m4kcjR7HZbKej0WhlRUUFkUjE6fV6vxIR22L95ERA
RLaHw+F9JSUlGTLU1dWt1TTtgyUnICKeoqKiLxoaGhCB3r1QVQEFBQWUlZW9LSKhJSWgadrxaDRa
pOs6m9vh8RAcegl8BRAMBnW32316yQiIyKqmpqYjgUCANVXQudHU+71wpBdshhAMBp8SkR1LQsDv
938eiURsTgc8twNEAGXOPVIFuzrB5XJRWFh46n4b8r4JiEiktbV1p2EYtK+H/HwYi8GJT+G7702b
7k7w+2DFihWrNU1746EREBGtuLj4TGNjI74CaFln6s9fgsEh+OYcTEyA0wEv7ga73Y7P53tPRAof
CgGgp6OjY62u62xsA0M3lTdumuNYHP68ZV5v3gCV5eD3+726ri+4LRckICLuioqKT0KhECUBqK+e
mvtneOr67yFz1AVe7QFN0/B4PL0isuaBCADHtm3bVgSwoQWz6Sz8e2fKaPD2lL45BM1hcLvdhmEY
p3ImICIr6+vrj9bW1lIVhNIiIG0iPgax+AwCKSBpjod6QNeFvLy8Z0QkmhMBTdPe6erqcojAE+Gs
7NOgUtNt0+kpcqSguhy2toPD4UDX9ZOLJiAixc3NzQfKy8upXAk+N4gVQNKQ7zC7PiOBZSApc04s
Eq90m2eF3W5vFZH2RRFwOBxvdXd32wEaaq3MrAqodBqV+A+SgwwMDNDf30++XEeN3yI9PoxKjEBy
glXFik3rQdd1ROTEbHGMObIv7OzsfC0QCFBYEMPnvE1y+DKpsV9QyWFUahRQ3PgVLlwwn/HZIDVy
jyde3m7w9VlBRKIi0qyUurhgBbxe79G2tjZnX18f7jsfEb/+IYmhs6TH/kBNjEBKQQpC1pbMd0JN
GWYTToMiVJmgLTRBOp0GePceitbXTXb2XsMwfk8mk95IA3z72WwUTRm6C5Fe6NkKx16Y2+7Cz9B5
ePI2rJT6KXMz2xK8nkwmvQCHn7UymUOWu+HKl2A35rdbXwfN9XDxKgAngN2ZuWlLICIu4E2AcDW0
r2WWsk7Hzb8gHlvY7vBkSLqyT8eZPXAQWA6wK7Kw030fQ+MBqNoDl6/Ob9vRAH7PZMzj9xAQEQOY
XKlN66x9PQfG43D2kmk7GodzF+e3NxREw5OJ7hGRlTMr0AmUAgSLoKZ4/oycGrz/PJQsgycfg/1P
L1yxLY2TsXRgP2TtAhHps0iwdwOc6eWhy+BdqD0IVsgBYJUopRCRUkuhZ4xL/dBUA03VsKYMXA5w
OSHPbh7DLgfkOcCmmw7jCXMpYhMwOg5jFm7fhR/74dJvcOUGjCemcdqS+cvZybQX7f3D0FEiuT0L
nMz0gAJiuZQ1mZos6WIlBYxn90AA2AzUAKsxfzw9gBPIs0Yn4ADm++JNAhNA3ELMwihwE+gHrgE/
KKWuZXrAZjnPnxHIOQscFiE34ALsQMIKMAKMAeNZBLKR0Y9Z9rFp7wIRAXNrGpgNabOubVagDGxZ
yGSdtIgkrApkj8kspFRW0P8BcVptcl859dUAAAAASUVORK5CYII=

Nos quedaría algo asi:

Mi imágen base 64

Como podemos observar es sencillo y en lugar de hacer una petición al servidor la imágen viaja en modo texto.

Saludos y que lo disfruten!

Bookmark and Share

Resaltar Sintaxis (Syntax Highlighter)

Para los que tenemos un Blog con POSTs relacionados con códigos de programación nos va a resultar útil esta herramienta. Permite hacer "Highlight" de sintaxis con unos pocos pasos. Para más referencias podemos visitar la Web Oficial de syntaxhighlighter.

El Primer Paso seria agregar el "Widget" a nuestro Blog, lo podemos hacer simplemente haciendo click en el siguiente Link. Veremos una página como esta y tendremos que hacer clic en el botón "Add to Blogger":



Con eso ya tenemos disponible esta utilidad para utilizarla en todos nuestros POSTs, y la forma de hacerlo es muy sencilla, simplemente debemos "envolver" nuestro código entre los tags:



Lo único que debemos tener en cuenta es reemplazar donde dice "[lenguage]" por el lenguaje que corresponda. Aqui tienen una lista de los lenguajes disponibles:

Lenguage Valor
C++ cpp, c, c++
C# c#, c-sharp, csharp
CSS css
Delphi delphi, pascal
Java java
Java Script js, jscript, javascript
PHP php
Python py, python
Ruby rb, ruby, rails, ror
Sql sql
VB vb, vb.net
XML/HTML xml, html, xhtml, xslt

Con estos simples pasos podemos usar esta fantastica herramienta. Para utilizar otras funcionalidades no olviden visitar la Web Oficial.

Saludos y que lo disfruten!

Bookmark and Share

Chachear Páginas con PHP

En nuestro Tip Definir Fecha de Expiración para Optimización de Sitios Web hablamos de lo útil que puede resultar el chacheo de páginas.

Acá les dejo un ejemplo de código en PHP para hacerlo. El funcionamiento es sencillo:

  1. Se incluye el primero de los archivos (cache.start.php) encargado de verificar si existe el chache de la página a la que se ingresa y si sigue siendo válido. En ese caso mostrará el contenido "chacheado".
  2. Se genera el contenido HTML correspondiente sin importar las funciones de "cacheo".
  3. Se incluye el archivo final (cache.end.php) encargado de generar el cache de la página que servira para los sucesivos ingresos.

[*] "cache.start.php":
// Settings
$cachedir = 'cache/';   // directorio de cache
$cachetime = 86400;   // duración del cache
$cacheext = 'cache';   // extensión de cache

// script a procesar
$cachepage = $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
$cachefile = $cachedir.md5($cachepage).'.'.$cacheext;

// calculamos el tiempo del cache
if (@file_exists($cachefile)) {
$cachelast = @filemtime($cachefile);
} else {
$cachelast = 0;
}

@clearstatcache();

// Mostramos el archivo si aun no vence
if (time() - $cachetime <$cachelast) {
@readfile($cachefile);
exit();
}

ob_start();
[*] "cache.end.php":
// Generamos el nuevo archivo cache
$fp = @fopen($cachefile, 'w');

// guardamos el contenido del buffer
@fwrite($fp, ob_get_contents());
@fclose($fp);

ob_end_flush();
[*] Implmentación:
include("cache.start.php");

// Aqui el código HTML

include("cache.end.php");
Saludos y que lo disfruten!
Bookmark and Share

Definir Fecha de Expiración

Si vamos a mostrar el mismo contenido a todos los usuarios durante un tiempo "X", ¿por qué hacer trabajar al sistema para que haga una consulta a la base de datos, o ejecutar una script cada vez si el resultado va a ser el mismo?

Cuando tus páginas son cacheadas el servidor no perderá ni tiempo ni recursos en regenerar una y otra vez estas, unicamente las cargará de la cache.

Existen tres tipos de caches que pueden actuar durante el proceso de solicitud de un documento web:
  1. Cache de Agente de Usuario (User-Agent): Está presenten en los navegadores web y lógicamente sólo funcionan para un único usuario.
  2. Cache Compartida o proxy-cachés directos: Este tipo de cache son utilizados por los proveedores de servicios de Internet y empresas para ahorrar ancho de banda. La comparten todos los usuarios que accedan.
  3. Cache pasarela o proxy-cachés inversos: Funcionan como respaldo de un servidor web de forma transparente para los usuarios. Es posible trabajar con varias cache pasarela de manera conjunta para implementar una Red de Distribución (CDN)

El funcionamiento del cache en términos muy básicos seria:
  1. Se realiza una petición de un recurso al servidor y se valida la fecha de modificación.
  2. En caso de que la fecha de modificación sea la misma que esta guardada en el cache NO es necesario que el servidor envie el archivo, directamente se envia al usuario el "chacheado".
  3. Si el archivo que se solicita ha sido modificdo, el servidor lo envia, se chachea y se repite el proceso en futuras consultas.

Puede seguir mi tutorial para Chachear Páginas con PHP.

Saludos y que lo disfruten!

Bookmark and Share

Evitar Redirecciones

Como hemos visto en los sucesivos Tips tras cada petición se envian datos (cabeceras) que por mas minimas que sean en la suma total de elementos que las solicitan la cuenta final puede resultar abultada.

Hay un error muy común en los desarrollos que es olvidar poner en una ruta que apunta a una carpeta la barra final ("/"), que indica justamente que es una carpeta. Parece un detalle menor pero si lo vemos un poco en detalle nos daremos cuenta que no lo es tanto:

Usaremos una direccion de ejemplo: "http://misitio.com". En en Index tenemos un link que apunta a la carpeta Portfolio de la siguiente manera: "http://misitio.com/portfolio" (notese la falta de la barra final). Cuando intentemos acceder a esa URL la petición será para buscar el archivo llamado "portfolio" (sin extensión, recuerden que existen archivos sin extensión) en lugar de buscar la carpeta llamada asi.

Al no encontrar dicho archivo automáticamente se hará una redirección a "http://misitio.com/portfolio/" (notese la barra al final) para dar con la carpeta especificada.

Si de entrada hubieramos colocado esa barra nos habriamos ahorrado esa redirección con todas las peticiones en vano que se hicieron en un principio.

Saludos y que lo disfruten!

Bookmark and Share