Подтвердить что ты не робот

Объединение и минимизация JavaScript на лету ИЛИ во время сборки - ASP.NET MVC

В качестве дополнения к этому вопросу здесь Связывание библиотек JavaScript в элементах управления пользователями Я был после нескольких примеров того, как люди конкатенируют и минимизируют JavaScript на лету ИЛИ во время сборки, Я также хотел бы посмотреть, как это работает на ваших главных страницах.

Я не возражаю против того, чтобы файлы с конкретными страницами были проиндексированы и связаны друг с другом, как они есть в настоящее время (см. ниже), но все файлы JavaScript на главной главной странице (у меня около 5 или 6). Я хотел бы объединить и уменьшить.

Бонусные баллы для всех, кто также включает в себя CSS-конкатенацию и минимизацию!: -)

Текущая главная страница с общими файлами JavaScript, которые я хотел бы объединить и уменьшить:

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>
<head runat="server">
    ... BLAH ...
    <asp:ContentPlaceHolder ID="AdditionalHead" runat="server" />
    ... BLAH ...
    <%= Html.CSSBlock("/styles/site.css") %>
    <%= Html.CSSBlock("/styles/jquery-ui-1.7.1.css") %>
    <%= Html.CSSBlock("/styles/jquery.lightbox-0.5.css") %>
    <%= Html.CSSBlock("/styles/ie6.css", 6) %>
    <%= Html.CSSBlock("/styles/ie7.css", 7) %>
    <asp:ContentPlaceHolder ID="AdditionalCSS" runat="server" />
</head>
<body>
    ... BLAH ...
    <%= Html.JSBlock("/scripts/jquery-1.3.2.js", "/scripts/jquery-1.3.2.min.js") %>
    <%= Html.JSBlock("/scripts/jquery-ui-1.7.1.js", "/scripts/jquery-ui-1.7.1.min.js") %>
    <%= Html.JSBlock("/scripts/jquery.validate.js", "/scripts/jquery.validate.min.js") %>
    <%= Html.JSBlock("/scripts/jquery.lightbox-0.5.js", "/scripts/jquery.lightbox-0.5.min.js") %>
    <%= Html.JSBlock("/scripts/global.js", "/scripts/global.min.js") %>
    <asp:ContentPlaceHolder ID="AdditionalJS" runat="server" />
</body>

Используется на странице, подобной этой (которой я доволен):

<asp:Content ID="signUpContent" ContentPlaceHolderID="AdditionalJS" runat="server">
    <%= Html.JSBlock("/scripts/pages/account.signup.js", "/scripts/pages/account.signup.min.js") %>
</asp:Content>


UPDATE: Рекомендации на данный момент (конец 2013 года):

Я бы посмотрел на Microsoft ASP.NET, встроенный в Bundling and Minification.

4b9b3361

Ответ 1

В приложении Профессиональный ASP.NET 3.5 Скотт Гензельман рассказывает о Packer для .NET. Это будет интегрировано с файлами MSBuild и пакета javascript для производственных развертываний и т.д.

Ответ 2

Попробуйте следующее:

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

Первое очевидное, что нужно сделать, это сравнить свой сайт с использованием Yahoos YSlow и Googles PageSpeed. Они будут подчеркивать улучшение производительности "низких повешений". Если вы уже не сделали этого, полученные предложения почти наверняка будут включать в себя объединение, минимизацию и gzipping вашего статического контента.

Действия, которые должны были выполняться:

Напишите пользовательский HTTPHandler для объединения и минимизации CSS. Напишите пользовательский HTTPHandler для объединения и минимизации JS. Включите механизм, гарантирующий, что вышесказанное делает только свою магию, когда приложение не находится в режиме отладки. Напишите пользовательский веб-элемент управления на стороне сервера, чтобы легко поддерживать включение файла css/js. Включить GZIP определенных типов контента в IIS 6. Правильно, давайте начнем с CSSHandler.asax, который реализует интерфейс .NET IHttpHandler:

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Web;

namespace WebApplication1
{
    public class CssHandler : IHttpHandler
    {
        public bool IsReusable { get { return true; } }

        public void ProcessRequest(HttpContext context)
        {
            string[] cssFiles = context.Request.QueryString["cssfiles"].Split(',');

            List<string> files = new List<string>();
            StringBuilder response = new StringBuilder();
            foreach (string cssFile in cssFiles)
            {
                if (!cssFile.EndsWith(".css", StringComparison.OrdinalIgnoreCase))
                {
                    //log custom exception
                    context.Response.StatusCode = 403;
                    return;
                }

                try
                {
                    string filePath = context.Server.MapPath(cssFile);
                    string css = File.ReadAllText(filePath);
                    string compressedCss = Yahoo.Yui.Compressor.CssCompressor.Compress(css);
                    response.Append(compressedCss);
                }
                catch (Exception ex)
                {
                    //log exception
                    context.Response.StatusCode = 500;
                    return;
                }
            }

            context.Response.Write(response.ToString());

            string version = "1.0"; //your dynamic version number 

            context.Response.ContentType = "text/css";
            context.Response.AddFileDependencies(files.ToArray());
            HttpCachePolicy cache = context.Response.Cache;
            cache.SetCacheability(HttpCacheability.Public);
            cache.VaryByParams["cssfiles"] = true;
            cache.SetETag(version);
            cache.SetLastModifiedFromFileDependencies();
            cache.SetMaxAge(TimeSpan.FromDays(14));
            cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
        }
    }
}

Хорошо, теперь некоторое объяснение:

Свойство IsReUsable:

Мы имеем дело с любым конкретным экземпляром, что означает, что мы можем безопасно повторно использовать один и тот же экземпляр обработчика для обработки нескольких запросов, потому что наш ProcessRequest является потокобезопасным. Дополнительная информация.

Метод ProcessRequest:

Ничего слишком напряженного здесь. Прокручивались через предоставленные нам файлы CSS (см. Ниже приведенный ниже CSSControl, как они поступают) и сжимают каждый из них, используя порт .NET Yahoos YUICompressor, перед добавлением содержимого в поток исходящих ответов.

Остальная часть метода связана с настройкой некоторых свойств кеширования HTTP для дальнейшей оптимизации загрузки клиентом браузера (или, если это будет возможно) содержимого.

Мы устанавливаем Etags в код, чтобы они были одинаковыми для всех машин нашей фермы серверов. Мы устанавливаем зависимости Response и Cache на наши фактические файлы, поэтому, если они будут заменены, кеш будет признан недействительным. Мы устанавливаем Cacheability таким образом, чтобы прокси могли кэшировать. Мы VaryByParams используем наш атрибут cssfiles, чтобы мы могли кэшировать каждую группу файлов CSS, отправленную через обработчик. И вот CSSControl, настраиваемый серверный элемент управления, наследующий .NET LiteralControl.

Спереди:

<customcontrols:csscontrol id="cssControl" runat="server">
  <CustomControls:Stylesheet File="main.css" />
  <CustomControls:Stylesheet File="layout.css" />
  <CustomControls:Stylesheet File="formatting.css" />
</customcontrols:csscontrol>

Назад:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Linq;
using TTC.iTropics.Utilities;

namespace WebApplication1
{
    [DefaultProperty("Stylesheets")]
    [ParseChildren(true, "Stylesheets")]
    public class CssControl : LiteralControl
    {
        [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
        public List<Stylesheet> Stylesheets { get; set; }

        public CssControl()
        {
            Stylesheets = new List<Stylesheet>();
        }

        protected override void Render(HtmlTextWriter output)
        {
            if (HttpContext.Current.IsDebuggingEnabled)
            {
                const string format = "<link rel=\"Stylesheet\" href=\"stylesheets/{0}\"></link>";

                foreach (Stylesheet sheet in Stylesheets)
                    output.Write(format, sheet.File);
            }
            else
            {
                const string format = "<link type=\"text/css\" rel=\"Stylesheet\" href=\"stylesheets/CssHandler.ashx?cssfiles={0}&version={1}\"/>";
                IEnumerable<string> stylesheetsArray = Stylesheets.Select(s => s.File);
                string stylesheets = String.Join(",", stylesheetsArray.ToArray());
                string version = "1.00" //your version number

                output.Write(format, stylesheets, version);
            }

        }
    }

    public class Stylesheet
    {
        public string File { get; set; }
    }
}

HttpContext.Current.IsDebuggingEnabled подключен к следующему параметру в вашем web.config:

<system.web>
  <compilation debug="false">
</system.web>

Итак, в основном, если ваш сайт находится в режиме отладки, вы получаете разметку HTML следующим образом:

<link rel="Stylesheet" href="stylesheets/formatting.css"></link>
<link rel="Stylesheet" href="stylesheets/layout.css"></link
<link rel="Stylesheet" href="stylesheets/main.css"></link>

Но если вы работаете в режиме производства (debug = false), вы получите разметку следующим образом:

<link type="text/css" rel="Stylesheet" href="CssHandler.ashx?cssfiles=main.css,layout.css,formatting.css&version=1.0"/>

Затем последний явно вызовет CSSHandler, который позаботится о том, чтобы комбинировать, минимизировать и кэшировать ваш статический контент CSS.

Все вышеперечисленное может быть также дублировано для вашего статического содержимого JavaScript:

`JSHandler.ashx:

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Web;

namespace WebApplication1
{
    public class JSHandler : IHttpHandler
    {
        public bool IsReusable { get { return true; } }

        public void ProcessRequest(HttpContext context)
        {
            string[] jsFiles = context.Request.QueryString["jsfiles"].Split(',');

            List<string> files = new List<string>();
            StringBuilder response = new StringBuilder();

            foreach (string jsFile in jsFiles)
            {
                if (!jsFile.EndsWith(".js", StringComparison.OrdinalIgnoreCase))
                {
                    //log custom exception
                    context.Response.StatusCode = 403;
                    return;
                }

                try
                {
                    string filePath = context.Server.MapPath(jsFile);
                    files.Add(filePath);
                    string js = File.ReadAllText(filePath);
                    string compressedJS = Yahoo.Yui.Compressor.JavaScriptCompressor.Compress(js);
                    response.Append(compressedJS);
                }
                catch (Exception ex)
                {
                    //log exception
                    context.Response.StatusCode = 500;
                    return;
                }
            }

            context.Response.Write(response.ToString());

            string version = "1.0"; //your dynamic version number here

            context.Response.ContentType = "application/javascript";
            context.Response.AddFileDependencies(files.ToArray());
            HttpCachePolicy cache = context.Response.Cache;
            cache.SetCacheability(HttpCacheability.Public);
            cache.VaryByParams["jsfiles"] = true;
            cache.VaryByParams["version"] = true;
            cache.SetETag(version);
            cache.SetLastModifiedFromFileDependencies();
            cache.SetMaxAge(TimeSpan.FromDays(14));
            cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
        }
    }
}

И его сопутствующий ОАО:

Спереди:

<customcontrols:JSControl ID="jsControl" runat="server">
  <customcontrols:Script File="jquery/jquery-1.3.2.js" />
  <customcontrols:Script File="main.js" />
  <customcontrols:Script File="creditcardpayments.js" />
</customcontrols:JSControl>

Назад:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Linq;

namespace WebApplication1
{
    [DefaultProperty("Scripts")]
    [ParseChildren(true, "Scripts")]
    public class JSControl : LiteralControl
    {
        [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
        public List<Script> Scripts { get; set; }

        public JSControl()
        {
            Scripts = new List<Script>();
        }

        protected override void Render(HtmlTextWriter writer)
        {
            if (HttpContext.Current.IsDebuggingEnabled)
            {
                const string format = "<script src=\"scripts\\{0}\"></script>";

                foreach (Script script in Scripts)
                    writer.Write(format, script.File);
            }
            else
            {
                IEnumerable<string> scriptsArray = Scripts.Select(s => s.File);
                string scripts = String.Join(",", scriptsArray.ToArray());
                string version = "1.0" //your dynamic version number
                const string format = "<script src=\"scripts/JsHandler.ashx?jsfiles={0}&version={1}\"></script>";

                writer.Write(format, scripts, version);
            }
        }
    }

    public class Script
    {
        public string File { get; set; }
    }
}

Включение GZIP:

Как говорит Джефф Этвуд, включение Gzip на ваш сервер веб-сайта не вызывает затруднений. После некоторой трассировки я решил включить Gzip в следующих типах файлов:

.css .js .axd(файлы Microsoft Javascript) .aspx(Обычное содержимое веб-форм ASP.NET) .ashx(Наши обработчики) Чтобы включить сжатие HTTP на веб-сервере IIS 6.0:

Откройте IIS, щелкните правой кнопкой мыши веб-узлы, вкладку "Службы", включите файлы сжатых файлов и сжимайте статические файлы Остановить IIS Откройте метабазу IIS в блокноте (C:\WINDOWS\system32\inetsrv\MetaBase.xml) - и сделайте резервную копию, если вы нервничаете из-за этих вещей Найдите и перезапишите два элемента IIsCompressionScheme и один элемент IsCompressionSchemes со следующим:               

И вот оно! Это сэкономило нам массу пропускной способности и привело к появлению более гибкого веб-приложения.

Наслаждайтесь!

Ответ 3

Почему бы не использовать ScriptManager? Здесь MVCScriptManager, который будет сочетать AND squish.

Ответ 4

Используйте компрессор YUI или компрессор Dojo. Они оба используют механизм разбора Rhino JS, который токенизирует ваш код и поэтому будет работать, только если код является допустимым кодом. Если есть ошибка, они сообщают вам (что является хорошим бонусом IMO!) Packer, с другой стороны, упакует ваш код, даже если он содержит ошибки.

Я использую YUI во всех моих проектах через скрипты сборки. Никогда не делайте это "на лету", для сжатия требуется слишком много времени. И YUI, и Dojo основаны на Java (ala Rhino), и если вы это сделаете "на лету", вы будете создавать нереализованные фоновые процессы для генерации вывода - это не хорошо для производительности. Всегда делайте это во время сборки.

Ответ 5

Rejuicer - отличный новый minifier для ASP.NET, который вызывает много шума: http://rejuice.me

Он настроен как HTTP-модуль и выполняет минимизацию во время выполнения (один раз) и кэширует вывод.

Это:

  • Имеет свободный интерфейс для конфигурации
  • Позволяет вам указывать файлы для минимизации с помощью подстановочных правил.
  • Работает на Windows Azure
  • Немного волшебным образом отключается в средах разработки, поэтому вы можете отлаживать исходный код javascript (не уточняется).

Конфигурация (сделанная на ApplicationStart в global.asax.cs) проста:

OnRequest.ForJs("~/Combined.js")
            .Compact
            .FilesIn("~/Scripts/")
              .Matching("*.js")
            .Cache
            .Configure();

Ответ 6

Вот что я использовал для конкатенации, сжатия и кэширования CSS и JS файлов: http://gist.github.com/130913

Это просто требует Yahoo.Yui.Compressor.dll в каталоге bin. Он не сжимается во время компиляции, но файлы кэшируются с зависимостями файлов, поэтому они загружаются только один раз, пока они не будут изменены.

Затем я просто добавляю этот код в <head> :

<link rel="stylesheet" type="text/css" href="/YuiCompressor.ashx?css=reset,style,etc" />

и это непосредственно перед </body> :

<script type="text/javascript" src="/YuiCompressor.ashx?js=main,other,etc"></script>

Он предназначен для работы с несколькими файлами по одному и тому же пути, но может быть легко обновлен для поддержки разных путей.

Ответ 7

Я использую индивидуальное решение на основе MSBuild и Microsoft Ajax Minifier. Значительная часть существующих сообщений в блоге не правильно обрабатывает определенные случаи, такие как интеграция с сборкой TFS.

Для каждого веб-проекта мы создаем файл wpp.targets для расширения веб-публикации. Например, если проект "Website.csproj" создает в проекте файл с именем "Website.wpp.targets".

Поместите следующий код в файл целей:

<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="$(MSBuildExtensionsPath32)\PATH TO YOUR MSBUILD MINIFY TARGETS" />

  <!-- Hook up minification task to WPP build process -->
  <PropertyGroup>
    <OnAfterPipelineTransformPhase>
      $(OnAfterPipelineTransformPhase);
      MinifyResourceFiles;
    </OnAfterPipelineTransformPhase>
  </PropertyGroup>

  <!-- Define temporary location to store minified resources -->
  <PropertyGroup>
    <MinifyResourceIntermediateOutput Condition="'$(MinifyResourceIntermediateOutput)'==''">MinifyResourceFiles</MinifyResourceIntermediateOutput>
    <MinifyResourceIntermediateLocation Condition="'$(MinifyResourceIntermediateLocation)'==''">$(_WPPDefaultIntermediateOutputPath)$(MinifyResourceIntermediateOutput)</MinifyResourceIntermediateLocation>
  </PropertyGroup>

  <Target Name="MinifyResourceFiles" DependsOnTargets="PipelineCollectFilesPhase" Condition="'$(Configuration)' == 'Release'">
    <!-- Create lists of the resources to minify -->
    <!-- These extract all Javascript and CSS files from the publishing pipeline "FilesForPackagingFromProject" and create two new lists.
     The "MinifiedFile" metadata on each item contains the temporary location where the minified file will be stored -->
    <ItemGroup>
      <JavaScriptToMinify Include="@(FilesForPackagingFromProject)" 
                          Condition="'%(FilesForPackagingFromProject.Extension)' == '.js'">
        <MinifiedFile>$(MinifyResourceIntermediateLocation)\minified\%(DestinationRelativePath)</MinifiedFile>
      </JavaScriptToMinify>
      <StylesheetToMinify Include="@(FilesForPackagingFromProject)"
                          Condition="'%(FilesForPackagingFromProject.Extension)' == '.css'">
        <MinifiedFile>$(MinifyResourceIntermediateLocation)\minified\%(DestinationRelativePath)</MinifiedFile>
      </StylesheetToMinify>    
    </ItemGroup>

    <!-- Minify resources -->
    <!-- These commands should be replaced with the MSBuild Tasks used to perform your minification
         I use my own custom tasks based on the Microsoft Ajax Minifier DLL 
         The input of the minifier takes a source file directly from the project and outputs to a temporary location -->
    <MinifyJavaScript SourceFiles="@(JavaScriptToMinify)" DestinationFiles="@(JavaScriptToMinify->'%(MinifiedFile)')"
                      Comments="None" />
    <MinifyStylesheet SourceFiles="@(StylesheetToMinify)" DestinationFiles="@(StylesheetToMinify->'%(MinifiedFile)')"
                      Comments="None" />

    <!-- Remove the original source files from the packaging system and include the new minfied resources from the temporary location -->
    <ItemGroup>
      <!--Remove unminified resources from the pipeline -->
      <FilesForPackagingFromProject Remove="@(JavaScriptToMinify)" Condition="'@(JavaScriptToMinify)' != ''" />
      <FilesForPackagingFromProject Remove="@(StylesheetToMinify)" Condition="'@(StylesheetToMinify)' != ''" />
      <!--Add the minified resources at the new loction to the pipeline -->
      <FilesForPackagingFromProject Include="@(JavaScriptToMinify->'%(MinifiedFile)')" Condition="'@(JavaScriptToMinify)' != ''"/>
      <FilesForPackagingFromProject Include="@(StylesheetToMinify->'%(MinifiedFile)')" Condition="'@(StylesheetToMinify)' != ''"/>
    </ItemGroup>
  </Target>
</Project>

Условие '' $(Конфигурация ') ==' Release '' в отношении цели минимизации может быть изменено в зависимости от ваших потребностей. Он автоматически минимизирует (и проверяет) все файлы CSS и JS в проекте при публикации, упаковке и создании на сервере.

Возможно, вам потребуется включить WPP "CopyWebApplication" для сборки сервера. Для этого установите для свойства MSBuild UseWP_CopyWebApplication значение True и PipelineDependsOnBuild для False. Мы устанавливаем их в файле проекта, прежде чем файл целевого файла веб-приложения будет включен.

Ответ 8

Я бы рекомендовал http://www.RequestReduce.com, который сводит к минимуму и объединяет css и javascript, а также спрайты css с фоновыми изображениями и оптимизирует их сжатие PNG. Он делает все это во время выполнения и кэширует выход. Он не требует никакого кода или конфигурации, кроме добавления HttpModule. Он обслуживает весь кешированный контент с оптимизированными далекими будущими заголовками и ETags, чтобы гарантировать, что браузеры кэшируют css/javascript/спрайты как можно дольше. Хотя он не требует конфигурации, он очень настраивается и может быть настроен для работы с CDN и синхронизированными кешированными файлами на веб-ферме.

Все javascript, изображения и css извлекаются через HTTP, поэтому он может включать в себя css и js от третьих сторон, а также отличный способ минимизировать/объединить ресурсы .axd, такие как WebResource.axd и ScriptResource.axd. Он определяет наличие js и css по типу контента, поэтому целевой ресурс может иметь любое (или нет) расширение. Он работает с любой технологией, основанной на IIS, включая все версии и механизмы просмотра MVC, веб-форм и "веб-страниц".

Вы можете скачать с http://www.RequestReduce.com, Nuget или fork из https://github.com/mwrock/RequestReduce.