Stop Bundle Transformer преобразует относительные пути в LESS
Я использую Bundle Transformer для LESS-компиляции в проекте MVC5. Мой пакет LESS содержит один файл main.less, который импортирует другие файлы, расположенные в подпапках. Некоторые из файлов содержат ссылки на файлы изображений, как это - это в файле /css/structure/header.less
:
.site__logo {
background: url('../img/logo.png') no-repeat;
// ...
}
В скомпилированном пакете (/css/lessBundle
) это становится:
background: url('/css/img/logo.png') no-repeat;
Я хочу, чтобы относительный путь в файле.less сохранялся при его объединении, чтобы он правильно указывал на /img/logo.png
не /css/img/logo.png
, Я думаю, что Bundle Transformer отвечает за преобразование относительных путей - документация содержит этот параграф, но не вдавается в подробности:
Вы также должны понимать, что когда вы подключаете экземпляры классов CssTransformer и JsTransformer, вы подключаете набор преобразований (выбор между отладочной и предварительно минимизированной версиями файлов, код перевода с промежуточных языков, минимизация кода времени выполнения, преобразование относительного пути к абсолюту (только для CSS-кода) и комбинирование кода). Набор преобразований зависит от того, какие модули Bundle Transformer вы установили и какие настройки вы указали в файле Web.config.
Вот мой BundleConfig:
public class BundleConfig
{
public const string LessBundlePath = "~/css/lessBundle";
public static void RegisterBundles(BundleCollection bundles)
{
var nullBuilder = new NullBuilder();
var cssTransformer = new CssTransformer();
var nullOrderer = new NullOrderer();
// Skip JS-related stuff
var lessBundle = new Bundle(LessBundlePath)
.Include("~/css/main.less");
lessBundle.Builder = nullBuilder;
lessBundle.Transforms.Add(cssTransformer);
lessBundle.Orderer = nullOrderer;
bundles.Add(lessBundle);
BundleTable.EnableOptimizations = true;
}
}
/css/main.less
в основном это просто куча импорта:
@import "bootstrap/bootstrap";
@import "structure/header";
// etc.
html, body {
height: 100%;
}
Я попытался поиграть с этим параметром в web.config, но безрезультатно:
<css defaultMinifier="NullMinifier" disableNativeCssRelativePathTransformation="true">
Если возможно, я бы не стал изменять пути к файлам в файлах.less, так как они предоставлены третьей стороной, и все отлично работает на их сервере интеграции (который не использует.NET). Есть ли что-нибудь еще, что я могу сделать?
2 ответа
В модулях BundleTransformer.Less и http://www.nuget.org/packages/BundleTransformer.SassAndScss/ нельзя отключить преобразование относительных путей в абсолютные, так как это может нарушать ссылки на изображения при использовании @import
директивы.
Получить /img/logo.png
вместо /css/img/logo.png
вам просто нужно правильно указать относительный путь в исходном коде (../../img/logo.png
вместо ../img/logo.png
).
Я решил это некоторое время назад, до того, как в MVC появилась какая-либо поддержка файлов LESS. Только что проверил, чтобы проверить, и этот класс будет правильно применять текущую папку @imported .less
при преобразовании его в CSS.
- Это использует библиотеку без точек
BundleHelper.cs:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Hosting;
using System.Web.Optimization;
using dotless.Core;
using dotless.Core.Abstractions;
using dotless.Core.Importers;
using dotless.Core.Input;
using dotless.Core.Loggers;
using dotless.Core.Parser;
public static class BundleHelper
{
internal class LessBundle : StyleBundle
{
public LessBundle(string virtualPath)
: base(virtualPath)
{
// inject LessTransform to the beginning of the Transforms
Transforms.Insert(0, new LessTransform());
}
public LessBundle(string virtualPath, string cdnPath)
: base(virtualPath, cdnPath)
{
// inject LessTransform to the beginning of the Transforms
Transforms.Insert(0, new LessTransform());
}
}
// TODO: speed improvement - consider not parsing any CSS files that are not LESS
// TODO: verify that this still works for nested @imports
internal class LessTransform : IBundleTransform
{
public void Process(BundleContext context, BundleResponse bundle)
{
if (context == null)
throw new ArgumentNullException("context");
if (bundle == null)
throw new ArgumentNullException("bundle");
context.HttpContext.Response.Cache.SetLastModifiedFromFileDependencies();
// initialize variables
var lessParser = new Parser();
ILessEngine lessEngine = CreateLessEngine(lessParser);
var content = new StringBuilder(bundle.Content.Length);
var bundleFiles = new List<BundleFile>();
foreach (var bundleFile in bundle.Files)
{
bundleFiles.Add(bundleFile);
// set the current file path for all imports to use as the working directory
SetCurrentFilePath(lessParser, bundleFile.IncludedVirtualPath);
using (var reader = new StreamReader(bundleFile.VirtualFile.Open()))
{
// read in the LESS file
string source = reader.ReadToEnd();
// parse the LESS to CSS
content.Append(lessEngine.TransformToCss(source, bundleFile.IncludedVirtualPath));
content.AppendLine();
// add all import files to the list of bundleFiles
////bundleFiles.AddRange(GetFileDependencies(lessParser));
}
}
// include imports in bundle files to register cache dependencies
if (BundleTable.EnableOptimizations)
bundle.Files = bundleFiles.Distinct();
bundle.ContentType = "text/css";
bundle.Content = content.ToString();
}
/// <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, true, false);
}
// TODO: this is not currently working and may be unnecessary.
/// <summary>
/// Gets the file dependencies (@imports) of the LESS file being parsed.
/// </summary>
/// <param name="lessParser">The LESS parser.</param>
/// <returns>An array of file references to the dependent file references.</returns>
private static IEnumerable<BundleFile> GetFileDependencies(Parser lessParser)
{
foreach (var importPath in lessParser.Importer.Imports)
{
var fileName = VirtualPathUtility.Combine(lessParser.FileName, importPath);
var file = BundleTable.VirtualPathProvider.GetFile("~/Content/test2.less");
yield return new BundleFile(fileName, file);
}
lessParser.Importer.Imports.Clear();
}
/// <summary>
/// Informs the LESS parser about the path to the currently processed file.
/// This is done by using a 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 static void SetCurrentFilePath(Parser lessParser, string currentFilePath)
{
var importer = lessParser.Importer as Importer;
if (importer == null)
throw new InvalidOperationException("Unexpected dotless importer type.");
var fileReader = importer.FileReader as FileReader;
if (fileReader != null && fileReader.PathResolver is ImportedFilePathResolver)
return;
fileReader = new FileReader(new ImportedFilePathResolver(currentFilePath));
importer.FileReader = fileReader;
}
}
public class ImportedFilePathResolver : IPathResolver
{
private string _currentFileDirectory;
private string _currentFilePath;
public ImportedFilePathResolver(string currentFilePath)
{
if (String.IsNullOrEmpty(currentFilePath))
throw new ArgumentNullException("currentFilePath");
CurrentFilePath = currentFilePath;
}
/// <summary>
/// Gets or sets the path to the currently processed file.
/// </summary>
public string CurrentFilePath
{
get
{
return _currentFilePath;
}
set
{
var path = GetFullPath(value);
_currentFilePath = path;
_currentFileDirectory = Path.GetDirectoryName(path);
}
}
/// <summary>
/// Returns the absolute path for the specified imported file path.
/// </summary>
/// <param name="filePath">The imported file path.</param>
public string GetFullPath(string filePath)
{
if (filePath.StartsWith("~"))
filePath = VirtualPathUtility.ToAbsolute(filePath);
if (filePath.StartsWith("/"))
filePath = HostingEnvironment.MapPath(filePath);
else if (!Path.IsPathRooted(filePath))
filePath = Path.GetFullPath(Path.Combine(_currentFileDirectory, filePath));
return filePath;
}
}
}
Пример использования:
App_Start / BundleConfig.cs:
public class BundleConfig { public static void RegisterBundles(BundleCollection bundles) { bundles.Add(new BundleHelper.LessBundle("~/Content/css").Include( "~/Content/normalStuff.css", "~/Content/template.less", "~/Content/site.less")); } }
Содержимое / template.less:
@import "themes/blue/test"; body { background: url('../img/logo.png') no-repeat; } footer { background: url('img/logo1.png') no-repeat; }
Содержание / темы / синий / test.less:
.myTheme { background: url('../img/logo2.png') no-repeat; } .myTheme2 { background: url('img/logo3.png') no-repeat; }
Используя этот пакет, вы получите следующий CSS, который должен быть именно тем, что вы ищете:
местоположение: example.com/path/Content/test.less
.myTheme { background: url('themes/img/logo2.png') no-repeat; } .myTheme2 { background: url('themes/blue/img/logo3.png') no-repeat; } body { background: url('../img/logo.png') no-repeat; } footer { background: url('img/logo1.png') no-repeat; }
ПРИМЕЧАНИЕ: основываясь на моих старых комментариях, я не уверен, насколько хорошо он будет обрабатывать вложенные @imports (импорт внутри test.less
еще в другую папку)
Дайте мне знать, если это не сработает для вас.