¿Cómo puedo especificar un orden de inclusión de ScriptBundle explícito?

91

Estoy probando la función MVC4 System.Web.Optimization 1.0 ScriptBundle .

Tengo la siguiente configuración:

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

y la siguiente vista:

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

donde BundlePaths.CanvasScriptsesta "~/bundles/scripts/canvas". Hace esto:

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

Hasta ahora todo va bien, excepto que ~/Scripts/Shared/achievements.jses el primer script en la fuente incluida. Depende de cada script incluido antes en el ScriptBundle. ¿Cómo puedo asegurarme de que respeta el orden en el que agrego declaraciones de inclusión al paquete?

Actualizar

Esta era una aplicación ASP.NET MVC 4 relativamente nueva, pero hacía referencia al paquete de versión preliminar del marco de optimización. Lo eliminé y agregué el paquete RTM de http://nuget.org/packages/Microsoft.AspNet.Web.Optimization . Con la versión RTM con debug = true en web.config,@Scripts.Render("~/bundles/scripts/canvas") procesa las etiquetas de script individuales en el orden correcto.

Con debug = false en web.config, la secuencia de comandos combinada tiene la secuencia de comandos logros.js primero, pero como es una definición de función (constructor de objeto) que se llama más tarde, se ejecuta sin errores. ¿Quizás el minificador es lo suficientemente inteligente como para descubrir dependencias?

También probé el IBundleOrderer implementación que sugirió Darin Dimitrov con RTM con ambas opciones de depuración y se comportó igual.

Entonces, la versión minificada no está en el orden que esperaba, pero funciona.

jrummell
fuente

Respuestas:

18

No veo este comportamiento en los bits de RTM, ¿está utilizando los bits de Microsoft ASP.NET Web Optimization Framework 1.0.0: http://nuget.org/packages/Microsoft.AspNet.Web.Optimization ?

Usé una reproducción similar a su muestra, basada en un nuevo sitio web de la aplicación de Internet MVC4.

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

Y luego, en la página de índice predeterminada, agregué:

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

Y verifiqué que en el javascript minificado para el paquete, el contenido de logros.js era posterior a modernizr ...

Hao Kung
fuente
Actualicé a la versión 1.0. Vea mi pregunta actualizada. El contenido de Achievements.js se incluye primero en la versión minimizada, pero funciona porque es una función llamada más tarde.
jrummell
115

Puede escribir un orden de paquetes personalizado ( IBundleOrderer) que garantizará que los paquetes se incluyan en el orden en que los registre:

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

y entonces:

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

y en tu opinión:

@Scripts.Render("~/bundles/scripts/canvas")
Darin Dimitrov
fuente
10
Esto debería funcionar, pero también debería ser innecesario, ya que el orden predeterminado generalmente respeta el orden de las inclusiones (solo promueve algunos archivos especiales en la parte superior, es decir, jquery, reset.css / normalize.css, etc.). No debería estar promocionando logros.js a la cima.
Hao Kung
1
@flipdoubt presenta un problema aquí con la reproducción: aspnetoptimization.codeplex.com/workitem/list/advanced
Hao Kung
1
¡Esto funciona perfecto, gracias! Sin embargo, estoy de acuerdo, esto no debería ser necesario.
CBarr
2
Esto funciona para mi. Estaba promocionando jqueryui sobre bootstrap cuando, de acuerdo con stackoverflow.com/questions/17367736/…, lo necesito volteado.
Sethcran
1
@Hao Kung, ¿podemos ordenar los archivos en el directorio de inclusión ?
shaijut
52

Gracias Darin. Agregué un método de extensión.

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
fuente
9
¡Muy hábil! : D Ahora mismo tenía que cambiar FileInfopara BundleFileen la firma del método de interfaz. Ellos lo cambiaron.
Leniel Maccaferri
sí, lo cambiaron en la versión preliminar que usa el marco
Owin
little Off-Topic: ¿Alguien tiene más información sobre esta BundleFileclase? Para agrupar recursos incrustados, estoy tratando de instanciar eso y requiere un VirtualFileparámetro (hijo concreto de) en su constructor.
superjos
Tengo la misma pregunta, necesito crear una instancia de BundleFile, pero no sé cómo
Rui
2
@superjos Sé que esto es bastante antiguo pero responderé en caso de que alguien más tenga el problema. Es parte de System.Web.Optimization.
Talon
47

Se actualizó la respuesta proporcionada por SoftLion para manejar los cambios en 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());

Me gusta usar una sintaxis fluida, pero también funciona con una sola llamada a un método y todos los scripts pasados ​​como parámetros.

Gerald Davis
fuente
2
Funciona en MVC 5. Gracias
Pascal Carmoni
4
Excelente. Esto debería ser incluido por MS en mi humilde opinión
Angelo
también funciona como un encanto en Web Forms ... ¡básicamente debería funcionar bien para todas las últimas versiones!
Sunny Sharma
Solución muy limpia y sencilla. Muchas gracias.
bunjeeb
5

Debería poder establecer el orden con la ayuda de BundleCollection.FileSetOrderList. Eche un vistazo a esta publicación de blog: http://weblogs.asp.net/imranbaloch/archive/2012/09/30/hidden-options-of-asp-net-bundling-and-minification.aspx . El código en su instancia sería 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
fuente
1

Debería considerar el uso de Cassette http://getcassette.net/ Admite la detección automática de dependencias de scripts en función de las referencias de scripts que se encuentran dentro de cada archivo.

Si prefiere seguir con la solución de optimización web de MS pero le gusta la idea de organizar scripts basados ​​en referencias de scripts, debe leer mi publicación de blog en http://blogs.microsoft.co.il/oric/2013/12/27/building-single -página-solicitud-paquete-ordenador /

Ori Calvo
fuente
1

La respuesta de @Darin Dimitrov funciona perfectamente para mí, pero mi proyecto está escrito en VB, así que aquí está su respuesta convertida a 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 usarlo:

Dim scriptBundleMain = New ScriptBundle("~/Scripts/myBundle")
scriptBundleMain.Orderer = New AsIsBundleOrderer()
scriptBundleMain.Include(
    "~/Scripts/file1.js",
    "~/Scripts/file2.js"
)
bundles.Add(scriptBundleMain)
CBarr
fuente
Sigo recibiendo este error: ¿ Class 'AsIsBundleOrderer' must implement 'Function OrderFiles(context as ....)' for interface 'System.Web.Optimization.IBundleOrderer'Alguna idea?
Mark Pieszak - Trilon.io