zoukankan      html  css  js  c++  java
  • JS&CSS文件请求合并及压缩处理研究(五)

    接上篇。在我们最终调用 @Html.RenderResFile(ResourceType.Script) 或者 @Html.RenderResFile(ResourceType.StyleSheet) 将页面中添加的文件路径合并成类似以下格式后:

    <script type="text/JavaScript" src="Resource/script?href=[Scripts/common/jquery][Scripts/functionA/A1,A2][Scripts/functionB/B1,B2]&compress"></script>

    接下来需要做的就是在 Resource/script 中接收压缩后的路径href参数,按顺序读取服务器存取的资源文件,合并,压缩(如果有compress参数传入的话),输出至客户端。有了基本的流程和思路,代码实现其实没有什么难度。看代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using Mcmurphy.Common;
    
    namespace Mcmurphy.Web.Controllers
    {
        /// <summary>
        /// 资源文件请求处理
        /// </summary>
        public class ResourceController : Controller
        {
            /// <summary>
            /// 处理脚本文件
            /// </summary>
            /// <returns></returns>
            public void Script()
            {
                //原始Url链接
                var rawUri = new Uri(Request.Url.Scheme + "://" + Request.Url.Host + Request.RawUrl);
                //是否压缩参数
                var isCompressJs = rawUri.Query.IndexOf("&compress", StringComparison.OrdinalIgnoreCase) >= 0 
                    || rawUri.Query.IndexOf("?compress", StringComparison.OrdinalIgnoreCase) >= 0;
                var queryHref = Request["href"];
                //如果参数为空或者参数中包含有危险字符
                if (string.IsNullOrEmpty(queryHref) || !UrlFilter.FiltUrl(Server.UrlDecode(rawUri.Query)))
                {
                    Response.Write("请求参数错误!");
                }
                try
                {
                    //请求参数拆分成文件列表
                    var jsPaths = PathHelper.QueryToFileList(queryHref, ".js");
                    //转成成物理文件列表
                    jsPaths = PathHelper.GetPhysicPaths(jsPaths, Request);
                    //获取合并后的文件内容(如果标识isCompressJs == true,则需要压缩)
                    var finalJsContent = JsCombiner.CombineJs(jsPaths, Server.MapPath("~/"), isCompressJs);
                    //替换内容中模板(就简单的替换了一下时间)
                    finalJsContent = finalJsContent.Replace("{{now}}", DateTime.Now.ToString("yyyyMMddHHmmss"));
                    //输出字节流
                    Response.ContentType = "text/javascript";
                    Response.BinaryWrite(EncodingHelper.ConvertToUTF8BomEncodingStringBytes(finalJsContent));
                    Response.AddHeader("Vary", " Accept-Encoding");
                }
                catch (Exception ex)
                {
                    Response.Write(ex.StackTrace);
                }
            }
    
            /// <summary>
            /// 处理样式文件
            /// </summary>
            /// <returns></returns>
            public void Style()
            {
                var rawUri = new Uri(Request.Url.Scheme + "://" + Request.Url.Host + Request.RawUrl);
                //是否压缩参数
                var isCompressJs = rawUri.Query.IndexOf("&compress", StringComparison.OrdinalIgnoreCase) >= 0
                                 || rawUri.Query.IndexOf("?compress", StringComparison.OrdinalIgnoreCase) >= 0;
                var queryHref = Request["href"];
                if (string.IsNullOrEmpty(queryHref) || !UrlFilter.FiltUrl(Server.UrlDecode(rawUri.Query)))
                {
                    Response.Write("请求参数错误!");
                }
                try
                {
                    //请求参数拆分成文件列表
                    var cssPaths = PathHelper.QueryToFileList(queryHref, ".css");
                    //转成成物理文件列表
                    cssPaths = PathHelper.GetPhysicPaths(cssPaths,Request);
                    //获取合并后的文件内容
                    var finalCssContent = CssCombiner.CombineAndCompressCss(cssPaths, Server.MapPath("~/"), isCompressJs);
                    //输出字节流
                    Response.ContentType = "text/css";
                    Response.BinaryWrite(EncodingHelper.ConvertToUTF8BomEncodingStringBytes(finalCssContent));
                    Response.AddHeader("Vary", " Accept-Encoding");
                    
                }
                catch (Exception ex)
                {
                    Response.Write(ex.StackTrace);
                }
            }
        }
    }

    以处理脚本文件的Script Action为例:

    1,首先对接收的参数进行常规的空值及危险字符检查。

    检查是否存在危险字符的UrlFilter.FiltUrl方法代码为

    namespace Mcmurphy.Common
    {
        public class UrlFilter
        {
            /// <summary>
            /// 判断是否有特殊字符
            /// </summary>
            /// <param name="url"></param>
            /// <returns></returns>
            public static bool FiltUrl(string url)
            {
                if (url.Contains("&_="))
                {
                    return false;
                }
                if (url.Contains("(") || url.Contains(")"))
                {
                    return false;
                }
                if (url.Contains("{") || url.Contains("}"))
                {
                    return false;
                }
                if (url.Contains(";") || url.Contains(":"))
                {
                    return false;
                }
    
                return true;
            }
        }
    }

    2,将获取到的诸如“[Scripts/common/jquery][Scripts/functionA/A1,A2][Scripts/functionB/B1,B2]”类型的路径参数,拆分为单独的路径数组,并获取其真实的物理路径,以便读取内容。

    3,调用JsCombiner.CombineJs方法,传入物理路径集合,对脚本文件进行合并。如果标记压缩,则需要进行压缩处理。
    其中,JsCombiner.CombineJs 方法代码为:

    namespace Mcmurphy.Common
    {
        public class JsCombiner
        {
            /// <summary>
            /// 拼接js文件内容
            /// </summary>
            /// <param name="jsPaths"></param>
            /// <param name="serverPath"></param>
            /// <param name="isCompress"></param>
            /// <returns></returns>
            public static string CombineJs(string[] jsPaths, string serverPath, bool isCompress)
            {
                var jsBuilder = new StringBuilder();
                foreach (string path in jsPaths)
                {
                    if (File.Exists(path))
                    {
                        var sourceFileContent = AutoDetectEncodingFileReader.ReadFileContent(path, Encoding.Default);
                        if (string.IsNullOrEmpty(sourceFileContent) == false)
                        {
                            if (isCompress)
                            {
                                var compressor = new JavaScriptCompressor();
                                //ASCII, BigEndianUnicode, Unicode, UTF32, UTF7, UTF8, Default (default).
                                compressor.Encoding = Encoding.UTF8;
                                //True (default) | False.  True => Obfuscate function and variable names
                                compressor.ObfuscateJavascript = true;
                                //True | False (default).  True => compress any functions that contain 'eval'. Default is False, which means a function that contains
                                compressor.IgnoreEval = false;
                                //True | False (default).
                                compressor.DisableOptimizations = false;
                                //True | False (default).  True => preserve redundant semicolons (e.g. after a '}'
                                compressor.PreserveAllSemicolons = false;
                                //The position where a line feed is appened when the next semicolon is reached. 
                                compressor.LineBreakPosition = -1;
                                sourceFileContent = compressor.Compress(sourceFileContent);
                            }
                            jsBuilder.Append(sourceFileContent);
                        }
                    }
                    else
                    {
                        jsBuilder.Append(string.Format("/*未找到文件{0}*/", path.Replace(serverPath, "")));
                    }
                }
                return jsBuilder.ToString();
            }
        }
    }

    由上述代码可以看到,对脚本文件的压缩,我们调用了 YUI Compressor for .Net 这个第三方类库。

    YUI Compressor for .Net 为脚本的压缩提供了丰富的选项,比如是否进行代码混淆,是否自动在'}'后添加分号,是否进行代码优化,错误日志记录,文件编码等。上面的代码仅仅是最常规的应用。更多信息可以参考其开源地址:http://yuicompressor.codeplex.com/,在该页面的 documentation 标签下,可以查看到更多的使用说明。

    需要说明的是,在调用最新的YUI Compressor for .Net 对脚本文件进行压缩时,需要添加三个引用:

    EcmaScript.NET.dll
    Yahoo.Yui.Compressor.Build.MsBuild.dll
    Yahoo.Yui.Compressor.dll

    4,将合并[压缩]后的内容,以UTF-8 BOM的字符流形式输出至客户端。

    其中EncodingHelper.ConvertToUTF8BomEncodingStringBytes代码为:

    namespace Mcmurphy.Common
    {
        public static class EncodingHelper
        {
            /// <summary>
            /// 为字符串添加BOM头信息
            /// 让客户端正确识别UTF-8编码
            /// </summary>
            /// <param name="originalString"></param>
            /// <returns></returns>
            public static byte[] ConvertToUTF8BomEncodingStringBytes(string originalString)
            {
                var bom = Encoding.UTF8.GetPreamble();
                var content = Encoding.UTF8.GetBytes(originalString);
                var resultBytes = new byte[bom.Length + content.Length];
                bom.CopyTo(resultBytes, 0);
                content.CopyTo(resultBytes, bom.Length);
                return resultBytes;
            }
        }
    }

    接下来我们定位到Mcmurphy.Web项目,在Scripts文件夹下添加几个脚本文件,测试一下。

    Scripts/common/jquery.js   
    
    Scripts/functionA/A1.js :   
    $(function() {
        console.log("folder:functionA,name:A1.js");
    });
    
    Scripts/functionA/A2.js :
    $(function() {
        console.log("folder:functionA,name:A2.js");
    });
    
    Scripts/functionB/B1.js :
    $(function () {
        console.log("folder:functionB,name:B1.js");
    });
    
    Scripts/functionB/B2.js :   
    $(function () {
        console.log("folder:functionB,name:B2.js");
    });

    然后我们将 Views/Shared/_Layout.cshtml 布局文件修改为:

    <!DOCTYPE html>
    <html>
    <head>
        <title>@ViewBag.Title</title>
        @{
            Html.AppendResFile(ResourceType.StyleSheet, "[Content/common]");
            Html.AppendResFile(ResourceType.Script, "[Scripts/common/jquery]");
            
            @RenderSection("Head_Section", false)
        }
        @Html.RenderResFile(ResourceType.StyleSheet)
    </head>
    
    <body>
        @RenderBody()
        
        @RenderSection("Foot_Section", false)
    
        @Html.RenderResFile(ResourceType.Script)
    </body>
    </html>

    再定位到 Views/Home/Index.cshtml 文件,修改其内容为:

    @{
        ViewBag.Title = "Index";
    }
    @section Head_Section{
        @{
            //添加样式文件A
            Html.AppendResFile(ResourceType.StyleSheet, "[Content/Styles/styleA]"); 
            //添加样式文件B,但设置了高优先级
            Html.AppendResFile(ResourceType.StyleSheet, "[Content/Styles/styleB]","",PriorityType.High);
    
            //添加脚本文件functionA/A1,functionB/B1
            Html.AppendResFile(ResourceType.Script, "[Scripts/functionA/A1],[Scripts/functionB/B1]");
        }
    }
    @section Foot_Section{
        @{
            //添加脚本文件functionA/A2,functionB/B2
            Html.AppendResFile(ResourceType.Script, "[Scripts/functionA/A2],[Scripts/functionB/B2]");
        }
    }
    <h2>Index</h2>

    (关于ASP.NET MVC及Razor,在此不再赘述)

    Okay,Ctrl + F5 直接运行。查看浏览器网络请求及控制台

    可以看到已合并的样式及脚本文件网络请求,而控制台也得到了正确的输出。

    当然,也可以直接在浏览器地址栏输出Url,查看到合并[压缩]后的脚本或样式文件。

    http://localhost:17509/Resource/Script?href=[Scripts/common/jquery][Scripts/functionA/A1,A2][Scripts/functionB/B1,B2]&compress
    
    或者:
    
    http://localhost:17509/Resource/Style?href=[Content/Styles/styleB][Content/common][Content/Styles/styleA]&compress

    至此关于JS&CSS文件的请求合并及压缩处理的实现就告一段落。当然,在此优化的基础上其实还有很多其它方面的工作可做。比如可将Resource/[Script][Style]的处理,放到独立的资源服务器,可以得到额外的CDN的好处。另外,诸如脚本样式等文件通常都为静态资源,并不会经常变动,可以考虑在服务器做一些缓存及版本号方面的控制等。

    最后附上全部源码种子:

    MvcResourceHandle.rar

  • 相关阅读:
    6-stm32 滴答定时器(delay不带中断延时)
    5-stm32 滴答定时器(delay中断延时)
    4- stm32 gpio重映射
    3- stm32 gpio寄存器
    2-stm32 gpio位带
    Linux Command
    DIV+CSS规范命名
    JS事件报错之Cannot set property 'onclick' of null
    创建对象的三种方式
    密码的显示和隐藏
  • 原文地址:https://www.cnblogs.com/mcmurphy/p/3347316.html
Copyright © 2011-2022 走看看