Improve ASP.NET Performance - CSSmin
If you follow Douglas Crockford's work, you might know about JSMin, a bit of code written for various languages to optimize JavaScript to make it smaller. One thing it does is makes everything go to one line, eliminating some of the space by removing white space.
In Web 2.0 applications, CSS is all you have for styles. In most apps, the CSS can get large really quick. One way to improve your performance (of not only ASP.NET apps) is to minify your CSS. Various people have created this sort of functionality for you, but you aren't given the flexibility of doing the minification yourself.
In my company's new website, part of the design decision was to always have an eye out for performance. To do this, we needed 1 CSS file and have it as small as possible. To do the "small as possible" part, we implemented a series of REGEX replacements that did all the work for minifying our CSS. Here's what we came up with:
C#:
1: public static string CompressCSS(string body)
2: {
3: body = Regex.Replace(body, "/\\*.+?\\*/", "", RegexOptions.Singleline);
4: body = body.Replace(" ", string.Empty);
5: body = body.Replace(Environment.NewLine + Environment.NewLine + Environment.NewLine, string.Empty);
6: body = body.Replace(Environment.NewLine + Environment.NewLine, Environment.NewLine);
7: body = body.Replace(Environment.NewLine, string.Empty);
8: body = body.Replace("\\t", string.Empty);
9: body = body.Replace(" {", "{");
10: body = body.Replace(" :", ":");
11: body = body.Replace(": ", ":");
12: body = body.Replace(", ", ",");
13: body = body.Replace("; ", ";");
14: body = body.Replace(";}", "}");
15: body = Regex.Replace(body, "/\\*[^\\*]*\\*+([^/\\*]*\\*+)*/", "$1");
16: body = Regex.Replace(body, "(?<=[>])\\s{2,}(?=[<])|(?<=[>])\\s{2,}(?= )|(?<=&ndsp;)\\s{2,}(?=[<])", string.Empty);
17:
18: return body;
19: }
VB:
1: Public Shared Function CompressCSS(ByVal body As String) As String
2: body = Regex.Replace(body, "/\*.+?\*/", "", RegexOptions.Singleline)
3: body = body.Replace(" ", String.Empty)
4: body = body.Replace(Environment.NewLine + Environment.NewLine + Environment.NewLine, String.Empty)
5: body = body.Replace(Environment.NewLine + Environment.NewLine, Environment.NewLine)
6: body = body.Replace(Environment.NewLine, String.Empty)
7: body = body.Replace("\t", String.Empty)
8: body = body.Replace(" {", "{")
9: body = body.Replace(" :", ":")
10: body = body.Replace(": ", ":")
11: body = body.Replace(", ", ",")
12: body = body.Replace("; ", ";")
13: body = body.Replace(";}", "}")
14: body = Regex.Replace(body, "/\*[^\*]*\*+([^/\*]*\*+)*/", "$1")
15: body = Regex.Replace(body, "(?<=[>])\s{2,}(?=[<])|(?<=[>])\s{2,}(?= )|(?<=&ndsp;)\s{2,}(?=[<])", String.Empty)
16:
17: Return body
18: End Function
So you see it's just a few logical replacements. These few replacements makes a world of difference. The amount saved in bandwidth and caching is amazing.
UPDATE: For those who asked, this is how I use this code. First what I do is enumerate through The files in my CSS directory. Here is my static method which I have in a static "Logic" class (more on that later):
1: public static IList<System.IO.FileInfo> GetFiles(string serverPath, string extention)
2: {
3: if (!serverPath.StartsWith("~/"))
4: {
5: if (serverPath.StartsWith("/"))
6: serverPath = "~" + serverPath;
7: else
8: serverPath = "~/" + serverPath;
9: }
10:
11: string path = HttpContext.Current.Server.MapPath(serverPath);
12:
13: if (!path.EndsWith("/"))
14: path = path + "/";
15:
16: if (!Directory.Exists(path))
17: throw new System.IO.DirectoryNotFoundException();
18:
19: IList<FileInfo> files = new List<FileInfo>();
20:
21: string[] fileNames = Directory.GetFiles(path, "*." + extention, System.IO.SearchOption.AllDirectories);
22: foreach (string name in fileNames)
23: files.Add(new FileInfo(name));
24:
25: return files;
26: }
Then what I do is get the CSS from the server and do the CSSmin:
1: public static string CombineCSS()
2: {
3: string allCSS = string.Empty;
4:
5: foreach (FileInfo fi in Logic.Files.GetFiles("~/Content/CSS/", "css"))
6: {
7: using (StreamReader sr = new StreamReader(fi.FullName))
8: allCSS += sr.ReadToEnd();
9: }
10:
11: allCSS = allCSS.Replace("~/", Global.BaseURL);
12:
13: allCSS = Compress(allCSS);
14:
15: return allCSS;
16: }
And finally I put it into an MVC controller. What you could do is put the following in a generic handler if you don't use the MVC bits:
1: [ControllerAction]
2: public void AllCSS(string id)
3: {
4: Response.ContentType = "text/css";
5:
6: Response.Write(Logic.CSS.CombineCSS());
7: }
Hope that helps!