Объединять и минимизировать JavaScript на лету ИЛИ во время сборки - ASP.NET MVC - PullRequest
46 голосов
/ 21 мая 2009

В качестве дополнения к этому вопросу здесь Связывание библиотек 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>


ОБНОВЛЕНИЕ: Рекомендации на данный момент (конец 2013 года):

Я бы посмотрел на встроенную в Microsoft ASP.NET Связку и минификацию .

Ответы [ 8 ]

41 голосов
/ 12 ноября 2009

Попробуйте это:

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

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

Шаги, которые мы собираемся выполнить:

Напишите собственный 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 YUICompressor от Yahoo, перед добавлением содержимого в поток исходящих ответов. *

Оставшаяся часть метода посвящена настройке некоторых свойств кэширования 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 и один элемент IIsCompressionSchemes следующим образом:

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

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

14 голосов
/ 27 мая 2009

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

7 голосов
/ 21 мая 2009

В приложении Professional ASP.NET 3.5 Скотт Хансельман рассказывает о упаковщике для .NET . Он будет интегрирован с MSBuild и упаковывает файлы javascript для рабочих развертываний и т. Д.

6 голосов
/ 27 мая 2009

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

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

4 голосов
/ 06 июля 2011

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

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

Она:

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

Настройка (выполняется на ApplicationStart в global.asax.cs) так же проста, как:

OnRequest.ForJs("~/Combined.js")
            .Compact
            .FilesIn("~/Scripts/")
              .Matching("*.js")
            .Cache
            .Configure();
2 голосов
/ 10 ноября 2011

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

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

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

2 голосов
/ 22 июля 2011

Я использую индивидуальное решение на основе 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>

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

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

2 голосов
/ 17 июня 2009

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

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

Тогда я просто добавляю этот код в :

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

и это как раз перед :

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...