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

91

我正在尝试使用MVC4 System.Web.Optimization 1.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语句到bundle的顺序?

更新

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

在web.config中debug=false的情况下,组合的脚本具有先出现achievements.js脚本,但由于它是稍后调用的函数定义(对象构造函数),因此它会顺利运行而没有错误。也许缩小器足够智能,可以解决依赖性问题?

我还尝试了Darin Dimitrov建议的使用IBundleOrderer实现的RTM和两个调试选项,它的行为相同。

因此,缩小版本的顺序不是我期望的顺序,但它可以正常工作。

7个回答

116

您可以编写一个自定义的 bundle 排序器 (IBundleOrderer),确保包含在注册它们时的顺序中:

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

然后:

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

你的看法是:

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

11
这应该是可行的,但也应该是不必要的,因为默认的排序器通常会尊重包含文件的顺序(它只会将一些特殊的文件提升到顶部,例如jquery、reset.css/normalize.css等)。 它不应该将achievements.js提升到顶部。 - Hao Kung
1
@flipdoubt 在这里提交一个问题,附上可重现的代码:http://aspnetoptimization.codeplex.com/workitem/list/advanced - Hao Kung
1
这个很完美,谢谢!但是我同意,这不应该是必需的。 - FiniteLooper
2
这对我来说有效。根据https://dev59.com/YGQm5IYBdhLWcg3wwxLF,它在推广jqueryui而不是bootstrap时,我需要翻转它。 - Sethcran
1
@Hao Kung,我们可以按照include目录中的文件顺序排序吗? - Shaiju T
显示剩余6条评论

52

谢谢Darin。我添加了一个扩展方法。

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

使用方法

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

9
很不错! :D 现在我必须在接口方法签名中将“FileInfo”更改为“BundleFile”。他们进行了更改。 - Leniel Maccaferri
是的,他们在预发布版本中进行了更改,该版本使用了Owin框架。 - Softlion
有点离题:有人对这个BundleFile类有更多的信息吗?为了捆绑嵌入式资源,我正在尝试实例化它,并且它在构造函数中需要一个(具体子类的)VirtualFile参数。 - superjos
我有同样的问题,我需要实例化BundleFile,但不知道如何操作。 - Rui
2
@superjos 我知道这已经有点老了,但我会回答以防其他人也遇到同样的问题。这是System.Web.Optimization的一部分。 - Talon
这绝对是我正在寻找的答案。 - Ian

47

更新了SoftLion提供的答案,以处理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;
    }
}

使用方法:

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

我喜欢使用流畅的语法,但也可以用单个方法调用和将所有脚本作为参数传递来实现。


2
适用于MVC 5。谢谢。 - Pascal Carmoni
4
很好。我认为这应该被包含在微软中。 - Angelo
在 Web Forms 上它也能够完美运行,基本上它应该能够适用于所有最新的版本! - Sunny Sharma
非常干净简洁的解决方案。非常感谢。 - bunjeeb

18
我在RTM版本中没有看到这种行为,您是否使用了Microsoft ASP.NET Web Optimization Framework 1.0.0版本:http://nuget.org/packages/Microsoft.AspNet.Web.Optimization
我使用了与您的示例类似的重现方式,基于一个新的MVC4互联网应用程序网站。
我在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); 

在默认的首页中,我添加了以下内容:
<script src="@Scripts.Url("~/bundles/scripts/canvas")"></script>

我验证了捆绑包的压缩JavaScript文件中,achievements.js的内容在modernizr之后。


我升级到了1.0版本。请查看我的更新问题。achievements.js的内容首先包含在压缩版本中,但它能够工作,因为它是稍后调用的函数。 - jrummell

5

您可以通过使用BundleCollection.FileSetOrderList来设置顺序。请参考这篇博客文章:http://weblogs.asp.net/imranbaloch/archive/2012/09/30/hidden-options-of-asp-net-bundling-and-minification.aspx。在您的实例中,代码应该是这样的:

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

1

1

@Darin Dimitrov的回答对我来说完美无缺,但我的项目是用VB编写的,所以这里是他的答案转换为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

使用它的方法是:

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

我一直收到这个错误:类'AsIsBundleOrderer'必须实现接口'System.Web.Optimization.IBundleOrderer'的'Function OrderFiles(context as ....)'。有任何想法吗? - Mark Pieszak - Trilon.io

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接