Compartir a través de


Tecnología de vanguardia

Programación de CSS: unión y minificación

Dino Esposito

Dino EspositoUn viejo mantra del desarrollo web reza que demasiadas solicitudes afectan el rendimiento de la página. Si disponemos de algún truco para reducir la cantidad de solicitudes HTTP gatilladas por nuestras páginas web, entonces lo aplicamos a como dé lugar. A medida que las páginas web se llenan de contenidos visuales más sofisticados, el costo de descargar los recursos asociados (como CSS, scripts e imágenes) crece de manera significativa. Ciertamente, gran parte de estos recursos los puede almacenar el explorador localmente en caché, pero puede resultar difícil sostener toda la carga inicial. Además, al usar menos solicitudes de tamaño menor se reduce el ancho de banda, disminuye la latencia y mejora la duración de la batería. Estos son factores críticos cuando se explora con dispositivos móviles. Una solución ampliamente aceptada para estos problemas combina dos acciones: la unión y la minificación.

En este artículo analizaré la unión y la minificación de los archivos CSS desde el punto de vista específico de las herramientas de software disponibles en ASP.NET MVC 4. Esto sigue después de mi última columna “Creación de vistas móviles optimizadas en ASP.NET MVC 4, parte 2: uso de WURFL” (msdn.microsoft.com/magazine/dn342866).

Aspectos básicos de la unión y la minificación

La unión es el proceso de consolidar un número de recursos diferentes dentro de un solo recurso descargable. Por ejemplo, una agrupación puede estar compuesta por varios archivos JavaScript o CSS que cargamos en la máquina local mediante una sola solicitud HTTP a un extremo ad hoc. La minificación, por otra parte, es una transformación que se aplica al recurso. Concretamente, la minificación es la eliminación de todos los caracteres innecesarios de un recurso textual, sin alterar la funcionalidad esperada. Esto significa acortar los identificadores, emplear alias para las funciones y eliminar comentarios, caracteres de espacio y líneas nuevas; en general, todos los caracteres que usualmente se agregan para mejorar la legibilidad pero que ocupan espacio y no cumplen realmente ningún objetivo funcional.

La unión y la minificación se pueden aplicar juntas, pero son procesos independientes. Según las necesidades, podemos optar por crear uniones solamente o por minificar los archivos individuales. Usualmente, sin embargo, en los sitios de producción no hay ninguna razón que impida aplicar la unión y minificación en todos los archivos CSS y JavaScript; excepto quizás algunos recursos comunes como jQuery que probablemente se podrían encontrar en una red de entrega de contenido (CDN) conocida. A la hora de depurar, sin embargo, la situación es completamente diferente: un recurso minificado o unido resulta difícil de leer y revisar, así que probablemente nos convendrá deshabilitar la unión y la minificación.

Muchos marcos proveen servicios de unión y minificación con niveles ligeramente diferentes de extensibilidad y diferentes conjuntos de características. Pero, en gran medida, todos ofrecen las mismas funcionalidades, así que la elección de uno u otro es cosa simplemente de preferencias. Si escribe una aplicación con ASP.NET MVC 4, la opción natural para la unión y minificación es el Marco de optimización de Microsoft ASP.NET, disponible a través de un paquete NuGet (bit.ly/1bS8u4B) y que se muestra en la figura 1.

Installing the Microsoft ASP.NET Web Optimization FrameworkFigura 1 Instalación del Marco de optimización de Microsoft ASP.NET

Uso de la unión de CSS

En mi opinión, la mejor forma de entender la mecánica de la unión de CSS es comenzar con un proyecto ASP.NET MVC totalmente vacío. Esto significa crear un proyecto nuevo con la plantilla Proyecto vacío y eliminar todos los archivos y referencias que no se usan. Luego, digamos que agregamos un archivo de diseño que vincula dos archivos CSS:

<link rel="stylesheet"
  href="@Url.Content("~/content/styles/site.css")"/>

Si abre la página y examina la actividad de la red con Fiddler o las Herramientas de desarrollo de Internet Explorer, podrá ver dos descargas que van en paralelo. Este es el comportamiento predeterminado.

Observe que en ASP.NET MVC 4 podemos reescribir el marcado anterior de manera más compacta con la nueva prestación Styles.Render:

@Styles.Render(
  "~/content/styles/site.css",
  "~/content/styles/site.more.css")

Ubicada bajo System.Web.Optimization, la clase Styles es mucho más poderosa de lo que podría parecer a primera vista. El método Styles.Render también funciona con uniones. Esto significa que el método tiene diferentes sobrecargas, de las cual una acepta una matriz con las URL de los archivos CSS. Otra sobrecarga, sin embargo, recibe el nombre de una unión creada con anterioridad (en breve lo veremos con más detención). En este caso, emite un elemento <link> único y hace que apunte hacia una dirección URL que devuelve todas las hojas de estilos en forma unida o minificada.

Creación de uniones de CSS

Habitualmente, creamos las uniones de manera programática en global.asax. En concordancia con el patrón ASP.NET MVC 4 para la configuración del código, podemos crear una clase BundleConfig bajo la carpeta App_Start y exponer un método de inicialización estático:

BundleConfig.RegisterBundles(BundleTable.Bundles);

Una unión de CSS es simplemente una colección de hojas de estilos. Este es el código necesario para agrupar los dos archivos CSS que mencionamos previamente en una sola descarga:

public class BundleConfig
{
  public static void RegisterBundles(BundleCollection bundles)
  {
    // Register bundles first
    bundles.Add(new Bundle("~/mycss").Include(
      "~/content/styles/site.css",
      "~/content/styles/site.more.css"));
    BundleTable.EnableOptimizations = true;
  }
}

Creamos una clase Bundle nueva y pasmos al constructor la ruta de acceso virtual que se usará para hacer referencia a la unión dentro de una vista o una página web. También podemos establecer la ruta de acceso virtual posteriormente mediante la propiedad Path. Para asociar los archivos CSS con la unión, usamos el método Include. Este método recibe una matriz de cadenas que representan rutas de acceso virtuales. Podemos indicar los archivos CSS en forma explícita como en el ejemplo anterior o podemos usar una cadena con un patrón, como este:

bundles.Add(new Bundle("~/mycss")
  .Include("~/content/styles/*.css");

La clase Bundle también tiene un método IncludeDirectory que nos permite indicar la ruta de acceso a un directorio virtual determinado y posiblemente una cadena de coincidencia de patrones y una marca booleana para habilitar la búsqueda también en los subdirectorios.

La propiedad booleana EnableOptimization que se aprecia en la clase Bundle­Table en el fragmento de código anterior se refiere a la necesidad de habilitar la unión en forma explícita. Si no se habilita en forma programática, la unión simplemente no funciona. Tal como se mencionó, la unión es una forma de optimización; como tal, se realiza principalmente cuando el sitio se encuentra en producción. La propiedad EnableOptimization es una forma conveniente de configurar la unión tal como debiera quedar en producción, pero lo ideal es deshabilitarla hasta que el sitio se compile en modo de depuración. Incluso podríamos usar el siguiente código:

if (!DEBUG)
{
  BundleTable.EnableOptimizations = true;
}

Más características avanzadas de la unión

En lo que respecta a las uniones CSS, no hay mucho más que sea de importancia, excepto la minificación. Sin embargo, la clase BundleCollection es una clase de propósito general que también se puede usar para unir archivos de scripting. Concretamente, la clase BundleCollection tiene dos características que vale la pena mencionar, aunque son útiles principalmente al unir archivos de scripts en vez de archivos CSS.

La primera característica es la ordenación. La clase BundleCollection tiene una propiedad llamada Orderer del tipo IBundleOrderer. Por muy obvio que pueda parecer, un ordenador es el componente responsable de determinar el orden en el que queremos unir los archivos para la descarga. El ordenador predeterminado es la clase DefaultBundleOrderer. Esta clase une los archivos en el orden que resulta de los valores que se establecen mediante FileSetOrderList, una propiedad de BundleCollection. FileSetOrderList se diseñó para que fuera una colección de clases BundleFileSetOrdering. Cada una de estas clases define un patrón para seleccionar archivos (por ejemplo jquery-*), y el orden en el que las clases BundleFileSetOrdering se agregan a FileSetOrderList determina el orden mismo de los archivos. Por ejemplo, con la configuración predeterminada, todos los archivos jQuery siempre se unen antes de los archivos de Modernizr.

El impacto de la clase DefaultBundleOrderer en los archivos CSS es más limitado. Si tenemos un archivo llamado “reset.css” o “normalize.css” en el sitio web, estos archivos se unen automáticamente antes de cualquier otro archivo CSS y reset.css siempre va antes de normalize.css. Para aquellos lectores que no estén familiarizados con las hojas de estilos de restablecimiento y normalización, la finalidad de estos es proporcionar un conjunto estándar de atributos de estilo para todos los elementos HTML, de modo que las páginas no hereden las configuraciones de cada explorador, como fuentes, tamaños y márgenes. Aunque existen contenidos recomendados para ambos tipos de archivos CSS, usted elige el contenido. Si tiene archivos con estos nombres en su proyecto, entonces ASP.NET MVC 4 se encarga especialmente de que se unan antes de cualquier otra cosa. Si desea reemplazar el ordenador predeterminado y prescindir de los ordenamientos predeterminados para las uniones de archivos, tiene dos opciones. Primero, puede crear su propio ordenador que opere en función de cada unión. Este es un ejemplo que simplemente hace caso omiso de los órdenes predeterminados:

public class PoorManOrderer : IBundleOrderer
{
  public IEnumerable<FileInfo> OrderFiles(
    BundleContext context, IEnumerable<FileInfo> files)
  {
     return files;
  }
}

Se usa del siguiente modo:

var bundle = new Bundle("~/mycss");
bundle.Orderer = new PoorManOrderer();

Además, se pueden restablecer todos los ordenamientos con el siguiente código:

bundles.ResetAll();

En este caso, el efecto de usar el ordenador predeterminado o el ordenador minimalista que vimos es el mismo. Observe, eso sí, que ResetAll también restablece el orden de los archivos de scripts.

La segunda característica, más avanzada, es la lista de omisión. Definida mediante la propiedad IgnoreList de la clase BundleCollection, define las cadenas de coincidencia de patrones que se seleccionan para la inclusión pero que se deberían omitir. La principal ventaja de las uniones, tal como se implementan en ASP.NET MVC 4, es que podemos obtener todos los archivos JavaScript (*.js) en la carpeta con una sola llamada. Sin embargo, es probable que *.js también seleccione archivos que no queremos descargar, como por ejemplo los archivos vsdoc.js. La configuración predeterminada de IgnoreList se encarga de las situaciones más comunes y también nos ofrece realizar personalizaciones.

Unión de CSS en la práctica

La unión de CSS es una característica de optimización poderosa, ¿pero cómo funciona en la práctica? Observe el siguiente código:

var bundle = new Bundle("~/mycss");
bundle.Include("~/content/styles/*.css");           
bundles.Add(bundle);

El tráfico HTTP correspondiente se muestra en la figura 2.

The Second Request Is a Bundle with Multiple CSS Files
Figura 2 La segunda solicitud es una unión con varios archivos CSS

La primera solicitud es para la página principal; la segunda solicitud no apunta hacia ningún archivo CSS específico, sino que se refiere a una unión que contiene todos los archivos CSS de la carpeta content/styles (ver figura 3).

An Example of Bundled CSS
Figura 3 Ejemplo de CSS unido

Ahora con minificación

La clase Bundle solo se ocupa de combinar varios recursos para que se capturen en una sola descarga y se almacenen en caché.

Como se aprecia en el código de la figura 3, sin embargo, el contenido descargado está organizado con caracteres de espacio y nueva línea para que resulte legible. Sin embargo, a los exploradores no les interesa la legibilidad y el código en CSS de la figura 3 es perfectamente equivalente al siguiente código minificado:

html,body{font-family:'segoe ui';font-size:1.5em;margin:10px}
  html,body{background-color:#111;color:#48d1cc}

En el caso del CSS sencillo que uso en este ejemplo, la diferencia probablemente no sea significativo. Pero el CSS minificado toma importancia en los sitios empresariales con hojas de estilos grandes.

¿Cómo incorporamos la minificación? Es tan fácil como reemplazar la clase Bundle con StyleBundle. StyleBundle es sorprendentemente sencilla. Se hereda de Bundle y simplemente consiste en un constructor diferente:

public StyleBundle(string virtualPath)
  : base(virtualPath, new IBundleTransform[] { new CssMinify() })
{
}

La clase Bundle tiene un constructor que acepta una lista de objetos IBundleTransform. Estas transformaciones se aplican al contenido, una tras otra. La clase StyleBundle simplemente agrega el transformador CssMinify. CssMinify es el minificador predeterminado de ASP.NET MVC 4 (y las versiones posteriores) y se basa en las herramientas de optimización de WebGrease (webgrease.codeplex.com). Huelga decir que, si deseamos cambiar a otro minificador, lo único que necesitamos hacer es tomar la clase (una implementación de IBundleTransform) y pasarla mediante el constructor de la clase Bundle.

No más excusas

El Marco de optimización web que viene con ASP.NET MVC 4 aporta una característica pequeñísima, pero es una característica que más vale tener que no tener. Simplemente no hay ninguna razón para no minificar y unir los recursos. Hasta ahora, una excusa posible era la falta de prestaciones nativas en la plataforma ASP.NET. Esto ya no es el caso con ASP.NET MVC 4 y las versiones posteriores.

En cuanto a los archivos CSS, sin embargo, debemos considerar otro aspecto: la generación dinámica de estilos. Diseñado para ser una mera piel que se aplica a las páginas, CSS se está convirtiendo en un recurso más dinámico, hasta tal punto que se han desarrollado algunos lenguajes de seudoprogramación para generar CSS en forma programática. La próxima vez hablaremos precisamente sobre este tema.

Dino Esposito es el autor de “Architecting Mobile Solutions for the Enterprise” (Microsoft Press, 2012) y del libro próximo “Programming ASP.NET MVC 5” (Microsoft Press). Como evangelizador técnico para las plataformas .NET y Android en JetBrains y orador frecuente en numerosos eventos mundiales de la industria, Esposito comparte su visión sobre el software en software2cents.wordpress.com y en Twitter en twitter.com/despos.

Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo: Christopher Bennage (Microsoft)
Christopher Bennage es desarrollador en Microsoft en el equipo de Patrones y procedimientos. Su trabajo es descubrir, recopilar y fomentar las prácticas que le alegran la vida a los desarrolladores. Entre sus intereses técnicos más recientes se encuentran JavaScript y el desarrollo (informal) de juegos. Mantiene un blog en dev.bennage.com.