Como en los foros de MSDN alguien necesitaba de un tema similar, lo tenia en borrador y encontré el “justificativo” de terminarlo. Ahora lo tengo para referencias de “opciones de capturar la salida HTML”, encubriendo para minificar el HTML resultante.
Que es Compactar (Minify) HTML?
La idea es limpiar el HTML resultante de caracteres que no son necesarios para renderizar la pagina (espacios en blanco innecesarios, salto de línea) esto se llama Minificar (Minification) Por que son innecesarios estos caracteres? Solo están para una mejor lectura para nosotros “los mortales desarrolladores” la maquina no los necesita.. y el HTML tampoco 🙂
Aquí utilizare de sinónimo Minificar = Minimizar = Compactar
Puedes ver con un ejemplo online de esta tarea:
Veamos a “vista de águila”…
Antes | Después |
Se utiliza generalmente para los Javascript, y los CSS donde existen muchas herramientas online, incluso para .NET el Microsoft Ajax Minifier.
Es necesario compactar la salida HTML?
“No colocar todos los huevos en la misma canasta/cesta”
Depende… utilizando herramientas como YSlow o Page Speed no darán información en que lugar “ganamos más” optimizando. Y no siempre es en la compactación del HTML, ya que con la configuración de la Compresión HTTP (gzip) en el IIS es suficiente.
Entonces que tenemos que compactar?
Lo que si es necesario es comprimir, compactar y agrupar (son tres técnicas que en conjunto logran reducir tiempos deF descarga) los CSS y JS porque allí si ganamos en cantidad de archivos a descargar, como así también los CSS Sprites pero bueno eso ya es otro tema
Todo el mundo ya conoce que tenemos herramientas para estas tareas como por ejemplo Microsoft Ajax Minifier o YUI Compressor for .NET (u otras similares) Algunas con todas las características que necesitamos otras con menos.
Primero lo primero: Como modificamos el HTML resultante?
Si conocemos el ciclo de vida de una pagina ASP.NET, lo que se nos viene a la cabeza como lugar para realizar la acción es el método Render, por lo que hay que sobrescribirlo.
La idea la tome por aquí para obtener el HTML resultante, que era para quitar el Viewstate, así que nos sirve para este ejemplo.
Ahora el objetivo: Minimizar el HTML
Lo primero que se nos viene la cabeza es reemplazar cadenas, y allí el “balón de oro” es el que tenga mejor performance para cadenas inmutables 😉
Muy bueno este articulo: Comparing RegEx.Replace, String.Replace and StringBuilder.Replace – Which has better performance?
Pero además, vamos a necesitar si o si expresiones regulares (esos textos que como siempre digo parece que un Vulcano los haya escrito) para detectar algunos patrones que nos son simples de buscar. Entonces para el ejemplo RegEx.Replace. (perdonen pero hay que sufrir con esto de expresiones regulares)
Objetivos secundarios de este post
Vamos a ver opciones para modificar el HTML resultante para minimizarlo, las complicaciones que podamos tener y porque no es uno de los pilares de la optimización pero aunque no lo utilicemos veremos tecnicas que en alguna oportunidad nos pueden ayudar para otras tareas de modificar la salida:
- Modificar la salida de una pagina HTML
- Seguir
sufriendoutilizando con expresiones regulares y como nos ayudan bastante (aunque no las entendamos luego de 1 hora) - Utilizar los Response.Filter (Filtros para la salida)
- Conocer y ver una implementación (de terceros) de PageParserFilter
Que opciones tenemos para minimizar el HTML
- Sobrescribimos el método Render de una pagina, y “limpiamos” con expresiones regulares
- Sobrescribimos el método Render y limpiamos con componentes como el YUI Compressor for .NET
- Utilizamos la formidable clase en C# para minimizar CSS realizada por Michael Ash que es la que utiliza el YUI Compressor for .NET
- Un poco mas “técnicos”… Utilizamos Response.Filter
- Vamos hasta “al infinito y mas allá”. Utilizamos PageParserFilter.
Aqui nos ayudamos con un componente que ya lo tiene implementado - [Idea a futuro] Componente del IIS para limpiar caracteres en blanco… (no lo tengo implementado, si alguien tiene un ejemplo bienvenido en los comentarios)
OPCION 1: Sobrescribimos el método Render de una pagina, limpiamos con expresiones regulares
Aquí sobrescribimos el evento Render para obtener la cadena de salida y tratarla
protected override void Render(HtmlTextWriter writer) { //Obtemos el HTML resultante dentro de un TextWriter TextWriter tw = new StringWriter(); HtmlTextWriter htmlWriter = new HtmlTextWriter(tw); //Renderizamos la pagina base.Render(htmlWriter); htmlWriter.Close(); //Obtenemos la cadena del HTML string htmlResultante = tw.ToString(); //Limpiando string htmlMin = YODA.Web.HTMLUtil.MinificarHTML(htmlResultante); //Escribiendo la salida writer.Write(htmlMin); }
Haciendo uso de expresiones regulares simples podremos Minificar el HTML, solo para algunos patrones (espacios entre tags, saltos de linea, tabs, espacios en blanco mayores que 2
namespace YODA.Web { public class HTMLUtil { public HTMLUtil() { // // TODO: Add constructor logic here // } public static string MinificarHTML(string htmlCadena) { Regex regEspaciosEntreTags = new Regex(@">(?! )s+", RegexOptions.Singleline); Regex regSaltoDeLinea = new Regex(@"([ns])+?(?<= {2,})<", RegexOptions.Singleline); Regex regSaltoDeLinea2 = new Regex(@"rn", RegexOptions.Singleline); Regex regTabs = new Regex(@"t", RegexOptions.Singleline); Regex regEspaciosEnBlanco = new Regex(@"s{2,}", RegexOptions.Singleline); htmlCadena = regEspaciosEntreTags.Replace(htmlCadena, ">"); htmlCadena = regSaltoDeLinea.Replace(htmlCadena, "<"); htmlCadena = regSaltoDeLinea2.Replace(htmlCadena, string.Empty); htmlCadena = regTabs.Replace(htmlCadena, string.Empty); htmlCadena = regEspaciosEnBlanco.Replace(htmlCadena, string.Empty); return htmlCadena.ToString(); } } }
Puedes buscar en regexlib.com algunos ejemplos. En el ejemplo para descargar dejo varias implementaciones.
NOTA IMPORTANTE: Este código no es del todo óptimo, pero sirve como ejemplo. Ya que espacios de variables en javascript var vble1 = “ valor 1”; lo optimiza por los espacios; Los saltos de linea no tiene en cuenta si lo que viene es una funcion js, y asi sucesivamente. Vuelvo a insistir, antes que el lector se acuerde de mi familia, que esto es un ejemplo de como modificar
OPCION 2: Sobrescribimos el método Render y limpiamos con componentes como el YUI Compressor for .NET
NOTA Opción 2: Aquí utilice el componente pero dentro del componente la clase que hace la magia de minimizar es un código de autoría de Michael Ash
- En el código fuente de YUI Compressor for .NET
http://yuicompressor.codeplex.com/SourceControl/changeset/view/62134#983563
- [El código original] Update to CSS Minification (por Michael Ash)
http://regexadvice.com/blogs/mash/archive/2008/04/27/Update-to-CSS-Minification.aspx
Asi que lo dejo también dentro de ejemplo a descargar en la carpeta App_Code
NOTA: No podremos utilizar Microsoft Ajax Minifier solo tenemos dos métodos que nos nos ayudan a minimizar JS y CSS incluso de obuscar para minimizar el nombre de variables
Opcion 1.1, 2.1 (Tip): Heredar comportamiento (sobreescribir el Render en un solo lugar)
Ambas Opciones (1, 2) que sobrescriben el Render pueden estar en una Pagina Base para luego heredarla o en una Master base, como el ejemplo
namespace YODA.Web { public class MasterPageBase : System.Web.UI.MasterPage { public MasterPageBase() { // // TODO: Add constructor logic here // } protected override void Render(HtmlTextWriter writer) { //Obtemos el HTML resultante dentro de un TextWriter TextWriter tw = new StringWriter(); HtmlTextWriter htmlWriter = new HtmlTextWriter(tw); //Renderizamos la pagina base.Render(htmlWriter); htmlWriter.Close(); //Obtenemos la cadena del HTML string htmlResultante = tw.ToString(); //Limpiando string htmlMin = YODA.Web.HTMLUtil.MinificarHTML(htmlResultante); //Escribiendo salida writer.Write(htmlMin); } } }
hay que hacer que la Master de nuestro sitio herede de la MasterPageBase que creamos
public partial class SiteWithOverrrideRender : YODA.Web.MasterPageBase { protected void Page_Load(object sender, EventArgs e) { } }
OPCION 3: Utilizamos Response.Filter
Aqui debemos crear un filtro, que es una clase que hereda en este ejemplo de MemoryStream y realice las acciones correspondientes
“(…)Cuando se crea un objeto Stream y se establece la propiedad Filter para el objeto Stream, toda la salida HTTP enviada por Write pasa por el filtro.(…)”
namespace YODA.Web { public class MinificarHTMLFiltro : MemoryStream { public Stream HtmlStream { get; set; } public MinificarHTMLFiltro(Stream htmlStream) { this.HtmlStream = htmlStream; } public override void Write(byte[] buffer, int offset, int count) { string contenidoEnElBuffer = UTF8Encoding.UTF8.GetString(buffer); //Acciones a realizar //Accion 1: Minificar salida limpinado tabs/espacios en blanco contenidoEnElBuffer = YODA.Web.HTMLUtil.MinificarHTML3(contenidoEnElBuffer); HtmlStream.Write(UTF8Encoding.UTF8.GetBytes(contenidoEnElBuffer), offset, UTF8Encoding.UTF8.GetByteCount(contenidoEnElBuffer)); } } }
Y lo utilizamos en el Load de la pagina, simple no?
protected void Page_Load(object sender, EventArgs e) { Response.Filter = new MinificarHTMLFiltro(Response.Filter); }
OPCION 4: Utilizamos PageParserFilter
Como ya lo dije, aquí es utilizando PageParserFilter, pero no comente que es un poco difícil (y doloroso) crear una clase que herede de PageParserFilter y haga lo que necesitamos, ya que deben construir todos los controles del árbol de controles de una pagina…
Gracias a enlace que compartió Jason Ulloa en los foros de MSDN, pude llegar hasta aquí:
Que es una implementación de PageParserFilter justamente para el objetivo de limpiar los espacios en blanco. Y podremos utilizarlo en ASP.NET Webforms y en ASP.NET MVC
Como lo implementamos, configuramos?
- Creamos nuestro PageParserFilter (o utilizamos un componente)
- Registramos en la seccion pages del web.config
<system.web> <pages pageParserFilterType="YODA.Web.MinificarHTMLPageParserFilter"> </pages>
...En el ejemplo Omari Нa que lo puedes descargar
<system.web> <pages pageParserFilterType="Omari.Web.UI.WhiteSpaceCleaner, WhiteSpaceCleanerForWebFormsAndMVC2"> </pages> ...
OPCION 5: [Idea a futuro] Componente del IIS para limpiar caracteres en blanco…minimizar
Mejorar… siempre! Para los mas experimentados… Comprimir todo en el IIS. CREAR UN COMPONENTE
Podríamos armar un componente para el IIS para que comprima el HTML resultante de cualquier pagina… ya existe? por favor háganmelo saber a través de los comentarios
Ganamos algo? Algunos inconvenientes
Comente mas arriba que hay otros lugares en nuestra app web para empezar a optimizar, porque no siempre ganamos haciendo esto de compactar la salida HTML
Problemas que podemos tener:
- Como la minificacion es automática, el documento HTML mal formado puede ocasionar una salida “no bien formada” (con errores para el renderizado en el navegador) (incluso eliminando porciones de contenido)
Por qué puede estar mal formado? porque somos nosotros simples mortales lo que escribimos parte del mismo. Todavía no es todo “autogenerado”, todavía 😉Es decir: Si alguien se le fue “los dedos” y escribió <br> /> y al limpiar entre tags no sabremos que puede pasar.
Mas arriba comente algo sobre por ejemplo limpiar bien los saltos de linea que esta demas (y el problema de javascript)
- Hay que tener en cuenta el tiempo que involucra tener esta funcionalidad y realizarla por cada pagina, en cada peticion
Con las otras tecnicas se puede mejorar.
Tips/Recomendaciones
Esto de compactar HTML ( o CSS y JS) tiene que ir de la mano de otras técnicas para optimiza porque no es tampoco nada recomendable estar haciendo esto siempre, ya que es un punto que hay procesamiento. Por este motivo podemos ayudarnos con:
- Comprimir el contenido que se envía al cliente (típicamente con Gzip) ya la mayoría de los “mortales” utilizamos navegadores modernos que soportan recibir este tipo de contenido.
- Cachear un poco el contenido en el servidor y en el cliente cuando se pueda,
Algunos tips a tener en cuenta para optimizar aplicaciones web pueden verlo aquí:
- [Geeks.ms] Material Charla – Técnicas Ninja de Optimización Web (por Gonzalo Perez)
Dejo otros enlaces mas abajo.
Síntesis
Se aprendió además de la idea de minificar el HTML, varias opciones de como modificar la salida HTML de nuestros proyectos con ASP.NET Webforms.
Ejemplo para descargar
Enlaces
- Response.Filter
-
Cómo: Almacenar los resultados de una página dinámica para una página HTML en ASP.NET mediante Visual C# .NET
http://support.microsoft.com/kb/810205/es
-
- Optimizando sitio web
- 14 Rules for Faster-Loading Web Sites
http://stevesouders.com/hpws/rules.php - Best Practices for Speeding Up Your Web Site
http://developer.yahoo.com/performance/rules.html - Reducing the file size of HTML documents
http://code.google.com/speed/articles/optimizing-html.html
- 14 Rules for Faster-Loading Web Sites
- PagePageFilter
Hola, muy buen articulo. Hecho en falta la opcion de realizar la compresion con un módulo Http ¿la has descartado por alguna razon?
@Pedro, No me he acordado. Gracias por el aviso.
No lo tenia en el listado cuando hice el borrador (hace bastantes meses)
Voy a hacerme un tiempo e implementarlo con un módulo Http, y que quede en el ejemplo. Alguna sugerencia?
Excelente aporte, el comprimir con GZIP desde codigo y no dejarlo a IIS hacerlo, es recomendable? y si aplica para un ambiente productivo?
Gracias por la respuesta.
@Alvaro
La recomendación es hacerlo mediante IIS.
Un enlace: HTTP Compression
http://www.iis.net/ConfigReference/system.webServer/httpCompression
Espero que te sirva