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 еще в другую папку)

Дайте мне знать, если это не сработает для вас.

Другие вопросы по тегам