How can I specify an explicit ScriptBundle include order?

64,881

Solution 1

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...

Solution 2

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

Solution 3

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

Solution 4

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.

Solution 5

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);
Share:
64,881
jrummell
Author by

jrummell

Microsoft web development; it's what I do. http://www.controlaltdevelopment.com/

Updated on August 24, 2020

Comments

  • jrummell
    jrummell over 3 years

    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.

  • jrummell
    jrummell over 11 years
    Thanks, I'll give that a try.
  • Hao Kung
    Hao Kung over 11 years
    This should work, but it also should be unnecessary, as the default orderer generally respects the order of the includes (it only promotes a few special files to the top, i.e. jquery, reset.css/normalize.css, etc.). It should not be promoting achievements.js to the top.
  • jrummell
    jrummell over 11 years
    I upgraded to the 1.0 version. See my updated question. The contents of achievements.js are included first in the minified version, but it works since its a function called later.
  • flipdoubt
    flipdoubt over 11 years
    @HaoKung I find the default orderer and even this AsIsBundleOrderer consistently promotes my custom Site.js before jquery-x.js. Where can I submit my repro?
  • Hao Kung
    Hao Kung over 11 years
    @flipdoubt file an issue here with the repro: aspnetoptimization.codeplex.com/workitem/list/advanced
  • Brian Mains
    Brian Mains about 11 years
    +1 I'm using RTM and just ran into the problem when adding all of my scripts to a single bundle. And to add to this, this interface is in System.Web.Optimization as described here: msdn.microsoft.com/en-us/library/…
  • FiniteLooper
    FiniteLooper about 11 years
    This works perfect, thanks! I agree though, this should not be necessary.
  • Leniel Maccaferri
    Leniel Maccaferri almost 11 years
    Pretty slick! :D Right now I had to change FileInfo for BundleFile in the Interface method signature. They changed it.
  • Softlion
    Softlion over 10 years
    yes they changed it in the prerelease version which uses the Owin framework
  • superjos
    superjos over 10 years
    little Off-Topic: does anybody have more info on this BundleFile class? In order to bundle embedded resources, I'm trying to instantiate that and it requires a (concrete child of) VirtualFile parameter in its constructor.
  • Rui
    Rui over 10 years
    I have the same question, I need instantiate BundleFile, but don't know how
  • Mark Pieszak - Trilon.io
    Mark Pieszak - Trilon.io about 10 years
    I keep getting this error: Class 'AsIsBundleOrderer' must implement 'Function OrderFiles(context as ....)' for interface 'System.Web.Optimization.IBundleOrderer' Any ideas?
  • Sethcran
    Sethcran about 10 years
    This works for me. It was promoting jqueryui over bootstrap when according to stackoverflow.com/questions/17367736/… I need it flipped.
  • Pascal Carmoni
    Pascal Carmoni almost 10 years
    Works in MVC 5. Thanks
  • Angelo
    Angelo over 9 years
    Excellent. This should be included by MS imho
  • Michael Draper
    Michael Draper over 9 years
    If the file set contains files in subdirectories, the files in the subdirectories get chunked first, instead of the order that they were processed in. Example: <code> "~/Scripts/angular.js", "~/Scripts/angular-route.min.js", "~/Scripts/angular-touch.js", "~/Scripts/angular-bootstrap-switch.min.js", "~/Scripts/ui-bootstrap-tpls-0.10.0.min.js", "~/Scripts/acs/controllers.js", "~/Scripts/acs/app.js", "~/Scripts/acs/slider.js"</code> controllers.js appears before app.js
  • Jessy
    Jessy over 9 years
    @HaoKung You're right, however if you do not use the exact naming scheme they have, you'll end up having issues (example: using . instead of - in file name for the min. jquery-min.js vs jquery.min.js) could cause you some trouble trying to get the order you're looking for. The above solution gives back full control of the ordering.
  • Talon
    Talon almost 9 years
    @superjos I know this is rather old but I'll answer in case anyone else has the problem. It's part of System.Web.Optimization.
  • Liron Harel
    Liron Harel almost 9 years
    I am getting this error: Error 64 'AsIsBundleOrderer' does not implement interface member 'System.Web.Optimization.IBundleOrderer.OrderFiles(System.We‌​b.Optimization.Bundl‌​eContext, System.Collections.Generic.IEnumerable<System.Web.Optimizati‌​on.BundleFile>)'
  • Sunny Sharma
    Sunny Sharma over 8 years
    works like a charm in Web Forms too... basically it should work fine for all the latest versions!
  • Shaiju T
    Shaiju T over 8 years
    @Hao Kung can we order the files in the include directory ?
  • Rajshekar Reddy
    Rajshekar Reddy about 8 years
    Signature has changed to public IEnumerable<BundleFile> OrderFiles(BundleContext context, IEnumerable<BundleFile> files)
  • bunjeeb
    bunjeeb about 8 years
    Very clean and simple solution. Thanks a lot.
  • Ian
    Ian about 6 years
    this was absolutely the answer I was looking for.