【问题标题】:Stop Bundle Transformer converting relative paths in LESS停止 Bundle Transformer 在 LESS 中转换相对路径
【发布时间】:2014-07-27 16:35:10
【问题描述】:

我在 MVC5 项目中使用 Bundle Transformer 进行 LESS 编译。我的 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 负责转换相对路径——documentation 有这一段,但没有进一步详细说明:

您还需要了解,当您插入 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)上一切正常。还有什么我可以做的吗?

【问题讨论】:

    标签: css less asp.net-mvc-5 bundletransformer


    【解决方案1】:

    BundleTransformer.LessBundleTransformer.SassAndScss 模块中不能禁用将相对路径转换为绝对路径,因为在使用@import 指令时会破坏对图像的引用。

    要获取/img/logo.png 而不是/css/img/logo.png,您只需在源代码中正确指定相对路径(../../img/logo.png 而不是../img/logo.png)。

    【讨论】:

      【解决方案2】:

      在 MVC 对 LESS 文件有任何形式的支持之前,我解决了这个问题。刚刚测试验证,这个类在转换为 CSS 时会正确应用 @imported .less 的当前文件夹。

      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;
        }
        

      注意:基于我的旧 cmets,我不确定它将如何处理嵌套的 @imports(将 test.less 内的导入到另一个文件夹)

      如果这对你不起作用,请告诉我。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-04-27
        • 2017-08-01
        • 2011-11-07
        • 2011-05-02
        相关资源
        最近更新 更多