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