、および理想的には MVC ビューでファイルを宣言し.js
、それらを使用するパーシャルを使用して、それらを自動的に実行時にバンドルし、本番環境で縮小できるようにするストックまたはプラグ可能な方法 (NuGet パッケージなど) はありますか? (別名「オートバンディング」).css
.less
組み込みの MVC 4 バンドルを試しました。バンドルが、ページの作成者がバンドルを見つけると予想する場所 ( BundleConfig.cs
. これは、C# 以外のチーム メンバーには実行できません。
私が探しているものの例として、 SquishItを使用して自分で組み立てたものを次に示します。
ExtendedViewPage.cs
/// <summary>
/// Caches a bundle of .js and/or .css specific to this ViewPage, at a path similar to:
/// shared_signinpartial_F3BD3CCE1DFCEA70F5524C57164EB48E.js
/// </summary>
public abstract class ExtendedViewPage<TModel> : WebViewPage<TModel> {
// This is where I keep my assets, and since I don't actually store any in my root,
// I emit all my bundles here. I also use the the web deployment engine,
// and remove extra files on publish, so I never personally have to clean them up,
// and I also don't have to hand-identify generated bundles from original code.
// However, to keep from needing to give the app write permissions
// on a static content folder, or collocate bundles with original assets,
// or conform to a specific asset path, this should surely be configurable
private const string ASSET_PATH = "~/assets/";
/// <summary>
/// Emits here the bundled resources declared with "AddResources" on all child controls
/// </summary>
public MvcHtmlString ResourceLinks {
get {
return MvcHtmlString.Create(
string.Join("", CssResourceLinks) + string.Join("", JsResourceLinks));
}
}
// This allows all resources to be specified in a single command,
// which permits .css and .js resources to be declared in an
// interwoven manner, in any order the site author prefers
// For me, this makes it clearer, to group my related .css and .js links,
// and to place my often control-specific CSS near last in the list
/// <summary>
/// Queues compressible resources to be emitted with the ResourceLinks directive
/// </summary>
/// <param name="resourceFiles">Project paths to JavaScript and/or CSS files</param>
public void AddResources(params string[] resourceFiles) {
var css = FilterFileExtension(resourceFiles, ".css");
AddCssResources(css);
var js = FilterFileExtension(resourceFiles, ".js");
AddJsResources(js);
}
/// <summary>
/// Bundles JavaScript files to be emitted with the ResourceLinks directive
/// </summary>
/// <param name="resourceFiles">Zero or more project paths to JavaScript files</param>
public void AddJsResources(params string[] resourceFiles) {
if (resourceFiles.Any()) {
JavaScriptBundle jsBundle = Bundle.JavaScript();
foreach (string jsFile in resourceFiles) {
jsBundle.Add(jsFile);
}
// Pages render from the inside-out, which is required for us to expose
// our resources declared in children to the parent where they are emitted
// however, it also means our resources naturally collect here in an order
// that is probably not what the site author intends.
// We reverse the order with insert
JsResourceLinks.Insert(0, jsBundle.MvcRender(ASSET_PATH + ViewIdentifier + "_#.js"));
}
}
/// <summary>
/// Bundles CSS files to be emitted with the ResourceLinks directive
/// </summary>
/// <param name="resourceFiles">Zero or more project paths to CSS files</param>
public void AddCssResources(params string[] resourceFiles) {
// Create a separate reference for each CSS path, since CSS files typically include path-relative images.
foreach (
var cssFolder in resourceFiles.
GroupBy(r => r.Substring(0, r.LastIndexOf('/')).ToLowerInvariant()).
// Note the CssResourceLinks.Insert command below reverses not only desirably
// the order of view emission, but also undesirably reverses the order of resources within this one view.
// for this page we'll 'pre-reverse' them. There's probably a clearer way to address this.
Reverse()) {
CSSBundle cssBundle = Bundle.Css();
foreach (string cssFile in cssFolder) {
cssBundle.Add(cssFile);
}
// See JsResourceLinks.Insert comment above
CssResourceLinks.Insert(0, cssBundle.MvcRender(cssFolder.Key + "/" + ViewIdentifier + "_#.css"));
}
}
#region private implementation
private string _viewIdentifier = null;
// ViewIdentifier returns a site-unique name for the current control, such as "shared_signinpartial"
// Some security wonks may take issue with exposing folder structure here
// It may be appropriate to obfuscate it with a checksum
private string ViewIdentifier {
get {
if (_viewIdentifier == null) {
_viewIdentifier =
// VirtualPath uniquely identifies the currently rendering View or Partial,
// such as "~/Views/Shared/SignInPartial.cshtml"
Path.GetFileNameWithoutExtension(VirtualPath).
// This "Substring" truncates the ~/Views/ or ~/Areas/ in my build, in others
// but it is probably inappropriate to make this assumption.
// It is certainly possible to have views in the root.
// Substring(8).
// It's assumed all of these bundles will be output to a single folder,
// to keep filesystem write-access minimal, so we flatten them here.
Replace("/", "_").
// The following assumes a typical MS filesystem, preserve-but-ignore case.
// The .NET string recommendations suggest instead using ToUpperInvariant
// for such an operation, but this was just a personal preference.
// My IIS rules typically drop the case on all content served.
// It may be altogether inappropriate to alter,
// although appending the MD5 hash ensure it does no harm on other platforms,
// while still collapsing the cases where multiply-cased aliases are used
ToLowerInvariant();
}
return _viewIdentifier;
}
}
private List<MvcHtmlString> CssResourceLinks {
get { return getContextHtmlStringList("SquishItCssResourceLinks"); }
}
private List<MvcHtmlString> JsResourceLinks {
get { return getContextHtmlStringList("SquishItJsResourceLinks"); }
}
// Note that at the resource render, if no bundles of a specific type (.css or .js)
// have been provided, this performs the unnecessary operation of instanciating a new List<MvcHtmlString>
// and adding it to the HttpContext.Items. This get/set could benefit from some clarification.
private List<MvcHtmlString> getContextHtmlStringList(string itemName) {
IDictionary contextItems = Context.ApplicationInstance.Context.Items;
List<MvcHtmlString> resourceLinks;
if (contextItems.Contains(itemName)) {
resourceLinks = contextItems[itemName] as List<MvcHtmlString>;
}
else {
resourceLinks = new List<MvcHtmlString>();
contextItems.Add(itemName, resourceLinks);
}
return resourceLinks;
}
private string[] FilterFileExtension(string[] filenames, string mustEndWith) {
IEnumerable<string> filtered =
filenames.Where(r => r.EndsWith(mustEndWith, StringComparison.OrdinalIgnoreCase));
return filtered.ToArray();
}
#endregion private implementation
}
PageWithHeaderLayout.cshtml (使用例)
@{
AddResources(
Links.Assets.Common.Script.GoogleAnalytics_js,
Links.Assets.Common.Style.ProprietaryTheme.jquery_ui_1_8_23_custom_css,
Links.Assets.Common.Style.SiteStandards_css,
Links.Assets.Common.CdnMirror.jquery._1_7_2.jquery_js,
Links.Assets.Common.CdnMirror.jQuery_Validate._2_0_0pre.jquery_validate_120826_js,
Links.Assets.Common.CdnMirror.jqueryui._1_8_23.jquery_ui_min_js,
Links.Assets.Common.JqueryPlugins.templates.jquery_tmpl_min_js,
Links.Assets.Common.JqueryPlugins.jquery_ajaxmanager_js,
Links.Assets.Common.JqueryPlugins.hashchange.jquery_ba_hashchange_min_js
);
}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>@ViewBag.Title</title>
<meta name="description" content="@ViewBag.Description" />
<meta name="keywords" content="@ViewBag.Keywords" />
<link rel="shortcut icon" href="@Url.Content("~/favicon.ico")" type="image/x-icon" />
<!-- all bundles from all page components are emitted here -->
@ResourceLinks
</head>
<body>
@Html.Partial(MVC.Common.Views.ContextNavigationTree)
<div id="pageContent">
@RenderBody()
</div>
</body>
</html>
残念ながら私が書いたので、多くの制限があります。.less
スクリプトは重複を排除しません。バンドルの記述には単純なアプローチが必要です。最近、サポートを許可するために醜いハックを追加しました。
これを行うための既存のソリューションはありますか?