[Из песочницы] Минификация приложений ExtJS и Sencha Touch средствами ASP.NET
Если вы пишете веб-приложения на ExtJS в связке с ASP.NET MVC и хотите минифицировать исходные файлы, но по каким-то причинам вам не нравится использовать для этого стандартный SenchaCmd, добро пожаловать под кат. Для тех, у кого нет времени и уже хочется попробовать, в конце статьи есть ссылки на библиотеку, а пока попробуем разобраться, в чём проблема и написать такой минификатор самостоятельно.Что будет в итоге public class BundleConfig { public static void RegisterBundles (BundleCollection bundles) { bundles.Add ( new SenchaBundle (»~/bundles/my-sencha-app») .IncludeDirectory (»~/Scripts/my-sencha-app»,»*.js», true) ); } } Intro Итак, вы разрабатываете с помощью библиотек ExtJS 4 или SenchaTouch 2, и ваши веб-приложения структурированы так, как это рекомендуют сами разработчики библиотеки. С ростом приложения количество исходников увеличивается, что наверняка приводит к задержке загрузки, ну или вы просто хотите скрыть свой красивый исходный код от чужих глаз.Первое, что приходит в голову это использовать SenchaCmd — продукт, который рекомендует команда Sencha. Ему можно скормить файл index.html или URL приложения, он послушно возьмёт страницу и отследит, в каком порядке были загружены исходники, после чего отдаст минификатору, и на выходе вы получите что хотели.
В чём неудобство? Здесь мнения могут разниться, но IMHO для сжатия файлов SenchaCmd тяжеловат. В процессе участвуют Java-приложение, nodejs и phantomjs. В принципе, для таких редких операций как минификация перед загрузкой на сервер, может и сгодится, но есть ещё нюансы. Например, Index.cshtml ему не отдашь: участки с Razor-разметкой не поймёт. Можно дать URL приложения, но если у вас используется аутентификация до прохождения которой загружается не всё приложение, то в минифицированном файле тоже будут не все исходники. А в случае с Windows-аутентификацией вообще всё плохо.
Намного проще было бы сказать: «Вот тебе папка, сам разберись, что к чему и дай мне сжатый файл». На просторах интернета полным-полно минификаторов, но среди нет тех, кто мог бы установить зависимости между исходными файлами. Попробуем это исправить.
Приступим
В стеке ASP.NET уже есть инструмент для конкатенации и минификации — Bundles. Ему нужно только немного помочь —, а именно, подсказать, в каком порядке склеивать исходники.BundleConfig.cs
public class BundleConfig
{
public static void RegisterBundles (BundleCollection bundles)
{
bundles.Add (
new ScriptBundle (»~/bundles/my-sencha-app»)
{
Orderer = // ?
}
.IncludeDirectory (»~/Scripts/my-sencha-app»,»*.js», true);
);
}
}
То, что нужно! Посмотрим на Orderer.IBundleOrderer
public interface IBundleOrderer
{
IEnumerable
Вручную в коде через Ext.require Через конфигурационное свойство класса requires Неявные (только конфигурационные свойства): При наследовании — extend При указании примесей — mixins При указании модели хранилища — model При указании представлений, моделей и хранилищ контроллера — views, models, stores При указании контроллеров приложения — controllers При автоматическом создании Viewport — autoCreateViewport: true К первому случаю претензий иметь не будем — в коде значит в коде. Остальные вполне поддаются анализу.Определимся со структурой программы. Для начала у нас есть JS-файл. Он может иметь несколько классов внутри, каждый из которых может иметь зависимости на другие классы:
SenchaFile.cs
public class SenchaFile
{
///
///
///
var extDefines = this.RootBlock.OfType
foreach (var cls in extApps.Union (extDefines)) { yield return cls; } } } Следующим шагом необходимо определить зависимости каждого класса. Для этого возьмём тот же JSParser и пройдёмся по всем случаям определения зависимостей (явным и неявным), описанным выше. Приводить код не буду, чтобы не загружать статью, но суть та же: перебираем дерево блоков в поисках нужных свойств и выбираем имена используемых классов.Теперь у нас в наличии список файлов, у каждого файла найдены описанные в нём классы, а у каждого класса — его зависимости. И нужно как-то расставить их в порядке очереди. Для этого существует так называемая топологическая сортировка. Алгоритм несложный и для интересующихся есть онлайн-демка:
SenchaOrderer.cs
public class SenchaOrderer
{
///
// Идём по его зависимостям foreach (TNode dependency in node.Dependencies) { // Если мы в этом узле не были (он белый), заходим вглубь if (dependency.Color == SenchaFile.SortColor.White) { DependencyResolve (dependency, resolved); } // А если были (серый), то всё плохо: есть циклическая зависимость else if (dependency.Color == SenchaFile.SortColor.Gray) { throw new InvalidOperationException (String.Format ( «Circular dependency detected: '{0}' → '{1}'», node.FullName? String.Empty, dependency.FullName? String.Empty) ); } }
// Но лучше, чтобы циклов не было… // При выходе из узла добавляем его в очередь, метим чёрным и больше не возвращаемся. node.Color = SenchaFile.SortColor.Black; resolved.Add (node); }
///
// Коллекции файлов с неразрешёнными и разрешёнными зависимостями
IList
TNode startNode = unresolved .Where (ef => ef.Color == SenchaFile.SortColor.White) .FirstOrDefault ();
while (startNode!= null) { DependencyResolve (startNode, resolved); startNode = unresolved .Where (ef => ef.Color == SenchaFile.SortColor.White) .FirstOrDefault (); }
return resolved; } } Вот как бы и всё. Ещё пара служебных файлов и можно пользоваться:
BundleConfig.cs
public class BundleConfig { public static void RegisterBundles (BundleCollection bundles) { bundles.Add ( new SenchaBundle (»~/bundles/my-sencha-app») .IncludeDirectory (»~/Scripts/my-sencha-app»,»*.js», true) ); } } Index.cshtml
… Итого В чём плюсы такого решения? Я думаю, очевидно: использовать стандартную функциональность, предусмотренную фреймворком ASP.NET. В чём минусы? Они тоже есть: Старт веб-приложения несколько задерживается, пока минифицируются файлы. Алгоритм чувствителен к написанию кода, например, autoCreateViewport: true он поймёт, а autoCreateViewport: !0 — уже нет (без допиливания). Приложение ExtJS или SenchaTouch необходимо создавать строго через вызов Ext.application. Такой минификатор используется у нас в нескольких проектах, один из которых имеют своеобразную структуру файлов. В основном, после его подключения, они завелись без проблем, но в том своебразном пришлось чуть-чуть подправить исходники, чтобы убрать спагетти зависимостей.Попробовать NuGet. Пакет SenchaMinify. Проект на GitHub с демками. На гитхабе также включён проект самостоятельного exe-файла для командной строки (SenchaMinify.Cmd). Так что желающие могут использовать свои любимые средства автоматизации.Буду рад конструктиву, идеям или пулл-реквестам.