MVC4 Less Bundle @import Directory
Я пытаюсь использовать связывание MVC4 для группировки некоторых из моих меньших файлов, но похоже, что путь импорта, который я использую, отключен. Моя структура каталогов:
static/
less/
mixins.less
admin/
user.less
В user.less я пытаюсь импортировать mixins.less, используя это:
@import "../mixins.less";
Это использовалось для меня раньше, когда я использовал chirpy с бесчисленным количеством, но теперь я заметил, что ELMAH злится на меня, говоря это:
System.IO.FileNotFoundException:
You are importing a file ending in .less that cannot be found.
File name: '../mixins.less'
Я должен использовать другой @import
с MVC4?
Дополнительная информация
Здесь менее класс и код global.asax.cs, который я использую, чтобы это сделать:
LessMinify.cs
...
public class LessMinify : CssMinify
{
public LessMinify() {}
public override void Process(BundleContext context, BundleResponse response)
{
response.Content = Less.Parse(response.Content);
base.Process(context, response);
}
}
...
Global.asax.cs
...
DynamicFolderBundle lessFB =
new DynamicFolderBundle("less", new LessMinify(), "*.less");
BundleTable.Bundles.Add(lessFB);
Bundle AdminLess = new Bundle("~/AdminLessBundle", new LessMinify());
...
AdminLess.AddFile("~/static/less/admin/user.less");
BundleTable.Bundles.Add(AdminLess);
...
Ответы
Ответ 1
Я написал короткое сообщение в блоге о Использование LESS CSS с веб-оптимизацией MVC4.
В основном это сводится к использованию пакета BundleTransformer.Less Nuget и изменения вашего BundleConfig.cs.
Протестировано с помощью бутстрапа.
РЕДАКТИРОВАТЬ: Следует упомянуть причину, по которой я это говорю, я также столкнулся с проблемой структуры каталогов @import, и эта библиотека обрабатывает ее правильно.
Ответ 2
В GitHub Gist есть код, который хорошо работает с @import и dotLess: https://gist.github.com/2002958
Я протестировал его с помощью Twitter Bootstrap, и он работает хорошо.
ImportedFilePathResolver.cs
public class ImportedFilePathResolver : IPathResolver
{
private string currentFileDirectory;
private string currentFilePath;
/// <summary>
/// Initializes a new instance of the <see cref="ImportedFilePathResolver"/> class.
/// </summary>
/// <param name="currentFilePath">The path to the currently processed file.</param>
public ImportedFilePathResolver(string currentFilePath)
{
CurrentFilePath = currentFilePath;
}
/// <summary>
/// Gets or sets the path to the currently processed file.
/// </summary>
public string CurrentFilePath
{
get { return currentFilePath; }
set
{
currentFilePath = value;
currentFileDirectory = Path.GetDirectoryName(value);
}
}
/// <summary>
/// Returns the absolute path for the specified improted file path.
/// </summary>
/// <param name="filePath">The imported file path.</param>
public string GetFullPath(string filePath)
{
filePath = filePath.Replace('\\', '/').Trim();
if(filePath.StartsWith("~"))
{
filePath = VirtualPathUtility.ToAbsolute(filePath);
}
if(filePath.StartsWith("/"))
{
filePath = HostingEnvironment.MapPath(filePath);
}
else if(!Path.IsPathRooted(filePath))
{
filePath = Path.Combine(currentFileDirectory, filePath);
}
return filePath;
}
}
LessMinify.cs
public class LessMinify : IBundleTransform
{
/// <summary>
/// Processes the specified bundle of LESS files.
/// </summary>
/// <param name="bundle">The LESS bundle.</param>
public void Process(BundleContext context, BundleResponse bundle)
{
if(bundle == null)
{
throw new ArgumentNullException("bundle");
}
context.HttpContext.Response.Cache.SetLastModifiedFromFileDependencies();
var lessParser = new Parser();
ILessEngine lessEngine = CreateLessEngine(lessParser);
var content = new StringBuilder(bundle.Content.Length);
foreach(FileInfo file in bundle.Files)
{
SetCurrentFilePath(lessParser, file.FullName);
string source = File.ReadAllText(file.FullName);
content.Append(lessEngine.TransformToCss(source, file.FullName));
content.AppendLine();
AddFileDependencies(lessParser);
}
bundle.Content = content.ToString();
bundle.ContentType = "text/css";
//base.Process(context, bundle);
}
/// <summary>
/// Creates an instance of LESS engine.
/// </summary>
/// <param name="lessParser">The LESS parser.</param>
private ILessEngine CreateLessEngine(Parser lessParser)
{
var logger = new AspNetTraceLogger(LogLevel.Debug, new Http());
return new LessEngine(lessParser, logger, false);
}
/// <summary>
/// Adds imported files to the collection of files on which the current response is dependent.
/// </summary>
/// <param name="lessParser">The LESS parser.</param>
private void AddFileDependencies(Parser lessParser)
{
IPathResolver pathResolver = GetPathResolver(lessParser);
foreach(string importedFilePath in lessParser.Importer.Imports)
{
string fullPath = pathResolver.GetFullPath(importedFilePath);
HttpContext.Current.Response.AddFileDependency(fullPath);
}
lessParser.Importer.Imports.Clear();
}
/// <summary>
/// Returns an <see cref="IPathResolver"/> instance used by the specified LESS lessParser.
/// </summary>
/// <param name="lessParser">The LESS prser.</param>
private IPathResolver GetPathResolver(Parser lessParser)
{
var importer = lessParser.Importer as Importer;
if(importer != null)
{
var fileReader = importer.FileReader as FileReader;
if(fileReader != null)
{
return fileReader.PathResolver;
}
}
return null;
}
/// <summary>
/// Informs the LESS parser about the path to the currently processed file.
/// This is done by using custom <see cref="IPathResolver"/> implementation.
/// </summary>
/// <param name="lessParser">The LESS parser.</param>
/// <param name="currentFilePath">The path to the currently processed file.</param>
private void SetCurrentFilePath(Parser lessParser, string currentFilePath)
{
var importer = lessParser.Importer as Importer;
if(importer != null)
{
var fileReader = importer.FileReader as FileReader;
if(fileReader == null)
{
importer.FileReader = fileReader = new FileReader();
}
var pathResolver = fileReader.PathResolver as ImportedFilePathResolver;
if(pathResolver != null)
{
pathResolver.CurrentFilePath = currentFilePath;
}
else
{
fileReader.PathResolver = new ImportedFilePathResolver(currentFilePath);
}
}
else
{
throw new InvalidOperationException("Unexpected importer type on dotless parser");
}
}
}
Ответ 3
Добавление к ответу Бен-Кулла:
Я знаю, что это "должно быть комментарием к сообщению Бена Кулла", но добавляет немного лишнего, что было бы невозможно добавить в комментарий. Так что проголосуйте, если хотите. Или закройте меня.
Блог блога Бена делает все это, за исключением того, что он не указывает минимизацию.
Итак, установите пакет BundleTransformer.Less, как предлагает Бен, а затем, если вы хотите минимизировать свой css, выполните следующие действия (в ~/App_Start/BundleConfig.cs):
var cssTransformer = new CssTransformer();
var jsTransformer = new JsTransformer();
var nullOrderer = new NullOrderer();
var css = new Bundle("~/bundles/css")
.Include("~/Content/site.less");
css.Transforms.Add(cssTransformer);
css.Transforms.Add(new CssMinify());
css.Orderer = nullOrderer;
bundles.Add(css);
Добавленная строка:
css.Transforms.Add(new CssMinify());
Где CssMinify
находится в System.Web.Optimizations
Мне так приятно избавиться от проблемы @import, и полученный файл с расширением .less не нашел, что мне все равно, кто меня голосует.
Если, напротив, вы чувствуете желание голосовать за этот ответ, пожалуйста, дайте свой голос Бену.
Итак, там.
Ответ 4
Работа, которую я обнаружил, была очень полезной, заключалась в том, чтобы установить каталог перед запуском Less.Parse внутри LessMinify.Process(). Вот как я это сделал:
public class LessTransform : IBundleTransform
{
private string _path;
public LessTransform(string path)
{
_path = path;
}
public void Process(BundleContext context, BundleResponse response)
{
Directory.SetCurrentDirectory(_path);
response.Content = Less.Parse(response.Content);
response.ContentType = "text/css";
}
}
Затем прохождение в пути при создании объекта с меньшим преобразованием следующим образом:
lessBundle.Transforms.Add(
new LessTransform(HttpRuntime.AppDomainAppPath + "/Content/Less")
);
Надеюсь, что это поможет.
Ответ 5
Проблема заключается в том, что DynamicFolderBundle считывает все содержимое файлов и передает объединенное содержимое в LessMinify.
Таким образом, любые @imports не имеют ссылки на местоположение, из которого был получен файл.
Чтобы решить эту проблему, мне пришлось поместить все "менее" файлы в одно место.
Затем вы должны понимать, что порядок файлов становится важным.
Поэтому я начал переименовывать файл с номером (например: "0 CONSTANTS.less", "1 MIXIN.less", что означает, что они загружаются вверху комбинированного вывода, прежде чем они войдут в LessMinify.
если вы отлаживаете свой LessMinify и просматриваете ответ. Контент вы увидите объединенный меньше выходных данных.
Надеюсь, что это поможет
Ответ 6
Здесь простейшая версия кода для обработки этого я мог бы придумать:
public class LessTransform : IBundleTransform
{
public void Process(BundleContext context, BundleResponse bundle)
{
var pathResolver = new ImportedFilePathResolver(context.HttpContext.Server);
var lessParser = new Parser();
var lessEngine = new LessEngine(lessParser);
(lessParser.Importer as Importer).FileReader = new FileReader(pathResolver);
var content = new StringBuilder(bundle.Content.Length);
foreach (var bundleFile in bundle.Files)
{
pathResolver.SetCurrentDirectory(bundleFile.IncludedVirtualPath);
content.Append(lessEngine.TransformToCss((new StreamReader(bundleFile.VirtualFile.Open())).ReadToEnd(), bundleFile.IncludedVirtualPath));
content.AppendLine();
}
bundle.ContentType = "text/css";
bundle.Content = content.ToString();
}
}
public class ImportedFilePathResolver : IPathResolver
{
private HttpServerUtilityBase server { get; set; }
private string currentDirectory { get; set; }
public ImportedFilePathResolver(HttpServerUtilityBase server)
{
this.server = server;
}
public void SetCurrentDirectory(string fileLocation)
{
currentDirectory = Path.GetDirectoryName(fileLocation);
}
public string GetFullPath(string filePath)
{
var baseDirectory = server.MapPath(currentDirectory);
return Path.GetFullPath(Path.Combine(baseDirectory, filePath));
}
}
Ответ 7
Вот что я сделал:
Добавлен модуль Twitter Bootstrap Nuget.
Добавлено это в мой файл _Layout.cshtml:
<link href="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Content/twitterbootstrap/less")" rel="stylesheet" type="text/css" />
Обратите внимание, что я переименовал свою "меньшую" папку в twitterbootstrap, чтобы продемонстрировать, что я мог
Переместил все менее файлы в подпапку под названием "import" кроме bootstrap.less и (для гибкого дизайна) отзывчивый .less.
~/Content/twitterbootstrap/imports
Добавлена конфигурация в файле web.config:
<add key="TwitterBootstrapLessImportsFolder" value="imports" />
Созданы два класса (небольшая модификация класса выше):
using System.Configuration;
using System.IO;
using System.Web.Optimization;
using dotless.Core;
using dotless.Core.configuration;
using dotless.Core.Input;
namespace TwitterBootstrapLessMinify
{
public class TwitterBootstrapLessMinify : CssMinify
{
public static string BundlePath { get; private set; }
public override void Process(BundleContext context, BundleResponse response)
{
setBasePath(context);
var config = new DotlessConfiguration(dotless.Core.configuration.DotlessConfiguration.GetDefault());
config.LessSource = typeof(TwitterBootstrapLessMinifyBundleFileReader);
response.Content = Less.Parse(response.Content, config);
base.Process(context, response);
}
private void setBasePath(BundleContext context)
{
var importsFolder = ConfigurationManager.AppSettings["TwitterBootstrapLessImportsFolder"] ?? "imports";
var path = context.BundleVirtualPath;
path = path.Remove(path.LastIndexOf("/") + 1);
BundlePath = context.HttpContext.Server.MapPath(path + importsFolder + "/");
}
}
public class TwitterBootstrapLessMinifyBundleFileReader : IFileReader
{
public IPathResolver PathResolver { get; set; }
private string basePath;
public TwitterBootstrapLessMinifyBundleFileReader() : this(new RelativePathResolver())
{
}
public TwitterBootstrapLessMinifyBundleFileReader(IPathResolver pathResolver)
{
PathResolver = pathResolver;
basePath = TwitterBootstrapLessMinify.BundlePath;
}
public bool DoesFileExist(string fileName)
{
fileName = PathResolver.GetFullPath(basePath + fileName);
return File.Exists(fileName);
}
public string GetFileContents(string fileName)
{
fileName = PathResolver.GetFullPath(basePath + fileName);
return File.ReadAllText(fileName);
}
}
}
Моя реализация IFileReader рассматривает статический член BundlePath класса TwitterBootstrapLessMinify. Это позволяет нам вводить базовый путь для импорта. Мне хотелось бы использовать другой подход (путем предоставления экземпляра моего класса, но я не мог).
Наконец, я добавил следующие строки в Global.asax:
BundleTable.Bundles.EnableDefaultBundles();
var lessFB = new DynamicFolderBundle("less", new TwitterBootstrapLessMinify(), "*.less", false);
BundleTable.Bundles.Add(lessFB);
Это эффективно решает проблему импорта, не зная, куда импортировать.
Ответ 8
По состоянию на февраль 2013 года:
Идея Майкла Бэйрда была превосходно решена из-за ответа "BundleTransformer.Less Nuget Package", упомянутого в сообщении Бена Кулла. Похожие ответы:
http://blog.cdeutsch.com/2012/08/using-less-and-twitter-bootstrap-in.html
Cdeutsch blog and awrigley post добавление minification - это хорошо, но, видимо, теперь не правильный подход.
Кто-то другой с тем же решением получил ответы от автора BundleTransformer:
http://geekswithblogs.net/ToStringTheory/archive/2012/11/30/who-could-ask-for-more-with-less-css-part-2.aspx. См. Комментарии внизу.
В заключение используйте BundleTransformer.MicrosoftAjax вместо встроенного встроенного minifiers.
например
css.Transforms.Add(новый CssMinify());
заменены на
css.Transforms.Add(новый BundleTransformer.MicrosoftAjax());
Ответ 9
Далее, используя RockResolve ниже, чтобы использовать MicrosoftAjax minifier, ссылайтесь на него как на CSS по умолчанию minifier по умолчанию в web.config, а не на его передачу в качестве аргумента.
Из https://bundletransformer.codeplex.com/wikipage/?title=Bundle%20Transformer%201.7.0%20Beta%201#BundleTransformerMicrosoftAjax_Chapter
Чтобы MicrosoftAjaxCssMinifier по умолчанию CSS- minifier и MicrosoftAjaxJsMinifier по умолчанию JS- minifier, вам необходимо внести изменения в файл Web.config. В атрибуте defaultMinifier элемента \configuration\bundleTransformer\core\css должно быть установлено значение, равное MicrosoftAjaxCssMinifier, и в том же атрибуте элемента \configuration\bundleTransformer\core\js - MicrosoftAjaxJsMinifier.
Ответ 10
Проверьте мою библиотеку https://www.nuget.org/packages/LessMVCFour
Надеюсь, что это поможет.
Ответ 11
Я столкнулся с одной и той же проблемой, увидев одно и то же сообщение об ошибке. Поиск решения в Интернете привел меня сюда. Моя проблема заключалась в следующем:
В меньшем файле у меня в какой-то момент был неправильный стиль, который давал мне предупреждение. Чем меньше файлов невозможно разобрать. Я избавился от сообщения об ошибке, удалив неправильную строку.
Я надеюсь, что это поможет кому-то.