Microsoft.AspNet.Web.Optimization.Bundle的完美替换方案

Web应用程序中包含大量的样式(css)和脚本(js)文件,这些文件的引用、管理和发布有很多解决方案。在Asp.Net MVC应用程序中,大家最熟悉的解决方案应属Microsoft.AspNet.Web.Optimization这个package。这个package的使用也挺方便,对我来说,它依赖太多package,这点不合我胃口,我是比较崇尚精简的那种。接下来介绍这个package的使用及如何将它完美的替换。

1. Microsoft.AspNet.Web.Optimization的Bundle使用

将要合并的文件添加到BundleTable.Bundles集合中即可,样式文件使用StyleBundle类,脚本文件使用ScriptBundle类。示例如下:

public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {
        var style = new StyleBundle("~/Content/login")
            .Include("~/Content/common.css", "~/Content/login.css");
        bundles.Add(style);

        var script = new ScriptBundle("~/Scripts/login")
            .Include("~/Scripts/common.js", "~/Scripts/login.js");
        bundles.Add(script);
    }
}

View页面使用Styles和Scripts两个类来呈现。示例如下:

@Styles.Render("~/Content/login")
@Scripts.Render("~/Scripts/login")

这里只简单介绍一下Bundle的使用。个人觉得主要有如下问题:

  • 依赖过多的package,有WebGrease、Antlr、Newtonsoft.Json;
  • 不同文件夹的样式文件不能同时输出一个min文件,若包在一起时,有些样式文件引用的图片无法显示,这个问题我没想去解决,有了上面那一条,也不想去解决它。

2. 完美的替换方案

为了完美替换Microsoft.AspNet.Web.Optimization的Bundle,我采用了Bundler & Minifier这个VS的扩展,它可以方便的配置和生成样式及脚本的min文件。这个扩展只能生成min文件,而没有Bundle那样可以根据开发环境和生产环境来输出对应的源文件和min文件,不过这个问题很好解决,下面来介绍如何实现。

  • 安装Bundler & Minifier扩展及配置
    在VS中点击“工具-扩展和更新-联机”,再输入Bundler搜索,下载,重启VS完成安装。
  • Bundle的配置
    它的配置很简单,配一个outputFileName和inputFiles集合即可,inputFiles可以是文件,也可以是文件夹。打开bundleconfig.json文件,配置示例如下:
[
  {
    "outputFileName": "static/modules/login/index.min.css",
    "inputFiles": [
      "static/modules/login/index.css"
    ]
  },
  {
    "outputFileName": "static/modules/login/index.min.js",
    "inputFiles": [
      "static/libs/jquery.min.js",
      "static/libs/jquery.md5.js",
      "static/modules/core/js",
      "static/modules/login/index.js"
    ]
  }
]
  • 解决开发环境和生产环境输出特性
    我们知道Web.config文件有如下节点,可以设置当前程序的环境,可以通过HttpContextBase类的IsDebuggingEnabled属性来获取。
<configuration>
  <system.web>
    <compilation debug="true" />
  </system.web>
</configuration>

根据这个节点,我们来实现不同环境下样式和脚本文件的输出,即开发时输出源文件,生产环境下输出min文件。我们添加HtmlHelper类的扩展方法,一个是MinStyle输出样式,一个是MinScript输出脚本。View页面使用如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    @Html.MinStyle("static/modules/login/index.min.css")
</head>
<body>
    <div class="login">
        ...
    </div>
    @Html.MinScript("static/modules/login/index.min.js")
</body>
</html>

下面是这两个扩展方法的具体实现:

public static class HtmlExtension
{
    public static IHtmlString MinStyle(this HtmlHelper helper, string path)
    {
        var format = "<link rel=\"stylesheet\" href=\"/{0}\">";
        var html = GetHtmlString(helper, format, path);
        return new HtmlString(html);
    }

    public static IHtmlString MinScript(this HtmlHelper helper, string path)
    {
        var format = "<script src=\"/{0}\"></script>";
        var html = GetHtmlString(helper, format, path);
        return new HtmlString(html);
    }

    private static string GetHtmlString(HtmlHelper helper, string format, string path)
    {
        var random = DateTime.Now.ToString("yyMMddss");
        var html = string.Format(format, $"{path}?r={random}");
        var httpContext = helper.ViewContext.RequestContext.HttpContext;
        if (httpContext.IsDebuggingEnabled)
        {
            var bundle = BundleInfo.GetBundle(httpContext, path);
            if (bundle != null && bundle.HasInputFiles)
            {
                var rootPath = httpContext.Server.MapPath("~/");
                var paths = new List<string>();
                foreach (var inputFile in bundle.inputFiles)
                {
                    var inputPath = rootPath + inputFile;
                    if (File.Exists(inputPath))
                    {
                        paths.Add(string.Format(format, $"{inputFile}?r={random}"));
                    }
                    else if (Directory.Exists(inputPath))
                    {
                        var files = Directory.GetFiles(inputPath);
                        foreach (var file in files)
                        {
                            var filePath = file.Replace(rootPath, "").Replace("\\", "/");
                            paths.Add(string.Format(format, $"{filePath}?r={random}"));
                        }
                    }
                }
                html = string.Join(Environment.NewLine, paths);
            }
        }

        return html;
    }

    class BundleInfo
    {
        public string outputFileName { get; set; }
        public List<string> inputFiles { get; set; }

        public bool HasInputFiles
        {
            get { return inputFiles != null && inputFiles.Count > 0; }
        }

        public static BundleInfo GetBundle(HttpContextBase httpContext, string outputFile)
        {
            var jsonFile = httpContext.Server.MapPath("~/bundleconfig.json");
            if (!File.Exists(jsonFile))
                return null;

            var json = File.ReadAllText(jsonFile);
            if (string.IsNullOrWhiteSpace(json))
                return null;

            var bundles = json.FromJson<List<BundleInfo>>();
            if (bundles == null || bundles.Count == 0)
                return null;

            return bundles.FirstOrDefault(b => b.outputFileName == outputFile);
        }
    }
}
posted @ 2018-07-14 20:02  known  阅读(1596)  评论(5编辑  收藏  举报