我正在尝试在我的web项目中使用LESS文件,并将MVC 4捆绑功能调用到dotLess库中以将LESS转换为CSS,然后缩小结果并将其提供给浏览器。
我在ASP.NET site上找到了一个示例(标题为 LESS,CoffeeScript,SCSS,Sass Bundling。)。这给了我一个LessTransform
类看起来像这样:
public class LessTransform : IBundleTransform
{
public void Process(BundleContext context, BundleResponse response)
{
response.Content = dotless.Core.Less.Parse(response.Content);
response.ContentType = "text/css";
}
}
和我的BundleConfig
课程中的这一行:
bundles.Add(new Bundle(
"~/Content/lessTest",
new LessTransform(),
new CssMinify()).Include("~/Content/less/test.less"));
最后我在_Layout.cshtml中的<head>
中有以下行:
@Styles.Render("~/Content/lessTest")
如果我的网站处于调试模式,则会将其呈现给浏览器:
<link href="/Content/less/test.less" rel="stylesheet"/>
应用.less文件中的规则,并在该链接后显示LESS已正确转换为CSS。
但是,如果我将网站置于发布模式,则会显示出来:
<link href="/Content/less?v=lEs-HID6XUz3s2qkJ35Lvnwwq677wTaIiry6fuX8gz01" rel="stylesheet"/>
.less文件中的规则未已应用,因为链接后面的IIS会出现404错误。
所以捆绑似乎出现了问题。如何让它在发布模式下工作,或者如何找出确切的错误?
答案 0 :(得分:15)
作为accepted answer的补充,我创建了一个LessBundle类,它是StyleBundle类的Less eqivalent。
LessBundle.cs 代码为:
using System.Web.Optimization;
namespace MyProject
{
public class LessBundle : Bundle
{
public LessBundle(string virtualPath) : base(virtualPath, new IBundleTransform[] {new LessTransform(), new CssMinify()})
{
}
public LessBundle(string virtualPath, string cdnPath)
: base(virtualPath, cdnPath, new IBundleTransform[] { new LessTransform(), new CssMinify() })
{
}
}
}
用法类似于StyleBundle类,指定LESS文件而不是CSS文件。
将以下内容添加到 BundleConfig.RegisterBundles(BundleCollection)方法中:
bundles.Add(new LessBundle("~/Content/less").Include(
"~/Content/MyStyles.less"));
此方法在关闭优化时工作正常,但在启用优化时遇到了一些小问题(使用CSS资源路径)。经过一个小时的研究,我发现我有reinvented the wheel...
如果您 想要我上面描述的LessBundle功能,请查看System.Web.Optimization.Less。
可以找到NuGet包here。
答案 1 :(得分:12)
无点引擎似乎需要知道当前处理的bundle文件的路径以解析@import路径。如果您运行上面的进程代码,则当解析的.less文件导入的文件较少时,dotless.Core.Less.Parse()的结果为空字符串。
Ben Foster在这里的回答将通过首先阅读导入的文件来解决这个问题:
按如下方式更改LessTransform文件:
public 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();
var lessParser = new Parser();
ILessEngine lessEngine = CreateLessEngine(lessParser);
var content = new StringBuilder(bundle.Content.Length);
var bundleFiles = new List<FileInfo>();
foreach (var bundleFile in bundle.Files)
{
bundleFiles.Add(bundleFile);
SetCurrentFilePath(lessParser, bundleFile.FullName);
string source = File.ReadAllText(bundleFile.FullName);
content.Append(lessEngine.TransformToCss(source, bundleFile.FullName));
content.AppendLine();
bundleFiles.AddRange(GetFileDependencies(lessParser));
}
if (BundleTable.EnableOptimizations)
{
// include imports in bundle files to register cache dependencies
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);
}
/// <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 IEnumerable<FileInfo> GetFileDependencies(Parser lessParser)
{
IPathResolver pathResolver = GetPathResolver(lessParser);
foreach (var importPath in lessParser.Importer.Imports)
{
yield return new FileInfo(pathResolver.GetFullPath(importPath));
}
lessParser.Importer.Imports.Clear();
}
/// <summary>
/// Returns an <see cref="IPathResolver"/> instance used by the specified LESS lessParser.
/// </summary>
/// <param name="lessParser">The LESS parser.</param>
private IPathResolver GetPathResolver(Parser lessParser)
{
var importer = lessParser.Importer as Importer;
var fileReader = importer.FileReader as FileReader;
return fileReader.PathResolver;
}
/// <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 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))
{
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
{
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)
{
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;
}
}
答案 2 :(得分:3)
接受的答案不适用于最近对ASP.NET的更改,因此不再正确。
我已在接受的答案中修改了来源:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
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;
namespace Web.App_Start.Bundles
{
public 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();
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);
var name = context.HttpContext.Server.MapPath(bundleFile.VirtualFile.VirtualPath);
SetCurrentFilePath(lessParser, name);
using (var stream = bundleFile.VirtualFile.Open())
using (var reader = new StreamReader(stream))
{
string source = reader.ReadToEnd();
content.Append(lessEngine.TransformToCss(source, name));
content.AppendLine();
}
bundleFiles.AddRange(GetFileDependencies(lessParser));
}
if (BundleTable.EnableOptimizations)
{
// include imports in bundle files to register cache dependencies
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);
}
/// <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 IEnumerable<BundleFile> GetFileDependencies(Parser lessParser)
{
IPathResolver pathResolver = GetPathResolver(lessParser);
foreach (var importPath in lessParser.Importer.Imports)
{
yield return
new BundleFile(pathResolver.GetFullPath(importPath),
HostingEnvironment.VirtualPathProvider.GetFile(importPath));
}
lessParser.Importer.Imports.Clear();
}
/// <summary>
/// Returns an <see cref="IPathResolver"/> instance used by the specified LESS lessParser.
/// </summary>
/// <param name="lessParser">The LESS parser.</param>
private IPathResolver GetPathResolver(Parser lessParser)
{
var importer = lessParser.Importer as Importer;
var fileReader = importer.FileReader as FileReader;
return fileReader.PathResolver;
}
/// <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 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))
{
fileReader = new FileReader(new ImportedFilePathResolver(currentFilePath));
importer.FileReader = fileReader;
}
}
}
}
请注意,此代码的一个已知问题是LESS @imports必须使用其完整路径,即您必须使用@import "~/Areas/Admin/Css/global.less";
而不是@import "global.less";
。
答案 3 :(得分:2)
看起来这样有效 - 我更改了Process
方法来迭代文件集:
public void Process(BundleContext context, BundleResponse response)
{
var builder = new StringBuilder();
foreach (var fileInfo in response.Files)
{
using (var reader = fileInfo.OpenText())
{
builder.Append(dotless.Core.Less.Parse(reader.ReadToEnd()));
}
}
response.Content = builder.ToString();
response.ContentType = "text/css";
}
如果你的less文件中有任何@import
语句,这会中断,在这种情况下你需要做更多的工作,如下所示:https://gist.github.com/chrisortman/2002958
答案 4 :(得分:1)
已经有了一些很好的答案,这是我在尝试添加关注less
文件的MVC包时找到的一个非常简单的解决方案。
创建less
文件后(例如,test.less
),右键单击该文件,然后在网络编译器(get it here)选项下,选择Compile File
。
这将生成css
文件中生成的less
文件及其缩小版本。 (test.css
和test.min.css
)。
在您的捆绑包上,只需参考生成的css
文件
style = new StyleBundle("~/bundles/myLess-styles")
.Include("~/Content/css/test.css", new CssRewriteUrlTransform());
bundles.Add(style);
在您的观点中,引用该捆绑包:
@Styles.Render("~/bundles/myLess-styles")
它应该可以正常工作。