lunes, 1 de julio de 2013

Acelerando tiempos de carga HTML uniendo Javascript con PHP

Una de las reglas de oro cuando tratamos de mejorar el rendimiento en aplicaciones Web es reducir el uso de peticiones HTTP. Normalmente solemos tener varios archivos Javascript dentro de nuestros proyectos, lo que significa varias peticiones HTTP para descargarlos.
Es una práctica recomendable unir todos estos archivos Javascript en uno solo para reducir el número de peticiones. Podemos hacerlo de forma manual. No es difícil. Solo tenemos que cortar y pegar las fuentes Javascript en un único archivo. Incluso tenemos herramientas que nos ayudan a hacerlo, como Yslow.
Esto es una buena solución si nuestro proyecto está terminado. Pero si nuestro proyecto está vivo y lo estamos modificando, suele ser útil (al menos para mí) separar los archivos Javascript para tener el código más organizado y mantenerlo.
En definitiva, que tenemos que decidir entre el rendimiento de nuestra aplicación en producción o ayudar al desarrollador. Es por esto que me gusta usar el script que os voy a mostrar. Un script PHP que junta todos los archivos .js dentro de un único archivo de forma dinámica.


¿Cómo aceleramos los tiempos de carga de Javascript?

La idea es la siguiente. Normalmente suelo tener los archivos .js guardados jerárquicamente dentro de una carpeta que se llama js (¿original, no?). También suelo tener un servidor de desarrollo y uno de producción (¿realmente original, no?). Cuando estoy desarrollando mi aplicación me gusta separar los archivos y en un formato legible, pero cuando están en producción combinarlos en uno solo e incluso minimizarlos para mejorar el tiempo de descarga.

El script que tengo para combinar todos los archivos .js en uno solo es el siguiente:

    1. //js.php
    2. require 'jsmin.php';
    3. function checkCanGzip(){
    4. if (array_key_exists('HTTP_ACCEPT_ENCODING'$_SERVER)) {
    5. if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false)return "gzip";
    6. if (strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'x-gzip') !== false)return "x-gzip";
    7. }
    8. return false;
    9. }
    10. function gzDocOut($contents$level=6){
    11. $return = array();
    12. $return[] = "\x1f\x8b\x08\x00\x00\x00\x00\x00";
    13. $size = strlen($contents);
    14. $crc = crc32($contents);
    15. $contents = gzcompress($contents,$level);
    16. $contents = substr($contents, 0, strlen($contents) - 4);
    17. $return[] = $contents;
    18. $return[] = pack('V',$crc);
    19. $return[] = pack('V',$size);
    20. return implode(null, $return);
    21. }
    22. $ite = new RecursiveDirectoryIterator(dirname(__FILE__));
    23. foreach(new RecursiveIteratorIterator($iteas $file => $fileInfo) {
    24. $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
    25. if ($extension == 'js') {
    26. $f = $fileInfo->openFile('r');
    27. $fdata = "";
    28. while ( ! $f->eof()) {
    29. $fdata .= $f->fgets();
    30. }
    31. $buffer[] = $fdata;
    32. }
    33. }
    34. $output = JSMin::minify(implode(";\n"$buffer));
    35. header("Content-type: application/x-javascript; charset: UTF-8");
    36. $forceGz = filter_input(INPUT_GET, 'gz', FILTER_SANITIZE_STRING);
    37. $forcePlain = filter_input(INPUT_GET, 'plain', FILTER_SANITIZE_STRING);
    38. $encoding = checkCanGzip();
    39. if ($forceGz) {
    40. header("Content-Encoding: {$encoding}");
    41. echo gzDocOut($output);
    42. elseif ($forcePlain) {
    43. echo $output;
    44. else {
    45. if ($encoding){
    46. header("Content-Encoding: {$encoding}");
    47. echo GzDocOut($output);
    48. else {
    49. echo $output;
    50. }
    51. }

Como podemos ver el script se recorre recursivamente los archivos Javascriptdentro de una carpeta, los une e incluso le pasa la librería jsmin para PHP para reducir el tamaño del archivo Javascript resultante, y mejorar así el tiempo total de descarga en el navegador.
Con este script en marcha, es muy sencillo crear un archivo HTML que dependiendo del entorno en el que nos encontremos (producción o desarrollo) incluya los tags HTML necesarios para usar la versión minimizada y unificada o todos los archivos Javascript por separado. Aquí pongo un ejemplo usando el motor de plantillas Smarty:
    1. <html>
    2. <head>
    3. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    4. <title></title>
    5. </head>
    6. <body>
    7. Hello World
    8. {if $dev}
    9. <script src="js1.js" type="text/javascript"></script>
    10. <script src="js2.js" type="text/javascript"></script>
    11. <script src="xxx/js1.js" type="text/javascript"></script>
    12. {else}
    13. <script src="js.php" type="text/javascript"></script>
    14. {/if}
    15. </body>
    16. </html> 

Sí, lo sé. Hay un problema con esta solución. Quizás hemos mejorado el rendimiento en el cliente (navegador) reduciendo el número de peticiones HTTP, pero ¿Qué pasa con el rendimiento en servidor? Hemos pasado de servir archivos Javascript estáticos, a necesitar PHP para generar archivos estáticos. Ahora el servidor consume más CPU. Dependiendo del hosting que estemos usando, más CPU supone más caro.
Alojar los recursos y archivos estáticos en servidores independientes al servidor web nos permite descargar en paralelo de forma más rápida y eficiente.

Otra regla de oro del rendimiento Web es poner el contenido estático en un servidor dedicado, o CDN sin PHP. Con esta segunda regla de oro conseguimos realizar descargar múltiples más eficientes de nuestro contenido estático y reducir el consumo de CPU, ya que los servidores que sirven contenido estático no necesitan crear una instancia de PHP ni interpretar ningún código.
Por lo tanto una mejor solución en la siguiente: Generamos el archivo Javascript estático de forma offline cuando desplegamos nuestra aplicación a producción. Yo lo suelo hacer con un simple comando de curl, el un terminal:
curl http://nov/js/js.php -o jsfull.minified.js 
 
Ahora nuestra plantilla Smarty pasa a ser:
    1. <html>
    2. <head>
    3. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    4. <title></title>
    5. </head>
    6. <body>
    7. Hello World
    8. {if $dev}
    9. <script src="js1.js" type="text/javascript"></script>
    10. <script src="js2.js" type="text/javascript"></script>
    11. <script src="xxx/js1.js" type="text/javascript"></script>
    12. {else}
    13. <script src="jsfull.minified.js" type="text/javascript"></script>
    14. {/if}
    15. </body>
    16. </html>

Además de esto es recomendable poner un prefijo a nuestro archivo js para que el nombre sea distinto y asegurarnos así que en navegador renueva la caché y se descarga la última versión.
<script src="jsfull.minified.20110216.js" type="text/javascript"></script> 
 

Y esto es todo. Como más de uno se habrá dado cuenta esta misma técnica podremos emplearla también para unir archivos CSS y reducir así los tiempos de carga de este tipo de archivos.

Podreis descargar el codigo fuente desde aqui.
Tambien podreis ver la demonstracion en vivo de lo explicado mas arriba entrando aqui.

P.D: Los codigos de los dichos archivos los encontrareis en la pagina web oficial del tutorial.

No hay comentarios:

Publicar un comentario