MVC4 Less Bundle @import Directory - PullRequest
       71

MVC4 Less Bundle @import Directory

51 голосов
/ 07 марта 2012

Я пытаюсь использовать связывание MVC4 для группировки некоторых из моих файлов поменьше, но похоже, что используемый мной путь импорта отключен. Моя структура каталогов:

static/
    less/
        mixins.less
        admin/
            user.less

В user.less я пытаюсь импортировать mixins.less, используя это:

@import "../mixins.less";

Раньше это работало на меня, когда я использовал chirpy с dotless, но теперь я заметил, что 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);
...

Ответы [ 11 ]

41 голосов
/ 02 октября 2012

Я написал краткое сообщение в блоге о Использование LESS CSS с MVC4 Web Optimization .

В основном все сводится к использованию BundleTransformer.Less Nuget Package и изменению вашего BundleConfig.cs.

Проверено с помощью начальной загрузки.

РЕДАКТИРОВАТЬ: Следует упомянуть причину, по которой я говорю это, я также столкнулся с проблемой структуры каталогов @import, и эта библиотека обрабатывает ее правильно.

26 голосов
/ 06 августа 2012

В 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");
        }


    }
}
21 голосов
/ 31 октября 2012

Добавление к ответу Бена Калла:

Я знаю, что это "должно быть комментарием к посту Бена Калла", но оно добавляет немного лишнего, которое было бы невозможно добавить в комментарии.Поэтому, если хотите, проголосуйте за меня.Или закройте меня.

В блоге Бена все это делается, за исключением того, что не указывает минимизацию.

Итак, установите пакет 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 не найденчто мне все равно, кто проголосует за меня.

Если, наоборот, вы чувствуете желание проголосовать за этот ответ, пожалуйста, отдайте свой голос Бену.

Так что там.

17 голосов
/ 03 октября 2013

Обход, который я нашел, который был действительно полезен, состоял в том, чтобы установить каталог перед запуском 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")
);

Надеюсь, это поможет.

4 голосов
/ 15 марта 2012

Проблема в том, что DynamicFolderBundle считывает все содержимое файлов и передает объединенное содержимое в LessMinify.

Таким образом, любые @imports не имеют ссылки на местоположение файла.

Чтобы решить эту проблему, мне пришлось поместить все «менее» файлы в одно место.

Тогда вы должны понимать, что порядок файлов становится важным. Поэтому я начал переименовывать файл с помощью номера (например, «0 CONSTANTS.less», «1 MIXIN.less», что означает, что они загружаются в верхней части комбинированного вывода перед переходом в LessMinify.

если вы отлаживаете LessMinify и просматриваете ответ. Содержимое вы увидите вместе с выводом less!

Надеюсь, это поможет

3 голосов
/ 19 июня 2014

Вот самая простая версия кода, чтобы справиться с этим:

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));
    }
}
2 голосов
/ 25 мая 2012

Вот что я сделал:

Добавлен модуль Twitter Bootstrap Nuget.

Добавил это в мой файл _Layout.cshtml:

<link href="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Content/twitterbootstrap/less")" rel="stylesheet" type="text/css" />

Обратите внимание, что япереименовал мою папку "less" в twitterbootstrap, чтобы продемонстрировать, что я мог

переместить все файлы поменьше в подпапку с именем "import" , за исключением bootstrap.less и (для адаптивного дизайна) respive.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);

Это эффективно решает проблему импорта, не зная, куда импортировать.

1 голос
/ 17 апреля 2013

Исходя из приведенного ниже RockResolve, чтобы использовать минификатор MicrosoftAjax, укажите его как минификатор CSS по умолчанию в файле web.config, а не в качестве аргумента.

От https://bundletransformer.codeplex.com/wikipage/?title=Bundle%20Transformer%201.7.0%20Beta%201#BundleTransformerMicrosoftAjax_Chapter

Чтобы сделать MicrosoftAjaxCssMinifier CSS-минификатором по умолчанию, а MicrosoftAjaxJsMinifier JS-minifier по умолчанию, вам нужно внести изменения в файл Web.config.В defaultMinifier атрибуту элемента \ configuration \ bundleTransformer \ core \ css должно быть задано значение, равное MicrosoftAjaxCssMinifier , а в том же атрибуте элемента \ configuration \ bundleTransformer \ core \ js - MicrosoftAjaxJsMinifier.

1 голос
/ 08 февраля 2013

По состоянию на февраль 2013 года: отличное решение Майкла Бэйрда было заменено ответом «BundleTransformer.Less Nuget Package», упомянутым в сообщении Бена Калла.Аналогичный ответ по адресу: http://blog.cdeutsch.com/2012/08/using-less-and-twitter-bootstrap-in.html

Минимизация добавления блога и поста Criutsch в блоге Cdeutsch - это хорошо, но, видимо, сейчас это не правильный подход.

Кто-то с тем же решением получил ответы от автора BundleTransformer.: http://geekswithblogs.net/ToStringTheory/archive/2012/11/30/who-could-ask-for-more-with-less-css-part-2.aspx. Смотрите комментарии внизу.

В итоге используйте BundleTransformer.MicrosoftAjax вместо встроенных мини-определителей.например, css.Transforms.Add (new CssMinify ());заменено на css.Transforms.Add (новый BundleTransformer.MicrosoftAjax ());

0 голосов
/ 09 декабря 2013

У меня была такая же проблема, я видел то же сообщение об ошибке.Поиск решения в интернете привел меня сюда.Моя проблема была в следующем:

Внутри меньшего файла у меня был какой-то неправильный стиль, который давал мне предупреждение.Тем не менее файл не может быть проанализирован.Я избавился от сообщения об ошибке, удалив неправильную строку.

Надеюсь, это кому-нибудь поможет.

...