ASP.NET приложение MVC4 не удается скомпилировать Bootstrap.Меньше на производстве, пока он работает на dev

Я чувствую себя немного застрял прямо сейчас. Сначала я использовал nuget для

установка-пакет Bootstrap.меньше

а также

установить-пакет dotless

тогда, как показано в Рик Андерсонс Blogpost о связывании и минификации in asp.net mvc, я создал класс LessTransform. Я установил 2 почти пустым .меньше файлов и создан новый пакет упаковывать их...

    var lessBundle = new Bundle("~/MyLess").IncludeDirectory("~/Content/MyLess", "*.less", true);
    lessBundle.Transforms.Add(new LessTransformer());
    lessBundle.Transforms.Add(new CssMinify());
    bundles.Add(lessBundle);

что действовать лучше. Затем я добавил новый StyleBundle к основному бутстрапу.меньше файла (который в основном использует @import для включения всех других .меньше файлов, что Bootstrap.меньше кораблей)...

    bundles.Add(new StyleBundle("~/Bootstrap").Include("~/Content/Bootstrap/less/bootstrap.less"));

и ScriptBundle для загрузочных JavaScripts...

    bundles.Add(new ScriptBundle("~/bundles/Bootstrap").Include("~/Scripts/bootstrap/js/bootstrap-*"));

чтобы включить все отправленные bootstrap -*.файлы js и TADAA все работали нормально. CSS был скомпилирован, включая все импортированные файлы JavaScript были правильно загружены.

но ... все, что только работало режим разработки с

    <compilation debug="true" targetFramework="4.5"/>

как только я отключу отладку, чтобы увидеть, работает ли пакетирование в один файл и минимизация, я сталкиваюсь с проблемой.

процесс связывания, похоже, не в состоянии импортировать все это .меньше файлов, импортированных в bootstrap.меньше

/* Minification failed. Returning unminified contents.
(11,1): run-time error CSS1019: Unexpected token, found '/'
(11,2): run-time error CSS1019: Unexpected token, found '/'
(12,1): run-time error CSS1031: Expected selector, found '@import'
(12,1): run-time error CSS1025: Expected comma or open brace, found '@import'
(12,27): run-time error CSS1019: Unexpected token, found '/'
(12,28): run-time error CSS1019: Unexpected token, found '/'

 ... here go many many lines like these 

(60,25): run-time error CSS1019: Unexpected token, found ';'
(62,1): run-time error CSS1019: Unexpected token, found '/'
(62,2): run-time error CSS1019: Unexpected token, found '/'
(63,1): run-time error CSS1031: Expected selector, found '@import'
(63,1): run-time error CSS1025: Expected comma or open brace, found '@import'
(63,27): run-time error CSS1019: Unexpected token, found '/'
(63,28): run-time error CSS1019: Unexpected token, found '/'
: run-time error CSS1067: Unexpected end of file encountered
 */
/*!
 * Bootstrap v2.3.1 
 *
 * Copyright 2012 Twitter, Inc
 * Licensed under the Apache License v2.0
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Designed and built with all the love in the world @twitter by @mdo and @fat.
 */

// Core variables and mixins
@import "variables.less"; // Modify this for custom colors, font-sizes, etc
@import "mixins.less";

... and the rest of the original bootstrap.less... no style definitions

взглянув на minified bootstrap.JavaScript bundle также поражает меня. в dev не было проблем после загрузки страницы, теперь после начальной загрузки.javascript был в комплекте и minified в Google состояния консоли JavaScript

Uncaught TypeError: Cannot read property 'Constructor' of undefined

Я просмотрел несколько тем, которые казались тесно связанными с моей проблемой, и я попробовал несколько вещей, но до сих пор безуспешно.

заранее большое спасибо всем, кто может пролить свет на мою ситуацию и кто укажет, что я упускаю или делаю неправильно. С наилучшими пожеланиями, Инго

4 ответов


если вы хотите использовать bootstrap как less-files и, кроме того, хотите перестать беспокоиться о связывании и минимизации на вашей машине разработки, а также на вашей производственной машине, вы можете рассмотреть возможность использования следующего подхода.

Примечание: вам не нужно все это, если вы играете только с меньшими файлами во время отладки; но как только вы хотите, чтобы ваше приложение работало на рабочем сервере, таком как Windows Azure, и все еще хотите просто изменить свой меньше файлов без необходимости заботиться о процедурах связывания и минимизации... что ж... тогда этот подход будет работать

поэтому, чтобы решить проблему, я чувствовал себя немного застрявшим, я должен был подойти к проблеме по-другому и должен был изменить (см. модификацию 2 ниже по сообщению) "BundleSource", который я думал, что хотел бы иметь.

поэтому не забудьте прочитать 2-ю модификацию / предупреждение близко к нижней части этого отвечай!


Модификация 1)

таким образом, первая и большая часть работы заключается в том, чтобы получить связывание файлов без начальной загрузки. Для этого я взял на себя смелость раскошелиться на кусок кода, который я нашел в интернете, который (если вам нужен только один пакет с меньшим количеством файлов) сам решает мою проблему... если вы не хотите использовать или иметь возможность использовать несколько меньших пакетов с несколькими базовыми каталогами... Так вот где я на самом деле нашел подход, который мне очень помог ...

... поэтому я благодарю Кристоф Клаас для своего блога-запись "использование ASP.NET связывание и минимизация с меньшим количеством файлов", на которые я случайно и с удовольствием наткнулся.

как и я, он пытался использовать LessMinify.cs, который Скотт Хансельман показывал в своих выступлениях, чтобы работать с 1 LESS-file вместо того, чтобы просто связывать каждый файл в 1 каталоге, полном LESS-files.

но ему пришлось немного расширить всю процедуру связывания, как он показывает в своей записи в блоге. Таким образом, решение, которое он предлагает, может объединить 1 меньше файла, который использует импорт для загрузки других меньших файлов. Но как он статически реализует путь, который добавляется в исходный каталог, в котором можно найти меньше файлов... какой бы меньший пакет вы ни определили, он должен выбрать меньший файл в том же каталоге...

вот где я взял на себя смелость расширить его решение немного дальше. Я создал файл LessBundling.cs со следующим содержанием:

using dotless.Core.configuration;
using dotless.Core.Input;
using MvcApplication2.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Web;
using System.Web.Hosting;
using System.Web.Optimization;

namespace MvcApplication2.Extensions
{
    // create Less-Minifier (use Type to define source directory of less files [see below at BootstrapFileReader])
    public class LessMinify<TFileReader> : CssMinify
        where TFileReader : IFileReader
    {
        public LessMinify() {}

        public override void Process(BundleContext context, BundleResponse response)
        {
            var config = new DotlessConfiguration()
            { 
                MinifyOutput = true,
                ImportAllFilesAsLess = true,
                CacheEnabled = false,
                LessSource = typeof(TFileReader)
            };

            response.Content = dotless.Core.Less.Parse(response.Content, config);
            base.Process(context, response);
        }
    }

    // create a LessStyleBundler to allow initializing LessBundle with a single less file that uses imports
    public class LessStyleBundle<TFileReader> : Bundle
        where TFileReader : IFileReader
    {
        public LessStyleBundle(string virtualPath)
            : base(virtualPath, new LessMinify<TFileReader>()) {}

        public LessStyleBundle(string virtualPath, string cdnPath)
            : base(virtualPath, cdnPath, new LessMinify<TFileReader>()) { }
    }

    // create abstract VirtualFileReader from dotless-IFileReader as a Base for localized 
    internal abstract class VirtualFileReader : IFileReader
    {
        public byte[] GetBinaryFileContents(string fileName)
        {
            fileName = GetFullPath(fileName);
            return File.ReadAllBytes(fileName);
        }

        public string GetFileContents(string fileName)
        {
            fileName = GetFullPath(fileName);
            return File.ReadAllText(fileName);
        }

        public bool DoesFileExist(string fileName)
        {
            fileName = GetFullPath(fileName);
            return File.Exists(fileName);
        }


        public string GetFullPath(string path)
        {
            return  HostingEnvironment.MapPath(SourceDirectory + path);
        }

        public abstract string SourceDirectory {get;}
        // implement to return Path to location of less files
        // e. g. return "~/Content/bootstrap/less/";
    }

    // create BootstrapFileReader overwriting the Path where to find the Bootstrap-Less-Files
    internal sealed class BootstrapFileReader : VirtualFileReader
    {
        public override string SourceDirectory 
        {
            get { return "~/Content/bootstrap/less/"; }
        }
    }

}

Итак, что же это на самом деле?

  1. LessMinify расширяет класс CssMinify и поэтому приносит все необходимое для минимизации файлов css
    • важным отличием от" обычного " объединения является создание новой конфигурации без точек с источником LessSource, определенным как typeof (TFileReader) ...
    • С помощью можно определить класс, который будет содержать исходный каталог, в котором bundler / minifier будет искать меньше файлов, которые будут приняты во внимание
  2. LessStyleBundle расширяет пакет и поэтому приносит все необходимое для связывания файлов
    • в этом классе я снова использую TFileReader, так как здесь будет создан экземпляр LessMinify(er)
  3. VirtualFileReader реализует IFileReader, который является интерфейсом без точек, определяющим все методы, необходимые для анализа меньше файлы и дать информацию где найти файлы для импорта
    • чтобы расширить решение Кристофа на проблему, я добавил абстрактное свойство SourceDirectory... требуя от меня также сделать абстрактный класс VirtualFileReader

теперь с этой настройкой вы можете создать столько LessFileReaders, сколько хотите. Вам просто нужно расширить абстрактный VirtualFileReader, как видно в

  1. BootstrapFileReader расширяется VirtualFileReader
    • единственная цель BootstrapFileReader-иметь свойство-геттер для SourceDirectory, в котором bundler / minifier найдет меньше файлов, которые должны быть импортированы

Ну в моем случае Bootstraps меньше-файлов, где лежит в ~ / Content / bootstrap / less, который должен быть местоположением по умолчанию, если вы установите " twitter.загрузчик.меньше",-самородок.

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

если вы хотите использовать этот метод связывания, чтобы фактически связывать и минимизировать меньше файлов в рабочей среде, вы просто добавляете lessstylebundle-instantion в BundlerConfig.cs:

bundles.Add(new LessStyleBundle<BootstrapFileReader>("~/bundles/BootstrapCSS")
    .Include("~/Content/bootstrap/less/bootstrap.less"));

и, конечно, ваш файл _Layout.cshtml также должен знать о готовая связка

@Styles.Render("~/bundles/BootstrapCSS")

Модификация 2)

теперь небольшая модификация, которую я также должен был добавить, чтобы получить эту работу

в моей первой попытке связать bootstrap.меньше я использовал это

bundles.Add(new LessStyleBundle<BootstrapFileReader>("~/Content/BootstrapCSS")
    .Include("~/Content/bootstrap/less/bootstrap.less"));

Я думал, что буду использовать контент в маршрутах для CSS / Less и пакеты в маршрутах для Javascript.

но это не работает из коробки. ASP.net не допускает создания Пакет, который начинается с ~/контент. Вы получите сбой авторизации 403. Поэтому самое простое решение для этого-использовать ~/пачки вместо:

bundles.Add(new LessStyleBundle<BootstrapFileReader>("~/bundles/BootstrapCSS")
    .Include("~/Content/bootstrap/less/bootstrap.less"));

поскольку существует не так много реальных решений этой проблемы, я надеюсь, что это поможет хотя бы некоторым из вас, если вы планируете интегрировать Twitter bootstrap в свой asp.net применение mvc4.

С наилучшими пожеланиями, Инго!--6-->


Я изменил обходной путь Ingo, чтобы избавиться от пользовательских классов для каждого каталога. Кроме того, я добавил правильный вывод исключений (потому что в противном случае все исключения молчали, и вы просто получили пустой файл меньше в случае ошибки).

public class LessTransform : IItemTransform
{
    [ThreadStatic]
    internal static string CurrentParsedFileDirectory;


    public string Process (string includedVirtualPath, string input)
    {
        CurrentParsedFileDirectory = Path.GetDirectoryName (includedVirtualPath);

        var config = new DotlessConfiguration
        {
            MinifyOutput = false,
            CacheEnabled = false,
            MapPathsToWeb = true,
            ImportAllFilesAsLess = true,
            LessSource = typeof (VirtualFileReader),
            Logger = typeof (ThrowExceptionLogger)
        };

        return Less.Parse (input, config);
    }
}


internal class VirtualFileReader : IFileReader
{
    public bool UseCacheDependencies
    {
        get { return false; }
    }


    public byte[] GetBinaryFileContents (string fileName)
    {
        return File.ReadAllBytes (GetFullPath (fileName));
    }


    public string GetFileContents (string fileName)
    {
        return File.ReadAllText (GetFullPath (fileName));
    }


    public bool DoesFileExist (string fileName)
    {
        return File.Exists (GetFullPath (fileName));
    }


    public string GetFullPath (string path)
    {
        if (string.IsNullOrEmpty (path))
            return string.Empty;

        return HostingEnvironment.MapPath (path[0] != '~' && path[0] != '/'
                                               ? Path.Combine (LessTransform.CurrentParsedFileDirectory, path)
                                               : path);
    }
}


public class ThrowExceptionLogger : Logger
{
    public ThrowExceptionLogger (LogLevel level) : base (level)
    {
    }


    protected override void Log (string message)
    {
        if (string.IsNullOrEmpty (message))
            return;

        if (message.Length > 100)
            message = message.Substring (0, 100) + "...";

        throw new LessTransformException (message);
    }
}


[Serializable]
public sealed class LessTransformException : Exception
{
    public LessTransformException (string message) : base (message)
    {
    }
}

использование:

bundles.Add (new StyleBundle ("~/styles-bundle/common")
    .Include ("~/content/bootstrap/bootstrap.less", new LessTransform ()));

у меня была та же проблема сегодня, я нашел работу, но я хотел бы получить лучшее решение. Я также пытался использовать dotless и пользовательское преобразование, подобное тому, что у вас есть.

решение:

событие перед построением:

"$(SolutionDir)packages\dotless.1.3.1.0\tool\dotless.compiler.exe" "$(ProjectDir)Content\less\bootstrap.less"

это создаст bootstrap.css файл, который вы можете включить как обычный CSS вместо LESS.

это решение не идеально, так как вам придется обновлять событие сборки каждый раз при обновлении dotless, и иметь ручку пачки оно более чисто также.


Я очень, очень рекомендую установить WebEssentials 2012 вместо.

Он будет генерировать css-файл и уменьшенный css-файл из вашего.меньше, и вы можете ссылаться на css вместо этого. Он будет автоматически обновлять css каждый раз, когда вы вносите изменения в свой .меньше, поэтому нет необходимости запоминать какие-либо предварительные шаги или что-либо еще...

при установке WebEssentials вы также получите другие сладкие функции, такие как предварительный просмотр CoffeeScript, TypeScript и меньше. JSHint, автоматическое minification и много и много больше "вкусностей"!