Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Concatenate and minify JavaScript on the fly OR at build time - ASP.NET MVC

As an extension to this question here Linking JavaScript Libraries in User Controls I was after some examples of how people are concatenating and minifying JavaScript on the fly OR at build time. I would also like to see how it then works into your master pages.

I don't mind page specific files being minified and linked inidividually as they currently are (see below) but all the JavaScript files on the main master page (I have about 5 or 6) I would like concatenated and minified.

Bonus points for anyone who also incorporates CSS concatenation and minification! :-)

Current master page with the common JavaScript files that I would like concatenated and minified:

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %> <head runat="server">     ... BLAH ...     <asp:ContentPlaceHolder ID="AdditionalHead" runat="server" />     ... BLAH ...     <%= Html.CSSBlock("/styles/site.css") %>     <%= Html.CSSBlock("/styles/jquery-ui-1.7.1.css") %>     <%= Html.CSSBlock("/styles/jquery.lightbox-0.5.css") %>     <%= Html.CSSBlock("/styles/ie6.css", 6) %>     <%= Html.CSSBlock("/styles/ie7.css", 7) %>     <asp:ContentPlaceHolder ID="AdditionalCSS" runat="server" /> </head> <body>     ... BLAH ...     <%= Html.JSBlock("/scripts/jquery-1.3.2.js", "/scripts/jquery-1.3.2.min.js") %>     <%= Html.JSBlock("/scripts/jquery-ui-1.7.1.js", "/scripts/jquery-ui-1.7.1.min.js") %>     <%= Html.JSBlock("/scripts/jquery.validate.js", "/scripts/jquery.validate.min.js") %>     <%= Html.JSBlock("/scripts/jquery.lightbox-0.5.js", "/scripts/jquery.lightbox-0.5.min.js") %>     <%= Html.JSBlock("/scripts/global.js", "/scripts/global.min.js") %>     <asp:ContentPlaceHolder ID="AdditionalJS" runat="server" /> </body> 

Used in a page like this (which I'm happy with):

<asp:Content ID="signUpContent" ContentPlaceHolderID="AdditionalJS" runat="server">     <%= Html.JSBlock("/scripts/pages/account.signup.js", "/scripts/pages/account.signup.min.js") %> </asp:Content> 


UPDATE: Recommendations for now (late 2013):

I would look at Microsoft ASP.NET's built in Bundling and Minification.

like image 570
Charlino Avatar asked May 20 '09 22:05

Charlino


People also ask

Does Minifying JavaScript improve performance?

Minifying strips out all comments, superfluous white space and shortens variable names. It thus reduces download time for your JavaScript files as they are (usually) a lot smaller in filesize. So, yes it does improve performance.

What is the difference between minify and uglify?

Minification is just removing unnecesary whitespace and redundant / optional tokens like curlys and semicolons, and can be reversed by using a linter. Uglification is the act of transforming the code into an "unreadable" form, that is, renaming variables/functions to hide the original intent...

Does Uglify improve performance?

Uglification improves performance while reducing readability. Encryption: This is the process of translating data, called plain data, into encoded data. This encrypted, or encoded, data is known as ciphertext, and needs a secret key in order to decrypt it.

Does minification reduce parse time?

Minification of JavaScript files reduces payload and script parse time, resulting in faster page loads.


1 Answers

Try this:

I’ve recently completed a fair bit of research and consequent development at work that goes quite far to improve the performance of our web application’s front-end. I thought I’d share the basic solution here.

The first obvious thing to do is benchmark your site using Yahoo’s YSlow and Google’s PageSpeed. These will highlight the "low-hanging fruit" performance improvements to make. Unless you’ve already done so, the resulting suggestions will almost certainly include combining, minifying and gzipping your static content.

The steps we’re going to perform are:

Write a custom HTTPHandler to combine and minify CSS. Write a custom HTTPHandler to combine and minify JS. Include a mechanism to ensure that the above only do their magic when the application is not in debug mode. Write a custom server-side web control to easily maintain css/js file inclusion. Enable GZIP of certain content types on IIS 6. Right, let’s start with CSSHandler.asax that implements the .NET IHttpHandler interface:

using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Web;  namespace WebApplication1 {     public class CssHandler : IHttpHandler     {         public bool IsReusable { get { return true; } }          public void ProcessRequest(HttpContext context)         {             string[] cssFiles = context.Request.QueryString["cssfiles"].Split(',');              List<string> files = new List<string>();             StringBuilder response = new StringBuilder();             foreach (string cssFile in cssFiles)             {                 if (!cssFile.EndsWith(".css", StringComparison.OrdinalIgnoreCase))                 {                     //log custom exception                     context.Response.StatusCode = 403;                     return;                 }                  try                 {                     string filePath = context.Server.MapPath(cssFile);                     string css = File.ReadAllText(filePath);                     string compressedCss = Yahoo.Yui.Compressor.CssCompressor.Compress(css);                     response.Append(compressedCss);                 }                 catch (Exception ex)                 {                     //log exception                     context.Response.StatusCode = 500;                     return;                 }             }              context.Response.Write(response.ToString());              string version = "1.0"; //your dynamic version number               context.Response.ContentType = "text/css";             context.Response.AddFileDependencies(files.ToArray());             HttpCachePolicy cache = context.Response.Cache;             cache.SetCacheability(HttpCacheability.Public);             cache.VaryByParams["cssfiles"] = true;             cache.SetETag(version);             cache.SetLastModifiedFromFileDependencies();             cache.SetMaxAge(TimeSpan.FromDays(14));             cache.SetRevalidation(HttpCacheRevalidation.AllCaches);         }     } } 

Ok, now some explanation:

IsReUsable property:

We aren’t dealing with anything instance-specific, which means we can safely reuse the same instance of the handler to deal with multiple requests, because our ProcessRequest is threadsafe. More info.

ProcessRequest method:

Nothing too hectic going on here. We’re looping through the CSS files given to us (see the CSSControl below for how they’re coming in) and compressing each one, using a .NET port of Yahoo’s YUICompressor, before adding the contents to the outgoing response stream.

The remainder of the method deals with setting up some HTTP caching properties to further optimise the way the browser client downloads (or not, as the case may be) content.

We set Etags in code so that they may be the same across all machines in our server farm. We set Response and Cache dependencies on our actual files so, should they be replaced, cache will be invalidated. We set Cacheability such that proxies can cache. We VaryByParams using our cssfiles attribute, so that we can cache per CSS file group submitted through the handler. And here is the CSSControl, a custom server-side control inheriting the .NET LiteralControl.

Front:

<customcontrols:csscontrol id="cssControl" runat="server">   <CustomControls:Stylesheet File="main.css" />   <CustomControls:Stylesheet File="layout.css" />   <CustomControls:Stylesheet File="formatting.css" /> </customcontrols:csscontrol> 

Back:

using System; using System.Collections.Generic; using System.ComponentModel; using System.Web; using System.Web.UI; using System.Linq; using TTC.iTropics.Utilities;  namespace WebApplication1 {     [DefaultProperty("Stylesheets")]     [ParseChildren(true, "Stylesheets")]     public class CssControl : LiteralControl     {         [PersistenceMode(PersistenceMode.InnerDefaultProperty)]         public List<Stylesheet> Stylesheets { get; set; }          public CssControl()         {             Stylesheets = new List<Stylesheet>();         }          protected override void Render(HtmlTextWriter output)         {             if (HttpContext.Current.IsDebuggingEnabled)             {                 const string format = "<link rel=\"Stylesheet\" href=\"stylesheets/{0}\"></link>";                  foreach (Stylesheet sheet in Stylesheets)                     output.Write(format, sheet.File);             }             else             {                 const string format = "<link type=\"text/css\" rel=\"Stylesheet\" href=\"stylesheets/CssHandler.ashx?cssfiles={0}&version={1}\"/>";                 IEnumerable<string> stylesheetsArray = Stylesheets.Select(s => s.File);                 string stylesheets = String.Join(",", stylesheetsArray.ToArray());                 string version = "1.00" //your version number                  output.Write(format, stylesheets, version);             }          }     }      public class Stylesheet     {         public string File { get; set; }     } } 

HttpContext.Current.IsDebuggingEnabled is hooked up to the following setting in your web.config:

<system.web>   <compilation debug="false"> </system.web> 

So, basically, if your site is in debug mode you get HTML markup like this:

<link rel="Stylesheet" href="stylesheets/formatting.css"></link> <link rel="Stylesheet" href="stylesheets/layout.css"></link <link rel="Stylesheet" href="stylesheets/main.css"></link> 

But if you’re in production mode (debug=false), you’ll get markup like this:

<link type="text/css" rel="Stylesheet" href="CssHandler.ashx?cssfiles=main.css,layout.css,formatting.css&version=1.0"/> 

The latter will then obviously invoke the CSSHandler, which will take care of combining, minifying and cache-readying your static CSS content.

All of the above can then also be duplicated for your static JavaScript content:

`JSHandler.ashx:

using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Web;  namespace WebApplication1 {     public class JSHandler : IHttpHandler     {         public bool IsReusable { get { return true; } }          public void ProcessRequest(HttpContext context)         {             string[] jsFiles = context.Request.QueryString["jsfiles"].Split(',');              List<string> files = new List<string>();             StringBuilder response = new StringBuilder();              foreach (string jsFile in jsFiles)             {                 if (!jsFile.EndsWith(".js", StringComparison.OrdinalIgnoreCase))                 {                     //log custom exception                     context.Response.StatusCode = 403;                     return;                 }                  try                 {                     string filePath = context.Server.MapPath(jsFile);                     files.Add(filePath);                     string js = File.ReadAllText(filePath);                     string compressedJS = Yahoo.Yui.Compressor.JavaScriptCompressor.Compress(js);                     response.Append(compressedJS);                 }                 catch (Exception ex)                 {                     //log exception                     context.Response.StatusCode = 500;                     return;                 }             }              context.Response.Write(response.ToString());              string version = "1.0"; //your dynamic version number here              context.Response.ContentType = "application/javascript";             context.Response.AddFileDependencies(files.ToArray());             HttpCachePolicy cache = context.Response.Cache;             cache.SetCacheability(HttpCacheability.Public);             cache.VaryByParams["jsfiles"] = true;             cache.VaryByParams["version"] = true;             cache.SetETag(version);             cache.SetLastModifiedFromFileDependencies();             cache.SetMaxAge(TimeSpan.FromDays(14));             cache.SetRevalidation(HttpCacheRevalidation.AllCaches);         }     } } 

And its accompanying JSControl:

Front:

<customcontrols:JSControl ID="jsControl" runat="server">   <customcontrols:Script File="jquery/jquery-1.3.2.js" />   <customcontrols:Script File="main.js" />   <customcontrols:Script File="creditcardpayments.js" /> </customcontrols:JSControl> 

Back:

using System; using System.Collections.Generic; using System.ComponentModel; using System.Web; using System.Web.UI; using System.Linq;  namespace WebApplication1 {     [DefaultProperty("Scripts")]     [ParseChildren(true, "Scripts")]     public class JSControl : LiteralControl     {         [PersistenceMode(PersistenceMode.InnerDefaultProperty)]         public List<Script> Scripts { get; set; }          public JSControl()         {             Scripts = new List<Script>();         }          protected override void Render(HtmlTextWriter writer)         {             if (HttpContext.Current.IsDebuggingEnabled)             {                 const string format = "<script src=\"scripts\\{0}\"></script>";                  foreach (Script script in Scripts)                     writer.Write(format, script.File);             }             else             {                 IEnumerable<string> scriptsArray = Scripts.Select(s => s.File);                 string scripts = String.Join(",", scriptsArray.ToArray());                 string version = "1.0" //your dynamic version number                 const string format = "<script src=\"scripts/JsHandler.ashx?jsfiles={0}&version={1}\"></script>";                  writer.Write(format, scripts, version);             }         }     }      public class Script     {         public string File { get; set; }     } } 

Enabling GZIP:

As Jeff Atwood says, enabling Gzip on your web site server is a no-brainer. After some tracing, I decided to enable Gzip on the following file types:

.css .js .axd (Microsoft Javascript files) .aspx (Usual ASP.NET Web Forms content) .ashx (Our handlers) To enable HTTP Compression on your IIS 6.0 web server:

Open IIS, Right click Web Sites, Services tab, enable Compress Application Files and Compress Static Files Stop IIS Open up IIS Metabase in Notepad (C:\WINDOWS\system32\inetsrv\MetaBase.xml) – and make a back up if you’re nervous about these things Locate and overwrite the two IIsCompressionScheme and one IIsCompressionSchemes elements with the following:

And that’s it! This saved us heaps of bandwidth and resulted in a more responsive web application throughout.

Enjoy!

like image 171
Mark Gibaud Avatar answered Sep 24 '22 13:09

Mark Gibaud