Как я могу указать явный заказ ScriptBundle?

Я пытаюсь использовать MVC4 System.Web.Optimization 1.0 ScriptBundle.

У меня есть следующая конфигурация:

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

и следующий вид:

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

где BundlePaths.CanvasScripts - "~/bundles/scripts/canvas". Он делает это:

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

Пока что так хорошо, кроме ~/Scripts/Shared/achievements.js является первым script в связанном источнике. Это зависит от каждого script, включенного до него в ScriptBundle. Как я могу гарантировать, что он соблюдает порядок, в который я добавляю include-предложения в пакет?

Обновление

Это было относительно новое приложение ASP.NET MVC 4, но оно ссылалось на пакет предварительной версии инфраструктуры оптимизации. Я удалил его и добавил пакет RTM из http://nuget.org/packages/Microsoft.AspNet.Web.Optimization. С версией RTM с debug = true в web.config @Scripts.Render("~/bundles/scripts/canvas") отображает отдельные теги script в правильном порядке.

С debug = false в web.config объединенный script сначала имеет достижения .js script, но поскольку его определение функции (конструктор объекта), которое вызывается позже, оно выполняется без ошибок. Возможно, minifier достаточно умен, чтобы выяснить зависимости.

Я также попробовал реализацию IBundleOrderer, которую Дарин Димитров предложил с RTM с обеими параметрами отладки, и он вел себя одинаково.

Таким образом, сокращенная версия не в том порядке, который я ожидаю, но она работает.

Ответ 1

Я не вижу этого поведения в битках RTM, вы используете Microsoft ASP.NET Web Optimization Framework 1.0.0 бит: http://nuget.org/packages/Microsoft.AspNet.Web.Optimization?

Я использовал аналогичную репродукцию для вашего примера, основанную на новом веб-сайте интернет-приложения MVC4.

Я добавил в 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); 

И затем на индексной странице по умолчанию я добавил:

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

И я подтвердил, что в миниатюре javascript для пакета содержимое достижений .js было после модернизации...

Ответ 2

Вы можете написать собственный заказчик (IBundleOrderer), который обеспечит включение пакетов в порядке их регистрации:

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

а затем:

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

и на ваш взгляд:

@Scripts.Render("~/bundles/scripts/canvas")

Ответ 3

Спасибо Дарин. Я добавил метод расширения.

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

Использование

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

Ответ 4

Обновлен ответ, предоставленный SoftLion для обработки изменений в 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;
    }
}

Использование:

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

Мне нравится использовать свободный синтаксис, но он также работает с одним вызовом метода и всеми скриптами, переданными как параметры.

Ответ 5

Вы можете установить порядок с помощью BundleCollection.FileSetOrderList. Взгляните на это сообщение в блоге: http://weblogs.asp.net/imranbaloch/archive/2012/09/30/hidden-options-of-asp-net-bundling-and-minification.aspx. Код в вашем экземпляре будет выглядеть примерно так:

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

Ответ 6

Вам следует рассмотреть возможность использования Cassette http://getcassette.net/ Он поддерживает автоматическое обнаружение зависимостей script на основе script ссылок, найденных внутри каждого файла.

Если вы предпочитаете придерживаться решения MS Web Optimization, но, как идея создания сценариев на основе ссылок script, вы должны прочитать мой пост в блоге http://blogs.microsoft.co.il/oric/2013/12/27/building-single-page-application-bundle-orderer/

Ответ 7

@Darin Dimitrov отвечает для меня отлично, но мой проект написан на VB, поэтому здесь его ответ преобразован в 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

Использовать его:

Dim scriptBundleMain = New ScriptBundle("~/Scripts/myBundle")
scriptBundleMain.Orderer = New AsIsBundleOrderer()
scriptBundleMain.Include(
    "~/Scripts/file1.js",
    "~/Scripts/file2.js"
)
bundles.Add(scriptBundleMain)