web-dev-qa-db-fra.com

MVC4 Less Bundle @import Directory

J'essaie d'utiliser le regroupement MVC4 pour regrouper certains de mes fichiers moins, mais il semble que le chemin d'importation que j'utilise est désactivé. Ma structure de répertoire est:

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

Dans user.less, j'essaie d'importer mixins.less en utilisant ceci:

@import "../mixins.less";

Auparavant, cela fonctionnait pour moi lorsque j'utilisais Chirpy avec Dotless, mais maintenant j'ai remarqué que ELMAH se fâchait contre moi, disant ceci:

System.IO.FileNotFoundException: 
    You are importing a file ending in .less that cannot be found.
File name: '../mixins.less'

Suis-je censé utiliser un autre @import avec MVC4?

Quelques informations supplémentaires

Voici le moins de classe et de code global.asax.cs que j'utilise pour tenter ceci:

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);
...
51
JesseBuesking

J'ai écrit un petit article de blog sur tilisation de moins de CSS avec l'optimisation Web MVC4 .

Cela revient à utiliser BundleTransformer.Less Nuget Package et à modifier votre BundleConfig.cs.

Testé avec bootstrap.

EDIT: Devrais mentionner la raison pour laquelle je dis cela, c'est que j'ai également rencontré le problème de structure de répertoire @import, et cette bibliothèque le gère correctement.

41
Ben Cull

Il y a du code publié sur GitHub Gist qui fonctionne bien avec @import et dotLess: https://Gist.github.com/2002958

Je l'ai testé avec Twitter Bootstrap et cela fonctionne bien.

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


    }
}
26
Michael Baird

Addendum à la réponse de Ben Cull:

Je sais que cela "devrait être un commentaire sur le post de Ben Cull", mais cela ajoute un petit plus qui serait impossible à ajouter dans un commentaire. Alors votez contre si vous devez. Ou ferme-moi.

Le blog de Ben fait tout, sauf qu'il ne spécifie pas la minification.

Installez donc le paquet BundleTransformer.Less comme le suggère Ben, puis, si vous souhaitez réduire votre CSS, procédez comme suit (dans ~/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);

La ligne ajoutée est:

css.Transforms.Add(new CssMinify());

CssMinify est dans System.Web.Optimizations

Je suis tellement soulagé de contourner le problème @import et le fichier résultant avec l'extension .less introuvable que je me fiche de savoir qui me vote.

Si, au contraire, vous ressentez le besoin de voter pour cette réponse, veuillez donner votre vote à Ben.

Donc là.

21
awrigley

Une solution que j'ai trouvée très utile était de définir le répertoire avant d'exécuter Less.Parse à l'intérieur de LessMinify.Process (). Voici comment je l'ai fait:

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

Puis en passant dans le chemin lors de la création de l'objet moins transformé comme ceci:

lessBundle.Transforms.Add(
    new LessTransform(HttpRuntime.AppDomainAppPath + "/Content/Less")
);

J'espère que cela t'aides.

17
Chris Peterson

Le problème est que DynamicFolderBundle lit tout le contenu des fichiers et transmet le contenu combiné au LessMinify.

En tant que tel, @imports n'a aucune référence à l'emplacement d'où provient le fichier.

Pour résoudre ce problème, j'ai dû placer tous les fichiers "moins" dans un seul emplacement.

Ensuite, vous devez comprendre que l'ordre des fichiers devient important. En tant que tel, j'ai commencé à renommer le fichier avec un nombre (par exemple: "0 CONSTANTS.less", "1 MIXIN.less", ce qui signifie qu'ils sont chargés en haut de la sortie combinée avant d'entrer dans LessMinify.

si vous déboguez votre LessMinify et affichez la réponse, vous verrez la sortie combinée en moins!

J'espère que cela t'aides

4
TheRealQuagmire

Voici la version la plus simple du code pour gérer cela, je pourrais trouver:

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));
    }
}
3
John

Voici ce que j'ai fait:

Twitter ajouté Bootstrap Nuget module.

Ajouté ceci à mon fichier _Layout.cshtml:

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

Notez que j'ai renommé mon dossier "less" en twitterbootstrap juste pour démontrer que je pouvais

Déplacé tous les fichiers moins dans un sous-dossier appelé "importations" sauf bootstrap.less et (pour une conception réactive) responsive.less .

~/Content/twitterbootstrap/imports

Ajout d'une configuration dans le web.config:

<add key="TwitterBootstrapLessImportsFolder" value="imports" />

Création de deux classes (légère modification de la classe ci-dessus):

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

Mon implémentation de IFileReader examine le membre statique BundlePath de la classe TwitterBootstrapLessMinify. Cela nous permet d'injecter un chemin de base pour les importations à utiliser. J'aurais aimé adopter une approche différente (en fournissant une instance de ma classe, mais je n'ai pas pu).

Enfin, j'ai ajouté les lignes suivantes au Global.asax:

BundleTable.Bundles.EnableDefaultBundles();

var lessFB = new DynamicFolderBundle("less", new TwitterBootstrapLessMinify(), "*.less", false);
BundleTable.Bundles.Add(lessFB);

Cela résout efficacement le problème des importations qui ne savent pas d'où importer.

2
PeteK68

En février 2013: la grande solution de Michael Baird a été dépassée par la réponse "BundleTransformer.Less Nuget Package" mentionnée dans le post de Ben Cull. Réponse similaire sur: http://blog.cdeutsch.com/2012/08/using-less-and-Twitter-bootstrap-in.html

Le blog de Cdeutsch et le billet d'Awrigley ajoutant la minification est bon, mais apparemment pas maintenant la bonne approche.

Quelqu'un d'autre avec la même solution a obtenu des réponses d'un auteur de BundleTransformer: http://geekswithblogs.net/ToStringTheory/archive/2012/11/30/who-could-ask-for-more-with-less- css-part-2.aspx . Voir les commentaires en bas.

En résumé, utilisez BundleTransformer.MicrosoftAjax au lieu des minificateurs intégrés intégrés. par exemple. css.Transforms.Add (nouveau CssMinify ()); remplacé par css.Transforms.Add (nouveau BundleTransformer.MicrosoftAjax ());

1
RockResolve

Dans le prolongement de RockResolve ci-dessous, pour utiliser le minifieur MicrosoftAjax, référencez-le en tant que minifieur CSS par défaut dans web.config au lieu de le passer en argument.

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

Pour faire de MicrosoftAjaxCssMinifier le minifieur CSS par défaut et MicrosoftAjaxJsMinifier le minifieur JS par défaut, vous devez apporter des modifications au fichier Web.config. Dans defaultMinifier l'attribut de l'élément\configuration\bundleTransformer\core\css doit avoir une valeur définie égale à MicrosoftAjaxCssMinifier , et dans le même attribut de l'élément\configuration\bundleTransformer\core\js - MicrosoftAjaxJsMinifier.

1
BarryF

Vérifiez ma bibliothèque https://www.nuget.org/packages/LessMVCFour J'espère que cela vous aidera.

0
Khoa Nguyen