[ASP.NET] Modificar la salida HTML para Compactar (Minify). En el Render (RegEx, YUI Compresor for .NET) , con Response.Filter y con PageParserFilter

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
image image

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 sufriendo utilizando 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

  1. Sobrescribimos el método Render de una pagina, y “limpiamos” con expresiones regulares
  2. Sobrescribimos el método Render y limpiamos con componentes como el YUI Compressor for .NET
    1. Utilizamos la formidable clase en C# para minimizar CSS realizada por Michael Ash que es la que utiliza el YUI Compressor for .NET
  3. Un poco mas “técnicos”… Utilizamos Response.Filter
  4. Vamos hasta “al infinito y mas allá”. Utilizamos PageParserFilter.
    Aqui nos ayudamos con un componente que ya lo tiene implementado
  5. [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

image

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

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

image

 

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?

  1. Creamos nuestro PageParserFilter (o utilizamos un componente)
  2. 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í:

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