Jak mogę określić jawną kolejność dołączania ScriptBundle?

91

Wypróbowuję funkcję MVC4 System.Web.Optimization 1.0 ScriptBundle .

Mam następującą konfigurację:

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

i następujący widok:

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

gdzie BundlePaths.CanvasScriptsjest "~/bundles/scripts/canvas". Przedstawia to:

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

Jak na razie dobrze, z wyjątkiem ~/Scripts/Shared/achievements.jspierwszego skryptu w dołączonym źródle. Zależy to od każdego skryptu zawartego przed nim w ScriptBundle. Jak mogę się upewnić, że przestrzega kolejności, w której dodam instrukcje include do pakietu?

Aktualizacja

Była to stosunkowo nowa aplikacja ASP.NET MVC 4, ale odwoływała się do pakietu wstępnego wersji platformy optymalizacji. Usunąłem go i dodałem pakiet RTM z http://nuget.org/packages/Microsoft.AspNet.Web.Optimization . W wersji RTM z debug = true w pliku web.config,@Scripts.Render("~/bundles/scripts/canvas") renderuje poszczególne tagi skryptu we właściwej kolejności.

Z debug = false w web.config, połączony skrypt ma najpierw skrypt osiągnięć.js, ale ponieważ jest to definicja funkcji (konstruktor obiektu), która jest wywoływana później, działa bez błędów. Być może minifier jest wystarczająco inteligentny, aby znaleźć zależności?

Próbowałem też IBundleOrderer implementację, którą Darin Dimitrov zasugerował w RTM z obiema opcjami debugowania i zachowywała się tak samo.

Tak więc zminimalizowana wersja nie jest w kolejności, której się spodziewam, ale działa.

jrummell
źródło

Odpowiedzi:

18

Nie widzę tego zachowania na bitach RTM, czy używasz bitów Microsoft ASP.NET Web Optimization Framework 1.0.0: http://nuget.org/packages/Microsoft.AspNet.Web.Optimization ?

Użyłem podobnej reprodukcji do twojej próbki, opartej na nowej stronie internetowej aplikacji MVC4.

Dodałem do 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); 

A potem na domyślnej stronie indeksu dodałem:

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

I sprawdziłem, że w zminimalizowanym skrypcie javascript dla paczki zawartość osiągnięć.js była po modernizr ...

Hao Kung
źródło
Zaktualizowałem do wersji 1.0. Zobacz moje zaktualizowane pytanie. Treść osiągnięć.js jest umieszczona jako pierwsza w wersji zminimalizowanej, ale działa, ponieważ jest to funkcja nazwana później.
jrummell
115

Możesz napisać niestandardowego zamawiającego paczki ( IBundleOrderer), który zapewni, że pakiety będą uwzględnione w zamówieniu, w którym je zarejestrujesz:

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

i wtedy:

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

i Twoim zdaniem:

@Scripts.Render("~/bundles/scripts/canvas")
Darin Dimitrov
źródło
10
Powinno to zadziałać, ale powinno być też niepotrzebne, ponieważ domyślny zamawiający ogólnie przestrzega kolejności dołączeń (promuje tylko kilka specjalnych plików na górę, np. Jquery, reset.css / normalize.css itp.). Nie powinno promować osiągnięć.js na szczycie.
Hao Kung,
1
@flipdoubt zgłoś problem tutaj z repliką
Hao Kung
1
Działa idealnie, dzięki! Zgadzam się jednak, że nie powinno to być konieczne.
CBarr
2
To działa dla mnie. Promował jqueryui zamiast bootstrapu, kiedy zgodnie ze stackoverflow.com/questions/17367736/ ... potrzebuję go odwrócić.
Sethcran,
1
@Hao Kung czy możemy zamówić pliki w katalogu include ?
shaijut
52

Dziękuję Darin. Dodałem metodę rozszerzenia.

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

Stosowanie

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());
Softlion
źródło
9
Całkiem zręcznie! : D Teraz musiałem zmienić FileInfona BundleFilew metodzie podpisu interfejs. Zmienili to.
Leniel Maccaferri
tak, zmienili to w wersji wstępnej, która korzysta z frameworka
Owin
mały Off-Topic: czy ktoś ma więcej informacji na temat tych BundleFilezajęć? Aby spakować osadzone zasoby, próbuję utworzyć instancję i wymaga to (konkretnego elementu potomnego) VirtualFileparametru w swoim konstruktorze.
superjos
Mam to samo pytanie, potrzebuję instancji BundleFile, ale nie wiem jak
Rui
2
@ superjos Wiem, że to dość stare, ale odpowiem na wypadek, gdyby ktoś inny miał problem. Jest częścią System.Web.Optimization.
Talon
47

Zaktualizowano odpowiedź dostarczoną przez SoftLion, aby obsłużyć zmiany w 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;
    }
}

Stosowanie:

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

Lubię używać płynnej składni, ale działa również z pojedynczym wywołaniem metody i wszystkimi skryptami przekazywanymi jako parametry.

Gerald Davis
źródło
2
Pracuje w MVC 5. Dzięki
Pascal Carmoni
4
Świetny. Powinno to zostać uwzględnione przez MS imho
Angelo
działa jak urok w formularzach sieci Web ... w zasadzie powinien działać dobrze dla wszystkich najnowszych wersji!
Sunny Sharma
Bardzo czyste i proste rozwiązanie. Wielkie dzięki.
bunjeeb
5

Powinieneś być w stanie ustawić kolejność za pomocą BundleCollection.FileSetOrderList. Spójrz na ten post na blogu: http://weblogs.asp.net/imranbaloch/archive/2012/09/30/hidden-options-of-asp-net-bundling-and-minification.aspx . Kod w Twojej instancji wyglądałby tak:

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);
Arne H. Bitubekk
źródło
1

Powinieneś rozważyć użycie Cassette http://getcassette.net/ Obsługuje automatyczne wykrywanie zależności skryptów na podstawie odwołań do skryptów znajdujących się w każdym pliku.

Jeśli wolisz trzymać się rozwiązania MS Web Optimization, ale podoba Ci się pomysł porządkowania skryptów na podstawie odniesień do skryptów, przeczytaj mój post na blogu pod adresem http://blogs.microsoft.co.il/oric/2013/12/27/building-single -page-application-bundle-orderer /

Ori Calvo
źródło
1

Odpowiedź @Darina Dimitrova działa idealnie dla mnie, ale mój projekt jest napisany w VB, więc oto jego odpowiedź przekonwertowana na 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

Aby z niego skorzystać:

Dim scriptBundleMain = New ScriptBundle("~/Scripts/myBundle")
scriptBundleMain.Orderer = New AsIsBundleOrderer()
scriptBundleMain.Include(
    "~/Scripts/file1.js",
    "~/Scripts/file2.js"
)
bundles.Add(scriptBundleMain)
CBarr
źródło
Ciągle pojawia się ten błąd: Class 'AsIsBundleOrderer' must implement 'Function OrderFiles(context as ....)' for interface 'System.Web.Optimization.IBundleOrderer'jakieś pomysły?
Mark Pieszak - Trilon.io