Bundle Transformer: Летние обновления
Начиная с сентября прошлого года, когда библиотека MSIE JavaScript Engine for .NET была заменена библиотекой JavaScript Engine Switcher и был создан модуль BundleTransformer.CleanCss, в Bundle Transformer практически не было каких-либо революционных изменений. Изменения были в основном эволюционными: добавлялась поддержка новых версий минимизаторов и трансляторов (самая рутинная и сложная часть работы над проектом), исправлялись мелкие ошибки и шла работа над увеличением производительности.
Но этим летом все изменилось: с конца мая по июль от пользователей Bundle Transformer было получено огромное количество рекомендаций по улучшению проекта. Большая часть из них была реализована в версии 1.9.0 и последующих летних обновлениях. В данной статье мы рассмотрим наиболее значимые из них:
Классы StyleTransformer и ScriptTransformerИзначально для классов CssTransformer и JsTransformer были выбраны не совсем удачные имена, потому что выбор имен производился по аналогии с классами CssMinify и JsMinify из System.Web.Optimization. Когда в System.Web.Optimization появились классы StyleBundle и ScriptBundle, то стало понятно, что эти названия никуда не годятся.Я долго откладывал переименование этих классов на потом, но при работе над версией 1.9.0 я решил их все-таки переименовать. Теперь они называются StyleTransformer и ScriptTransformer. Старые классы CssTransformer и JsTransformer по-прежнему доступны в ядре (реализованы как обертки над новыми классами), но они считаются устаревшими (помечены атрибутом Obsolete) и будут удалены в версии 2.0.0.
Постпроцессоры В конце мая мне пришел pull request «Support for Autoprefixer» от Вегарда Ларсена (сотрудника норвежской компании Digital Creations). При просмотре кода сразу стало понятно, что текущая архитектура Bundle Transformer не подходит для реализации таких модулей. Вегард реализовал функционал этого модуля в виде транслятора стилей, который должен был запускаться после всех остальных трансляторов (LESS, Sass и т.д.). Вся эта реализация выглядела как хак, поэтому я принял решение отклонить данный pull request. В итоге Вегард опубликовал в NuGet неофициальную версию модуля — BundleTransformer.Autoprefixer.Unofficial, а я начал работу над новой архитектурой Bundle Transformer.Требовался новый тип модулей, которые должны были запускаться после трансляторов и перед минимизаторами, причем количество и порядок вызова таких модулей должен определяться разработчиком. В качестве названия для нового типа модулей я решил использовать термин «постпроцессоры», который придумал Андрей Ситник (если вы не знаете, кто такой Андрей Ситник или что такое постпроцессоры, то рекомендую вам прослушать 6-й выпуск подкаста Frontflip).
Постпроцессором в Bundle Transformer может быть любой класс, реализующий интерфейс IPostProcessor или наследующий базовый класс PostProcessorBase из пространства имен BundleTransformer.Core.PostProcessors. Как и другие типы модулей (адаптеров) постпроцессоры должны быть зарегистрированы в файле Web.config. Рассмотрим процесс регистрации на примере CSS-постпроцессоров:
Регистрация JavaScript-постпроцессоров практически ничем не отличается от регистрации CSS-постпроцессоров, за исключением того, что она должна производиться в конфигурационном элементе /configuration/bundleTransformer/core/js.
Усовершенствованные отладочные HTTP-хэндлеры Как правило, большинство пользователей Bundle Transformer настраивают модули декларативным способом (через Web.config), но в некоторых случаях приходится использовать императивный подход. Например, при работе с LESS-переменными: using System.Collections.Generic; using System.Web.Optimization;
using BundleTransformer.Core.Builders; using BundleTransformer.Core.Orderers; using BundleTransformer.Core.Transformers; using BundleTransformer.Core.Translators; using BundleTransformer.Less.Translators;
public class BundleConfig { public static void RegisterBundles (BundleCollection bundles) { var nullBuilder = new NullBuilder (); var nullOrderer = new NullOrderer ();
var lessTranslator = new LessTranslator
{
GlobalVariables = «my-variable='Hurrah!'»,
ModifyVariables = «font-family-base='Comic Sans MS'; body-bg=lime; font-size-h1=50 px»
};
var styleTransformer = new StyleTransformer (
new List
var commonStylesBundle = new Bundle (»~/Bundles/BootstrapStyles»); commonStylesBundle.Include (»~/Content/bootstrap/bootstrap.less»); commonStylesBundle.Builder = nullBuilder; commonStylesBundle.Transforms.Add (styleTransformer); commonStylesBundle.Orderer = nullOrderer; bundles.Add (commonStylesBundle); } } В приведенном выше коде мы явно создаем экземпляр класса LessTranslator и с помощью свойств GlobalVariables и ModifyVariables производим настройку LESS-переменных. При таком подходе мы можем передавать транслятору значения LESS-переменных, полученные из внешнего источника (например, базы данных).Существует и второй способ работы с LESS-переменными. Сначала нужно создать пользовательскую трансформацию элемента:
using System.Text; using System.Web.Optimization;
public sealed class InjectContentItemTransform: IItemTransform { private readonly string _beforeContent; private readonly string _afterContent;
public InjectContentItemTransform (string beforeContent, string afterContent) { _beforeContent = beforeContent? string.Empty; _afterContent = afterContent? string.Empty; }
public string Process (string includedVirtualPath, string input) { if (_beforeContent.Length == 0 && _afterContent.Length == 0) { return input; }
var contentBuilder = new StringBuilder (); if (_beforeContent.Length > 0) { contentBuilder.AppendLine (_beforeContent); } contentBuilder.AppendLine (input); if (_afterContent.Length > 0) { contentBuilder.AppendLine (_afterContent); }
return contentBuilder.ToString (); } } А затем зарегистрировать ее при добавлении файла в бандл: using System.Web.Optimization;
using BundleTransformer.Core.Bundles; using BundleTransformer.Core.Orderers;
public class BundleConfig { public static void RegisterBundles (BundleCollection bundles) { var nullOrderer = new NullOrderer ();
const string beforeLessCodeToInject = @»@my-variable: 'Hurrah!';»; const string afterLessCodeToInject = @»@font-family-base: 'Comic Sans MS'; @body-bg: lime; @font-size-h1: 50 px;»;
var commonStylesBundle = new CustomStyleBundle (»~/Bundles/BootstrapStyles»); commonStylesBundle.Include ( »~/Content/bootstrap/bootstrap.less», new InjectContentItemTransform (beforeLessCodeToInject, afterLessCodeToInject)); commonStylesBundle.Orderer = nullOrderer; bundles.Add (commonStylesBundle); } } К сожалению, приведенные выше примеры кода раньше работали только в режиме выпуска. Это было вызвано тем, что отладочные HTTP-хэндлеры ничего «не знали» о настройках бандлов и просто транслировали код запрашиваемых файлов.Чтобы решить эту проблему нужно было, в первую очередь, найти способ передать отладочным HTTP-хэндлерам URL бандла, в который входит запрашиваемый файл. После изучения кода сборки System.Web.Optimization.dll с помощью декомпилятора решение было найдено: нужно написать свою собственную версию класса BundleResolver и зарегистрировать его в соответствующем классе. Я не буду вдаваться в детали реализации, а просто покажу, как использовать созданный класс:
… using BundleTransformer.Core.Resolvers;
public class BundleConfig { public static void RegisterBundles (BundleCollection bundles) { BundleResolver.Current = new CustomBundleResolver (); … } } После этого в режиме отладки будут генерироваться ссылки на файлы следующего вида: , где параметр строки запроса bundleVirtualPath содержит URL бандла.Таким образом, имея в распоряжении URL бандла я добавил в базовый отладочный HTTP-хэндлер возможность применять к запрашиваемому файлу, привязанные к нему пользовательские трансформации элементов и трансформации, заданные на уровне бандла (трансляторы и постпроцессоры).
Кроме того, были созданы два дополнительных HTTP-хэндлера:
CssAssetHandler. Для обработки CSS-файлов.
JsAssetHandler. Для обработки JavaScript-файлов.
Они позволяют применять к статическим файлам пользовательские трансформации элементов и постпроцессоры. Если запрашиваемый статический файл не входит ни в один из бандлов, то эти HTTP-хэндлеры передают запрос экземпляру класса System.Web.StaticFileHandler. В отличие, от отладочных HTTP-хэнлеров, идущих в комплекте с трансляторами, данные HTTP-хэндлеры не регистрируются в файле Web.config автоматически (во время установки NuGet-пакетов), их необходимо регистрировать вручную:
Bundle Transformer: Autoprefixer Модуль BundleTransformer.Autoprefixer содержит адаптер-постпроцессор AutoprefixCssPostProcessor, производящий актуализацию вендорных префиксов в CSS-коде. AutoprefixCssPostProcessor создан на основе популярного CSS-постпроцессора — Autoprefixer (на данный момент поддерживается версия 3.1). Я не буду объяснять для чего нужен Autoprefixer, потому что всю основную информацию о данном продукте вы можете подчеркнуть из статьи Андрея Ситника «Автопрефиксер — окончательное решение проблемы префиксов в CSS».В этом разделе я расскажу о том, как правильно настроить BundleTransformer.Autoprefixer. Если вы не читали разделы «Постпроцессоры» и «Усовершенствованные отладочные HTTP-хэндлеры» данной статьи, то обязательно прочитайте их, т.к. в них затрагиваются многие важные моменты, связанные с работой BundleTransformer.Autoprefixer.
После установки BundleTransformer.Autoprefixer и выбора JavaScript-движка вам нужно выполнить следующие действия:
Добавить постпроцессор AutoprefixCssPostProcessor в конец списка активных CSS-постпроцессоров, который задается в атрибуте defaultPostProcessors конфигурационного элемента /configuration/bundleTransformer/core/css.
Зарегистрировать отладочный HTTP-хэндлер CssAssetHandler в файле Web.config (требуется для режима отладки).
Зарегистрировать экземпляр класса CustomBundleResolver в качестве текущего BundleResolver`а (требуется для режима отладки).
Затем в конфигурационной секции /configuration/bundleTransformer/autoprefixer файла Web.config можно сделать необязательные настройки алгоритма Autoprefixer`а:
public class BundleConfig { public static void RegisterBundles (BundleCollection bundles) { … var commonTemplatesBundle = new CustomScriptBundle (»~/Bundles/CommonTemplates»); commonTemplatesBundle.Include ( … »~/Scripts/handlebars/handlebars.runtime.js», »~/Scripts/handlebars/HandlebarsHelpers.js», »~/Scripts/handlebars/HandlebarsTranslatorBadge.handlebars», …); commonTemplatesBundle.Orderer = nullOrderer; bundles.Add (commonTemplatesBundle); … } } В отличие от CoffeeScript и TypeScript скомпилированные Handlebars-шаблоны требуют наличия файла handlebars.runtime.js (урезанная версия библиотеки handlebars.js, из которой исключен код необходимый для компиляции шаблонов). Этот файл можно разместить в бандле с общими библиотеками или в бандле с Handlebars-шаблонами. Главное, чтобы его объявление шло перед объявлением шаблонов.В конфигурационной секции /configuration/bundleTransformer/handlebars файла Web.config можно произвести настройку прекомпиляции шаблонов:
public class BundleConfig { public static void RegisterBundles (BundleCollection bundles) { … var commonTemplatesBundle = new CustomScriptBundle (»~/Bundles/CommonTemplates»); commonTemplatesBundle.Include ( »~/Scripts/hogan/template-{version}.js», »~/Scripts/hogan/HoganTranslatorBadge.mustache», …); commonTemplatesBundle.Orderer = nullOrderer; bundles.Add (commonTemplatesBundle); … } } Также как и в Handlebars скомпилированные шаблоны требуют наличия специальной JavaScript-библиотеки — template-3.0.2.js (в будущих релизах номер версии, скорее всего, изменится).В конфигурационной секции /configuration/bundleTransformer/hogan файла Web.config можно произвести настройку прекомпиляции шаблонов: