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);
}
}
}