Интеграция стилей LESS в сайт на основе ASP.NET

Существует такая отличная штука LESS — надстройка над CSS, которая позволяет создавать стили для своего сайта более удобно, сохраняя при этом код в более аккуратном и поддерживаемом виде.

Поскольку изначально процессинг LESS написан на JavaScript, то актуальным остается вопрос каким образом интегрировать процессинг LESS-документов в проект ASP.NET. Здесь я рассмотрю известные мне способы.

JavaScript

Самый простой способ интеграции – использовать клиентский процессинг LESS. Этот способ также предлагают разработчики LESS на своем сайте.

Для подключения LESS на клиенте нужно выполнить два шага:

Шаг 1: Добавить ссылки на LESS-файлы

<link rel="stylesheet/less" type="text/css" href="styles.less" />

Шаг 2: Добавить ссылку на JavaScript-сценарий less.js с официального сайта:

<script src="less.js" type="text/javascript"></script>

В этом случае при загрузке страницы будет загружен JavaScript-сценарий для процессинга, файлы LESS, а также дополнительные файлы, если потребуется (ссылки на другие скрипты, изображения и т.д.).

Плюсы данного подхода:

  • Процессинг "из первых рук" – JS-библиотека поставляется разработчиками LESS;
  • В процесс сборки не нужно встраивать дополнительных механизмов;
  • Если вы работаете в команде, то данный подход не требует установки дополнительных инструментов в среде разработчика, что значительно всё упрощает;
  • Можно использовать дополнительные возможности клиентской библиотеки и управлять процессингом LESS на клиенте (изменять переменные, переключать стили и т.д.)

Минусы данного подхода:

  • На клиента тянется много лишнего – LESS-файлы, дополнительные ресурсы и изображения и т.д. Вместо этого клиент должен получать готовый результат в виде минифицированного CSS. С точки зрения клиентской оптимизации этот момент ставит крест на том, чтобы использовать этот метод в живых проектах.
  • Процессинг происходит на клиенте, а это тратит ресурсы клиентской машины. Конечно все зависит от объема и сложности LESS-документа и в большинстве случаев клиент этого даже не почуствует, но все же такие вещи лучше делать на сервере.
  • Сложно посмотреть результирующий CSS. Можно открыть CSS в инструментах разработчика в браузере и исследовать его там, но лично мне приятнее работать с CSS в среде разработки, в моем случае – в Visual Studio. Как это сделать при данном подходе мне неизвестно.

Выводы — данный способ подходит разве что для процесса разработки, когда LESS-код постоянно изменяется. На живый проектах использование данного способа весьма сомнительно. В моем случае минусы перекрывают все плюсы, поэтому я в своей работе не использую данный способ вообще.

Web Essentials 2012

Существует такое прекрасное расширение для Visual Studio - Web Essentials 2012. Это расширение добавляет много различных полезных вещей для веб-разработчика. Если вы не знакомы с этим расширением, очень рекомендую его посмотреть.

В контексте текущей темы, это расширение полезно с точки зрения работы с LESS. Оно может генерировать CSS-стили при сохранении LESS-файлов и при сборке проекта. Для этого после установки расширения нужно открыть окно настроек: ToolsOptionsWeb EssentialsLESS и задать соответствующие настройки.

Теперь можно добавить LESS-файл в проект и, если вы задали настройку Show preview window, то генерация CSS будет происходить “на лету”:

После генерации CSS-файлы также доступны в проекте:

В целом, в таком режиме работать очень удобно – сразу видишь генерируемый код.

Но пока этот способ у меня, к сожалению, не прижился. Не уверен почему так происходит, но Web Essentials часто отказывается обрабатывать, казалось бы, корректный LESS-документ. Например, если в предыдущем примере переместить переменную в начало документа, то Web Essentials поругается на такой документ:

Расчитывать на то, что будут обрабатываться какие-то уже готовые LESS-документы (например, LESS из Twitter Bootstrap) и вовсе не приходится, увы.

Плюсы данного подхода:

  • CSS генерируется “на лету”, так, что можно сразу же видеть результат в процессе написания LESS-кода;
  • Web Essentials имеет встроенные подсказки при написании LESS-кода. Например, отслеживаются все ваши mixin-ы и предлагаются в качестве подсказки при написании стилей;
  • Быстрый – даже при больших документах CSS генерируется действительно быстро, что лично для меня стало сюрпризом;
  • Разом получаем полный и минифицированный вариант CSS.

Минусы данного подхода:

  • Самый главный минус описан выше – ошибки при генерации CSS.
  • Нет возможности (или это я просто её не нашел) как генерировать CSS не для всех LESS-файлов, а выборочно. Например, генерировать пустой CSS для файла, где только описываются переменные бессмысленно. Более того, некоторые библиотеки написаны так, что сам по себе LESS-файл просто невозможно процессить, а процессится он только как часть другого.
  • Если вы работаете в команде, то вам придется всех "заставить" установить Web Essentials, чтобы совместно использовать LESS в проекте. Если учесть, что Web Essentials иногда все-таки подглючивает, а также существенно замедляет работу Visual Studio при редактировании больших CSS, то это тоже может стать проблемой.

Выводы – способ очень интересный и перспективный, но пока сырой. В моем случае использовать его не получается из-за постоянных ошибок процессинга.

Библиотека dotless

Библиотека dotless – это просто находка для .NET-окружения. Она предоставляет множество различных способов генерировать CSS на основе LESS в среде .NET. При этом существует вариант генерации CSS в момент выполнения приложения (есть готовый ASP.NET HTTP-handler), прямо из .NET-кода, а также при помощи консольной утилиты.

Чтобы не тратить время на генерацию CSS в момент исполнения, я предпочитаю получать CSS в момент сборки проекта. Поэтому в моем случае работает следующее решение. Сразу скажу, что в данном случае при создании проекта придется немного поработать руками – а именно подключить процессинг LESS-файлов через консольную утилиту.

Шаг 1: Создаем проект и добавляем в него LESS-файлы.

Шаг 2: Подключаем dotless. Можно загрузить его с официального сайта, либо подключить через nuget.

Шаг 3: Генерируем CSS при помощи dotless в момент сборки проекта.

Для генерации CSS нам придется немного поработать руками. Для этого открываем файл проекта (.csproj) и в самом конце файла, перед закрытием элемента <Project> добавляем новый Target и запускаем его после (можно перед) сборкой:

<Target Name="CompileDotlessCss" AfterTargets="AfterBuild">
  <ItemGroup>
    <__LessFiles Include="**\*.less"/>
  </ItemGroup>
  <Exec Command=""..\packages\dotless.1.3.1.0\tool\dotless.compiler.exe" -m -a "%(__LessFiles.FullPath)" "$([System.String]::Copy('%(__LessFiles.FullPath)').Replace('.less','.min.css'))"" />
  <Exec Command=""..\packages\dotless.1.3.1.0\tool\dotless.compiler.exe" -a "%(__LessFiles.FullPath)" "$([System.String]::Copy('%(__LessFiles.FullPath)').Replace('.less','.css'))"" />
</Target>

Этот небольшой скрипт запускает консольную утилиту dotless при сборке проекта и генерирует файлы .css и .min.css. Откроем проект, запустим сборку, видим появившиеся файлы CSS:

Очевидно, что в такой иерархии хранить генерируемые CSS очень неудобно – даже при небольшом их количестве в папке будет хаос. Поэтому еще немного поработаем руками.

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

Загружаем проект, теперь видим, что всё по полочкам:

Совершенству нет предела – давайте немного улучшим этот способ так, чтобы CSS генерировался не для всех LESS файлов, а выборочно. Сделаем так, чтобы процессинг происходил только для тех LESS-файлов, для которых уже существует CSS-файл. Тогда, чтобы добавить процессинг очередного LESS-файла, просто создадим для него пустой CSS-файл и при сборке будет осуществляться процессинг.

Для этого идем в файл проекта и изменяем скрипт следующим образом:

<Target Name="CompileDotlessCss" AfterTargets="AfterBuild">
  <ItemGroup>
    <__LessFiles Include="**\*.less" />
    <__LessFilesToProcessMin Include="%(__LessFiles.Identity)" Condition="Exists('%(__LessFiles.RootDir)%(__LessFiles.Directory)%(__LessFiles.Filename).min.css')" />
    <__LessFilesToProcessDev Include="%(__LessFiles.Identity)" Condition="Exists('%(__LessFiles.RootDir)%(__LessFiles.Directory)%(__LessFiles.Filename).css')" />
  </ItemGroup>
  <Exec Command=""..\packages\dotless.1.3.1.0\tool\dotless.compiler.exe" -m -a "%(__LessFilesToProcessMin.FullPath)" "$([System.String]::Copy('%(__LessFilesToProcessMin.FullPath)').Replace('.less','.min.css'))"" />
  <Exec Command=""..\packages\dotless.1.3.1.0\tool\dotless.compiler.exe" -a "%(__LessFilesToProcessDev.FullPath)" "$([System.String]::Copy('%(__LessFilesToProcessDev.FullPath)').Replace('.less','.css'))"" />
</Target>

Теперь генерация CSS выполняется только для тех LESS-файлов, для которых CSS-файлы существуют. По мне – очень неплохой компромис.

Плюсы данного подхода:

  • LESS процессится во время сборки – не тратится время и ресурсы в момент исполнения кода;
  • Есть возможность посмотреть результирующий CSS в процессе разработки;
  • При сборке получаем полный и минифицированный вариант;
  • Можем управлять тем, какие файлы нужно процессить, а какие – нет;
  • Несмотря на то, что в процессе сборки запускается консольная утилита, в случае ошибок они корректно отображаются в окне Error List в Visual Studio;
  • Если вы работаете в команде, то никаких дополнительных инстурментов на компьютер разработчика устанавливать не нужно – открывайте проект в Visual Studio, собирайте код, получайте результат.

Минусы данного подхода:

  • Нужно руками допиливать файл проекта при его создании, а также при добавлении новых LESS-файлов (если требуется их процессинг в CSS);
  • При изменении LESS-файлов требуется пересборка проекта.

Выводы — для меня это на данный момент рабочий вариант того, как процессить LESS в ASP.NET проектах. Приходится немного править проект руками, однако, это компенсируется дальнейшими удобствами.

Пример проекта с таким видом процессинга на github

ASP.NET Bundling and Minification

Не так давно в ASP.NET появилось пространство имен System.Web.Optimization, которое включает в себя инструменты для клиентской оптимизации. Одним из таких механизмов является возможность создания пакетов клиентских файлов (bundle).

Идея заключается в том, что в момент исполнения приложения ASP.NET создает пакет, который объединяет несколько JS или CSS файлов, при этом минифицируя их и/или выполняя еще какой-либо серверный процессинг. Собственно, идея данного способа заключается в том, что если у вас уже используются Bundle-ы для оптимизации, то вы можете встроить в эту цепочку и процессинг LESS, используя все тот же dotless, например. Как это сделать:

Шаг 1: Подключаем dotless к проекту (описывалось ранее).

Шаг 2: Создаем класс для LESS-трансформаций:

using System.Web.Optimization;

public class LessTransform : IBundleTransform
{
    public void Process(BundleContext context, BundleResponse response)
    {
        response.Content = dotless.Core.Less.Parse(response.Content);
        response.ContentType = "text/css";
    }
}

Шаг 3: Добавляем этот класс в общий список трансформаций при определении пакета:

var stylesBundle = new Bundle("~/bundles/styles").IncludeDirectory("~/Content/Styles/", "*.less");
stylesBundle.Transforms.Add(new LessTransform());
// ...
stylesBundle.Add(lessBundle);

Теперь этот пакет можно использовать как и все остальные пакеты. Про Bundle-ы и оптимизацию можно почтитать еще тут.

Плюсы данного подхода:

  • Уже практически готовый инструмент для сборки пакетов;
  • При сборке получаем полный (по желанию) и минифицированный вариант;
  • Можем управлять тем, какие файлы нужно процессить, а какие – нет;
  • Если вы работаете в команде, то никаких дополнительных инстурментов на компьютер разработчика устанавливать не нужно – открывайте проект в Visual Studio, собирайте код, получайте результат.

Минусы данного подхода:

  • Нет (по кр.мере я не нашел) простого способа посмотреть результирующий CSS – можно посмотреть dev-тулами в браузере, но это неудобно;
  • Процессинг идет в рантайме, что не всегда хорошо

Выводы — для тех у кого итак используется Bundles для организации клиентского кода этот способ вполне может подойти. Из-за описаных выше минусов мне этот способ не очень нравится, поэтому я отдаю предпочтение процессингу LESS-файлов на этапе сборке при помощи dotless.