Como posso especificar um pedido de inclusão de ScriptBundle explícito?

91

Estou testando o recurso MVC4 System.Web.Optimization 1.0 ScriptBundle .

Tenho a seguinte configuração:

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

e a seguinte visão:

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

onde BundlePaths.CanvasScriptsestá "~/bundles/scripts/canvas". Ele processa isso:

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

Até aqui tudo bem, exceto que ~/Scripts/Shared/achievements.jsé o primeiro script na fonte agrupada. Depende de cada script incluído antes dele no ScriptBundle. Como posso garantir que ele respeite a ordem em que adiciono instruções de inclusão ao pacote?

Atualizar

Este era um aplicativo ASP.NET MVC 4 relativamente novo, mas fazia referência ao pacote de pré-lançamento da estrutura de otimização. Eu o removi e adicionei o pacote RTM de http://nuget.org/packages/Microsoft.AspNet.Web.Optimization . Com a versão RTM com debug = true em web.config, @Scripts.Render("~/bundles/scripts/canvas")renderiza as tags de script individuais na ordem correta.

Com debug = false em web.config, o script combinado tem o script Achievements.js primeiro, mas como é uma definição de função (construtor de objeto) que é chamada posteriormente, ele é executado sem erros. Talvez o minificador seja inteligente o suficiente para descobrir dependências?

Também tentei a IBundleOrdererimplementação que Darin Dimitrov sugeriu com RTM com ambas as opções de depuração e se comportou da mesma forma.

Portanto, a versão reduzida não está na ordem que espero, mas funciona.

Jrummell
fonte

Respostas:

18

Não estou vendo esse comportamento nos bits RTM. Você está usando o Microsoft ASP.NET Web Optimization Framework 1.0.0 bits: http://nuget.org/packages/Microsoft.AspNet.Web.Optimization ?

Usei uma reprodução semelhante à sua amostra, baseada em um novo site de aplicativo MVC4 da Internet.

Eu adicionei a 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); 

E então, na página de índice padrão, adicionei:

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

E eu verifiquei que no javascript minificado para o pacote, o conteúdo de Achievements.js estava depois de modernizr ...

Hao Kung
fonte
Eu atualizei para a versão 1.0. Veja minha pergunta atualizada. O conteúdo de Achievements.js é incluído primeiro na versão reduzida, mas funciona porque é uma função chamada posteriormente.
jrummell de
114

Você pode escrever um ordenador de pacote personalizado ( IBundleOrderer) que garantirá que os pacotes sejam incluídos no pedido em que você os registra:

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

e depois:

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

e na sua opinião:

@Scripts.Render("~/bundles/scripts/canvas")
Darin Dimitrov
fonte
10
Isso deve funcionar, mas também deve ser desnecessário, já que o solicitante padrão geralmente respeita a ordem dos includes (ele apenas promove alguns arquivos especiais para o topo, ou seja, jquery, reset.css / normalize.css, etc.). Não deve promover o Achievements.js para o topo.
Hao Kung de
1
@flipdoubt registre um problema aqui com o repro: aspnetoptimization.codeplex.com/workitem/list/advanced
Hao Kung
1
Isso funciona perfeitamente, obrigado! Eu concordo, porém, isso não deve ser necessário.
CBarr
2
Isso funciona para mim. Ele estava promovendo o jqueryui sobre o bootstrap quando, de acordo com stackoverflow.com/questions/17367736/… , preciso inverter .
Sethcran
1
@Hao Kung podemos ordenar os arquivos no diretório de inclusão ?
shaijut de
52

Obrigado Darin. Eu adicionei um método de extensão.

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

Uso

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
fonte
9
Muito liso! : D Agora eu tive que mudar FileInfopara BundleFilena assinatura do método de interface. Eles mudaram isso.
Leniel Maccaferri
sim, eles mudaram na versão de pré-lançamento que usa o framework
Owin
pequeno Off-Topic: alguém tem mais informações sobre essa BundleFileaula? Para agrupar recursos incorporados, estou tentando instanciar isso e requer um VirtualFileparâmetro (filho concreto de) em seu construtor.
superjos
Tenho a mesma pergunta, preciso instanciar o BundleFile, mas não sei como
Rui
2
@superjos Eu sei que isso é um pouco antigo, mas responderei caso mais alguém tenha o problema. Faz parte do System.Web.Optimization.
Talon
47

Atualizado a resposta fornecida pelo SoftLion para lidar com as mudanças no 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;
    }
}

Uso:

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

Gosto de usar sintaxe fluente, mas também funciona com uma única chamada de método e todos os scripts passados ​​como parâmetros.

Gerald Davis
fonte
2
Trabalha em MVC 5. Obrigado
Pascal Carmoni
4
Excelente. Deve ser incluído por MS imho
Angelo
funciona perfeitamente no Web Forms também ... basicamente deve funcionar bem para todas as versões mais recentes!
Sunny Sharma
Solução muito limpa e simples. Muito obrigado.
Bunjeeb
5

Você deve ser capaz de definir o pedido com a ajuda de BundleCollection.FileSetOrderList. Dê uma olhada nesta postagem do blog: http://weblogs.asp.net/imranbaloch/archive/2012/09/30/hidden-options-of-asp-net-bundling-and-minification.aspx . O código em sua instância seria algo como:

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
fonte
1

Você deve considerar o uso de Cassette http://getcassette.net/ Ele suporta detecção automática de dependências de script com base nas referências de script encontradas dentro de cada arquivo.

Se você preferir usar a solução MS Web Optimization, mas gosta da ideia de organizar scripts com base em referências de script, leia minha postagem do blog em http://blogs.microsoft.co.il/oric/2013/12/27/building-single -page-application-bundle-orderer /

Ori Calvo
fonte
1

A resposta de @Darin Dimitrov funciona perfeitamente para mim, mas meu projeto foi escrito em VB, então aqui está sua resposta convertida para 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

Para usá-lo:

Dim scriptBundleMain = New ScriptBundle("~/Scripts/myBundle")
scriptBundleMain.Orderer = New AsIsBundleOrderer()
scriptBundleMain.Include(
    "~/Scripts/file1.js",
    "~/Scripts/file2.js"
)
bundles.Add(scriptBundleMain)
CBarr
fonte
Continuo recebendo este erro: Class 'AsIsBundleOrderer' must implement 'Function OrderFiles(context as ....)' for interface 'System.Web.Optimization.IBundleOrderer'Alguma ideia?
Mark Pieszak - Trilon.io