How can I specify an explicit ScriptBundle include order?

jrummell picture jrummell · Aug 16, 2012 · Viewed 63.6k times · Source

I'm trying out the MVC4 System.Web.Optimization 1.0 ScriptBundle feature.

I have the following configuration:

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

and the following view:

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

where BundlePaths.CanvasScripts is "~/bundles/scripts/canvas". It renders this:

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

So far so good, except ~/Scripts/Shared/achievements.js is the first script in the bundled source. It depends on every script included before it in the ScriptBundle. How can I ensure that it honors the order in which I add include statements to the bundle?

Update

This was a relatively new ASP.NET MVC 4 application, but it was referencing the optimization framework pre release package. I removed it and added the RTM package from http://nuget.org/packages/Microsoft.AspNet.Web.Optimization. With the RTM version with debug=true in web.config, @Scripts.Render("~/bundles/scripts/canvas") renders the individual script tags in the correct order.

With debug=false in web.config, the combined script has the achievements.js script first, but since its a function definition (object constructor) that's called later, it runs without error. Perhaps the minifier is smart enough to figure out dependencies?

I also tried the IBundleOrderer implementation that Darin Dimitrov suggested with RTM with both debug options and it behaved the same.

So the minified version is not in the order I expect, but it works.

Answer

Darin Dimitrov picture Darin Dimitrov · Aug 16, 2012

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