如何指定显式的 ScriptBundle 包含顺序?

我在试 MVC4 System.Web.Optimation1.0 ScriptBundle 特性

我有以下配置:

public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
// shared scripts
Bundle canvasScripts =
new ScriptBundle(BundlePaths.CanvasScripts)
.Include("~/Scripts/modernizr-*")
.Include("~/Scripts/json2.js")
.Include("~/Scripts/columnizer.js")
.Include("~/Scripts/jquery.ui.message.min.js")
.Include("~/Scripts/Shared/achievements.js")
.Include("~/Scripts/Shared/canvas.js");
bundles.Add(canvasScripts);
}
}

以及以下观点:

<script type="text/javascript" src="@Scripts.Url(BundlePaths.CanvasScripts)"></script>

其中 BundlePaths.CanvasScripts"~/bundles/scripts/canvas"。它呈现如下:

<script type="text/javascript" src="/bundles/scripts/canvas?v=UTH3XqH0UXWjJzi-gtX03eU183BJNpFNg8anioG14_41"></script>

到目前为止还不错,除了 ~/Scripts/Shared/achievements.js是绑定源代码中的第一个脚本。它取决于包含在 ScriptBundle之前的每个脚本。如何确保它遵循向包中添加 include 语句的顺序?

更新

这是一个相对较新的 ASP.NET MVC 4应用程序,但它引用了优化框架的预发布包。我删除了它,并添加了来自 http://nuget.org/packages/Microsoft.AspNet.Web.Optimization的 RTM 包。在 web.config 中使用 debug = true 的 RTM 版本,@Scripts.Render("~/bundles/scripts/canvas")以正确的顺序呈现各个脚本标记。

在 web.config 中使用 debug = false 时,组合脚本首先具有 achiements.js 脚本,但是由于它是后面调用的函数定义(对象构造函数) ,所以运行时没有错误。也许缩小器足够聪明,能够找出依赖关系?

我还尝试了 Darin Dimitrov 在 RTM 中提出的带有两个调试选项的 IBundleOrderer实现,结果表现相同。

所以这个缩小版并不是我所期望的顺序,但是它确实有效。

65565 次浏览

You could write a custom bundle orderer (IBundleOrderer) that will ensure bundles are included in the order you register them:

public class AsIsBundleOrderer : IBundleOrderer
{
public virtual IEnumerable<FileInfo> OrderFiles(BundleContext context, IEnumerable<FileInfo> files)
{
return files;
}
}

and then:

public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
var bundle = new Bundle("~/bundles/scripts/canvas");
bundle.Orderer = new AsIsBundleOrderer();
bundle
.Include("~/Scripts/modernizr-*")
.Include("~/Scripts/json2.js")
.Include("~/Scripts/columnizer.js")
.Include("~/Scripts/jquery.ui.message.min.js")
.Include("~/Scripts/Shared/achievements.js")
.Include("~/Scripts/Shared/canvas.js");
bundles.Add(bundle);
}
}

and in your view:

@Scripts.Render("~/bundles/scripts/canvas")

I'm not seeing this behavior on the RTM bits, are you using the Microsoft ASP.NET Web Optimization Framework 1.0.0 bits: http://nuget.org/packages/Microsoft.AspNet.Web.Optimization ?

I used a similar repro to your sample, based off of a new MVC4 Internet application website.

I added to BundleConfig.RegisterBundles:

        Bundle canvasScripts =
new ScriptBundle("~/bundles/scripts/canvas")
.Include("~/Scripts/modernizr-*")
.Include("~/Scripts/Shared/achievements.js")
.Include("~/Scripts/Shared/canvas.js");
bundles.Add(canvasScripts);

And then in the default index page, I added:

<script src="@Scripts.Url("~/bundles/scripts/canvas")"></script>

And I verified that in the minified javascript for the bundle, the contents of achievements.js was after modernizr...

Thank you Darin. I've added an extension method.

internal class AsIsBundleOrderer : IBundleOrderer
{
public virtual IEnumerable<FileInfo> OrderFiles(BundleContext context, IEnumerable<FileInfo> files)
{
return files;
}
}


internal static class BundleExtensions
{
public static Bundle ForceOrdered(this Bundle sb)
{
sb.Orderer = new AsIsBundleOrderer();
return sb;
}
}

Usage

bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js",
"~/Scripts/jquery-migrate-{version}.js",


"~/Scripts/jquery.validate.js",
"~/Scripts/jquery.validate.messages_fr.js",
"~/Scripts/moon.jquery.validation-{version}.js",


"~/Scripts/jquery-ui-{version}.js"
).ForceOrdered());

You should be able to set the order with help of the BundleCollection.FileSetOrderList. Have a look at this blog post: http://weblogs.asp.net/imranbaloch/archive/2012/09/30/hidden-options-of-asp-net-bundling-and-minification.aspx . The code in your instance would be something like:

BundleFileSetOrdering bundleFileSetOrdering = new BundleFileSetOrdering("js");
bundleFileSetOrdering.Files.Add("~/Scripts/modernizr-*");
bundleFileSetOrdering.Files.Add("~/Scripts/json2.js");
bundleFileSetOrdering.Files.Add("~/Scripts/columnizer.js");
bundleFileSetOrdering.Files.Add("~/Scripts/jquery.ui.message.min.js");
bundleFileSetOrdering.Files.Add("~/Scripts/Shared/achievements.js");
bundleFileSetOrdering.Files.Add("~/Scripts/Shared/canvas.js");
bundles.FileSetOrderList.Add(bundleFileSetOrdering);

@Darin Dimitrov's answer works perfectly for me, but my project is written in VB, so here's his answer converted to VB

Public Class AsIsBundleOrderer
Implements IBundleOrderer


Public Function IBundleOrderer_OrderFiles(ByVal context As BundleContext, ByVal files As IEnumerable(Of FileInfo)) As IEnumerable(Of FileInfo) Implements IBundleOrderer.OrderFiles
Return files
End Function
End Class

To Use it:

Dim scriptBundleMain = New ScriptBundle("~/Scripts/myBundle")
scriptBundleMain.Orderer = New AsIsBundleOrderer()
scriptBundleMain.Include(
"~/Scripts/file1.js",
"~/Scripts/file2.js"
)
bundles.Add(scriptBundleMain)

You should consider using Cassette http://getcassette.net/ It supports auto detection of script dependencies based on script references found inside each file.

If you prefer sticking to MS Web Optimization solution but like the idea of arranging scripts based on script references you should read my blog post at http://blogs.microsoft.co.il/oric/2013/12/27/building-single-page-application-bundle-orderer/

Updated the answer provided by SoftLion to handle changes in MVC 5 (BundleFile vs FileInfo).

internal class AsIsBundleOrderer : IBundleOrderer
{
public virtual IEnumerable<BundleFile> OrderFiles(BundleContext context, IEnumerable<BundleFile> files)
{
return files;
}
}


internal static class BundleExtensions
{
public static Bundle ForceOrdered(this Bundle sb)
{
sb.Orderer = new AsIsBundleOrderer();
return sb;
}
}

Usage:

    bundles.Add(new ScriptBundle("~/content/js/site")
.Include("~/content/scripts/jquery-{version}.js")
.Include("~/content/scripts/bootstrap-{version}.js")
.Include("~/content/scripts/jquery.validate-{version}")
.ForceOrdered());

I like using fluent syntax but it also works with a single method call and all the scripts passed as parameters.