DotNetCampania
Il primo portale campano dedicato allo sviluppo software con tecnologie Microsoft

CSS e Javascript minification con ASP.NET

100% of people found this useful
CSS e Javascript minification con ASP.NET

Chiunque abbia a che fare con la programmazione web si sarà reso conto che l'utilizzo dei CSS, così come l'utilizzo di framework javascript come jQuery, sia ormai praticamente uno standard de-facto.
Per quanto riguarda i framework Javascript, questi vengono rilasciati costantemente sia in versione 'sources' che in versione 'minified', lasciando però la scelta del minifier da utilizzare esclusivamente ai creatori/mantainer del framework.
Purtroppo ho la smania di tenere costantemente tutto sotto controllo, ragion per cui negli ultimi mesi ho cominciato a studiare i vari software che operano la minification.
Sul web se ne trovano diversi:

Su tutti, la mia attenzione si è focalizzata molto su YUI Compressor, che a detta dei suoi sviluppatori

The YUI Compressor is JavaScript minifier designed to be 100% safe and yield a higher compression ratio than most other tools.

Dopo diversi test e diverse rilasci in produzione di fogli di stile e codice javascript minimizzato con questo tool, posso affermare di non aver mai riscontrato incompatibilità o problemi di sorta.
L'unico grande difetto di questo tool è il suo essere un tool da riga di comando, il che si traduce in una serie di step da eseguire in pre-produzione.
Se da un lato, le modifiche a queste tipologie di file, sono rare, una volta raggiunta la fase di rilascio, è pur vero che piccole migliorie vengono sempre apportate dopo il rilascio di una applicazione web.
Stanco quindi di aprire frequentemente il prompt dei comandi, ho cercato una soluzione da inglobare direttamente nei progetti web, che riuscisse a minimizzare on-the-fly i CSS e i JS.

Animato dall'esigenza e dalla scoperta di questo porting per .NET YUI Compresso for .NET, ho buttato giù questo semplice HttpHandler, capace di riconoscere le richieste giuste e rispondere con la versione minimizzata del file richiesto.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using Yahoo.Yui.Compressor;

namespace DotNetCampania.Web.Handlers
{
	public class YUICompressor : IHttpHandler
	{
		private const int DEFAULT_CACHE_DURATION = 1440;
		private bool useCache = true;
		private bool noCompression = false;

		public bool IsReusable { get { return true; } }
		
		public void ProcessRequest(HttpContext context)
		{
			bool.TryParse(context.Request.QueryString.Get("useCache"), out this.useCache);
			bool.TryParse(context.Request.QueryString.Get("noCompression"), out this.noCompression);
			context.Response.ContentType = "text/plain";
			string filePath = GetFilePath();
			string fileExtension = Path.GetExtension(filePath);
			if (File.Exists(filePath))
			{
				context.Response.AddHeader("Content-Disposition", "filename=" + Path.GetFileName(filePath));
				switch (fileExtension)
				{
					case ".css":
						context.Response.ContentType = "text/css";
						if (!this.noCompression) 
							CompressCSS(filePath);
						else
							HttpContext.Current.Response.WriteFile(filePath);
						break;
					case ".js" :
						context.Response.ContentType = "application/x-javascript";
						if (!this.noCompression)
							CompressJavaScript(filePath);
						else
							HttpContext.Current.Response.WriteFile(filePath);
						break;
					default :
						context.Response.StatusCode = 404;
						break;
				}
			}
			else
			{
				context.Response.StatusCode = 404;
			}
			context.Response.Flush();
			context.Response.End();
		}
		
		private string GetFilePath()
		{
			string filePath = HttpContext.Current.Request.Url.AbsolutePath;
			filePath = filePath.TrimEnd(".axd".ToCharArray());
			filePath = HttpContext.Current.Server.MapPath(filePath);
			return filePath;
		}

		private void CompressCSS(string filePath)
		{
			if (this.useCache & HttpContext.Current.Cache[HttpContext.Current.Request.Url.AbsolutePath.GetHashCode().ToString()] != null)
			{
				HttpContext.Current.Response.Write((string)HttpContext.Current.Cache[HttpContext.Current.Request.Url.AbsolutePath.GetHashCode().ToString()]);
				return;
			}
			object fileLock = new object();
			lock (fileLock)
			{
				StreamReader sr = new StreamReader(filePath, true);
				string compressed = CssCompressor.Compress(sr.ReadToEnd());
				HttpContext.Current.Response.Write(compressed);
				if (this.useCache)
					HttpContext.Current.Cache.Add(HttpContext.Current.Request.Url.AbsolutePath.GetHashCode().ToString(), compressed, null, DateTime.MaxValue, new TimeSpan(0, DEFAULT_CACHE_DURATION, 0), System.Web.Caching.CacheItemPriority.Normal, null);
				sr.Close();
			}
		}

		private void CompressJavaScript(string filePath)
		{
			if (this.useCache & HttpContext.Current.Cache[HttpContext.Current.Request.Url.AbsolutePath.GetHashCode().ToString()] != null)
			{
				HttpContext.Current.Response.Write((string)HttpContext.Current.Cache[HttpContext.Current.Request.Url.AbsolutePath.GetHashCode().ToString()]);
				return;
			}
			object fileLock = new object();
			lock (fileLock)
			{
				StreamReader sr = new StreamReader(filePath, true);
				string compressed = JavaScriptCompressor.Compress(sr.ReadToEnd());
				HttpContext.Current.Response.Write(compressed);
				if (this.useCache)
					HttpContext.Current.Cache.Add(HttpContext.Current.Request.Url.AbsolutePath.GetHashCode().ToString(), compressed, null, DateTime.MaxValue, new TimeSpan(0, DEFAULT_CACHE_DURATION, 0), System.Web.Caching.CacheItemPriority.Normal, null);
				sr.Close();
			}
		}
	}
}
    

L'handler va ovviamente mappato nel web.config. Devo però fornire una doverosa annotazione: nel mio caso ho mappato le estensioni *.js.axd e *.css.axd. Questa scelta mi obbliga a dover fare attenzione nelle pagine .aspx o .html che creo, in quanto i riferimenti ai fogli di stile ed ai file javascript devono finire con questa estensione per essere processati. In un contesto di hosting condiviso (leggi Aruba), dove non ho possibilità di intervento sulle estensioni mappate in IIS, questa mi sembrava la scelta migliore. In contesti di maggior libertà di mapping delle estensioni in IIS, sarebbe bastato mappare le estensioni .css e .js sull'engine di ASP.NET, e mappare le stesse estensioni sull'handler.

Come si evince dal codice, l'handler utilizza anche la cache in modo da evitare di processare i singoli file ad ogni richiesta, ma solo quando strettamente necessario.

Una ulteriore aggiunta potrebbe essere l'utilizzo di chiavi negli appSettings, in modo da controllare l'abilitazione globale dell'handler direttamente da web.config, ma lo lascio fare a voi :)

Recent Comments

By: Alessandro Forte Posted on 13 Apr 2010 12:42

Ciao Gianluca,

una domanda...

Come utilizzo la dll da linea di comando?

Cioè la mia idea sarebbe quella di lanciare la minimizzazione solo nell'ambiente di produzione.

In questo modo avrei i file minimizzati in un apposita cartella.

Devo per forza utilizzare MSBuild con MSBuild.xml o esiste qualche altro modo indolore?

Grazie

By: ilNero Posted on 15 Apr 2010 14:55

Ciao Alessando! In base alle tue necessità direi che la libreria YUI Compressor for .NET ti supporta a pieno: qui trovi un MSBuild.xml di esempio yuicompressor.codeplex.com/wikipage . Inoltre hai la possibilità di effettuare anche il file combining (combinare tutti i file minimizzati in un unico file).

Nel mio caso ho predisposto una minimizzazione a runtime su richiesta, soluzione che trovo più comoda.

Associazione Culturale DotNetCampania - C.F.: 95127870632

Powered by Community Server (Commercial Edition), by Telligent Systems