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

后端 未结 8 2367
日久生厌
日久生厌 2020-11-29 15:38

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

8条回答
  •  时光取名叫无心
    2020-11-29 16:00

    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 files = new List();
                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:

    
      
      
      
    
    

    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 Stylesheets { get; set; }
    
            public CssControl()
            {
                Stylesheets = new List();
            }
    
            protected override void Render(HtmlTextWriter output)
            {
                if (HttpContext.Current.IsDebuggingEnabled)
                {
                    const string format = "";
    
                    foreach (Stylesheet sheet in Stylesheets)
                        output.Write(format, sheet.File);
                }
                else
                {
                    const string format = "";
                    IEnumerable 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:

    
      
    
    

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

    
    
    

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

    
    

    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 files = new List();
                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:

    
      
      
      
    
    

    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";
    
                    foreach (Script script in Scripts)
                        writer.Write(format, script.File);
                }
                else
                {
                    IEnumerable scriptsArray = Scripts.Select(s => s.File);
                    string scripts = String.Join(",", scriptsArray.ToArray());
                    string version = "1.0" //your dynamic version number
                    const string format = "";
    
                    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!

提交回复
热议问题