Рассказ о том, как написать свой собственный CSS препроцессор за 9 месяцев

Xочу рассказать о своем детище – препроцессоре и парсере CSS, которым я начал заниматься с апреля прошлого года. Зачем я начал заниматься им? Признаваясь себе честно уже сейчас, я могу сказать: хотелось изобрести свой собственный велосипед. Чем я руководствовался тогда? Трудно сказать. Возможно, тем же самым. А возможно, тем, что я толком не нашел ничего удовлетворяющего моим требованиям к CSS препроцессору для моей любимой платформы разработки.

Требования к CSS препроцессору у меня сформировались после прочтения одной из статей здесь. Это была статья про препроцессор «Stylus для Node.js». Собственно, тогда то я про эти «препроцессоры» и узнал. Меня поразила вся простота синтаксиса этого препроцессора. После двухдневного (а может и меньшего) просмотра результатов с гугла, я ничего интересного для себя не нашел. Вот именно в этот момент ко мне в голову и пришла шальная мысль: а почему бы нет?
Требования у меня были следующие:
  • Наиболее простой синтаксис (ну это само собой!)
  • Язык разработки – PHP (ну теперь то уже можно и сказать)
  • Возможность отображения исходного файла стилей в виде дерева блоков

Собственно, наверно, это и все.

Что у меня получилось?


Все это время, прошедшее с начальной точки отсчета, я потратил на разработку своего продукта, и у меня получилось следующее — библиотека MySheet с открытым исходным кодом, являющаяся одновременно и парсером, и препроцессором стилей CSS.

Ну, конечно же, мне пришлось написать небольшой сайтик для презентации своего продукта (вы уже догадались, какой язык программирования был выбран мной для этой цели?). Несколько интересных моментов процесса разработки – о том, как я создавал свою библиотеку, с какими подводными камнями столкнулся и что я узнал в процессе ее создания – я расскажу чуть позже. А теперь, о возможностях моего CSS препроцессора.
  • Арифметические выражения
    Данная возможность есть практически в каждом CSS препроцессоре. Не обошла она стороной и библиотеку MySheet:

    $wrapper_height = 50%
    .wrapper
        height $wrapper_height + 20px
        top ($wrapper_height / 2)
    

    Для чего она нужна? Да Бог его знает. Шутка. Сейчас эта функция моей библиотеки находится в очень сыром виде. Нет типа bool, а следовательно пока что (пока что!) нет условий. Но есть одна приятная плюшка, применение которой вы можете найти на главной странице моего сайта:

    .object
        color #a50c5b - 50sat /* decrease saturation by 50% */
        background-color #a50c5b + 50lt /* make color lighter by 50 percent */
    

    Да! Это именно то, о чем вы сейчас думаете! Можно выполнять арифметические действия над цветами! Я надеюсь, вы вдоволь поиграетесь с этой фишкой на официальном сайте библиотеки, а теперь перейдем к следующему пункту в нашем списке.
  • Mixin’ы
    Ну, вот, не смог я написать это слово на русском. Уж больно многие пишут его на английском:

    @mixin filter-grayscale(percent)
        -webkit-filter: grayscale($percent);
        -ms-filter: grayscale($percent);
        -o-filter: grayscale($percent);
        filter: grayscale($percent);
    
    img
        filter-grayscale 100%
    img:hover
        filter-grayscale 0%
    

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

    В объявлении миксина также доступна переменная $arguments, которая просто перечислит все переданные аргументы в одну строку:

    @mixin border-radius(topleft, topright)
        -webkit-border-radius $topleft $topright 4px 5px
        border-radius $arguments
    

    Я постарался, чтобы в препроцессор были уже встроены некоторые миксины. Это, во-первых, благоприятно скажется на производительности. Во-вторых, позволит разработчикам и дизайнерам сосредоточиться на написании стилей для сайта, а не методов для упрощения этого процесса. Плюс, второе часто бывает делать лень.
  • Функции
    Функций в библиотеки пока что всего – три. Это – abs, negate и unitless. Но база для их написания подготовлена, и в будущем список доступных функций будет расширяться.

    Синтаксис для вызова функций точно такой же как и синтаксис обычных функций CSS, с той лишь разницей, что при вызове зарегистрированной библиотечной функции в скомпилированном CSS будет фигурировать не она, а значение ею возвращаемое.
  • Флаги
    Эту возможность я придумал относительно недавно. Смысл ее – простой до безобразия, и я думаю лучше показать сразу пример ее использования:

    html
        height 0
        width 50px !prefixWith(ms, moz) !important
        border-radius 5px !important
        filter-grayscale 50%
        transform scale(2)
    

    Флаг в данном случае применяется для удобного добавления префиксов к правилам и компилируется в следующий код CSS:

    html {
        height: 0;
        -ms-width: 50px !important;
        -moz-width: 50px !important;
        width: 50px !important;
        -moz-border-radius: 5px;
        -webkit-border-radius: 5px;
        border-radius: 5px;
        -webkit-filter: grayscale(50%);
        filter: grayscale(50%);
        -ms-transform: scale(2);
        -moz-transform: scale(2);
        -o-transform: scale(2);
        -webkit-transform: scale(2);
        transform: scale(2)
    }
    

    Флаг выполняет некоторые действия над конкретным правилом. У меня есть идея использовать флаги еще и для того, чтобы помечать и наделять правила определенными свойствами. Например, так можно сделать флаг !noMixin, который запретит компиляцию и вставку миксина в код CSS. Таким образом можно избежать расширения синтаксиса лишними символами и ключевыми словами.
  • Плагины
    Я старался сделать свою библиотеку расширяемой. Т.е. чтобы любой человек (и Вы, и я, и, вообще, любая домохозяйка) могли расширить возможности библиотеки написанием плагина, а не созданием форка на гитхаб и переворачиванием всего исходного кода (хотя второе я и не воспрещаю делать). Сейчас написано два плагина для библиотеки MySheet:
    — PluginMixin — добавляет возможность использования миксинов в коде MSS (MySheet Styles)
    — PluginSelectorExtensions — добавляет вкусняшки вроде: обращение к родительскому селектору (или группе селекторов) через символ & и псевдо-селектор :any(), который я нагло содрал с препроцессора CSSCrush.
  • Дерево блоков
    Это, собственно, и есть третье по списку требование, о котором я писал в начале статьи. Я хотел, чтобы стилями CSS можно было управлять не только непосредственно, изменяя исходники вручную, но и делать это из бэкэнда, т.е. производить те манипуляции с кодом, которые присущи CSS парсерам (тык и еще тык). Что это дает? Например, можно изменять стили сайта на основании предпочтений пользователя. А предпочтения могут быть самыми разными. Одни хотят шрифт больше, другие – передвинуть заголовок на главной страницы немного ниже. Данную возможность можно добавлять к различным генераторам сайтов, что благоприятно скажется на аудитории пользователей программного продукта.

    Переходя от слов к делу, хочется показать, какое именно дерево блоков образуется на выходе после парсинга файла с помощью библиотеки MySheet. Рассмотрим такой простой исходный файл MSS:

    html { color red; text-align: center; margin: 0 auto; }
    @mixin rounded-corners (top, right, bottom, left)
        -webkit-border-radius \$left + \$right \$top + \$bottom
        -moz-border-radius \$arguments
        border-radius \$arguments \$left \$right \$left \$right
            
    @mixin diagonal-border-radius(left, right)
        border-radius \$arguments \$right \$left
            
    @page 
        padding 5px
    body
        rounded-corners 1 2 3 4 
        .wrapper
            diagonal-border-radius 6px 10px
            h1 span
                color blue
    

    Пропустим этот код через парсер:

    <?php
    try {
        $result = $mysheet->parseCode($code); 
        $compiledCode = $result->toRealCss();
    } catch (\MSSLib\Error\MySheetException $ex) {
        echo($ex->getTraceAsString());
    }
    

    и на выходе получим примерно следующее дерево:
    Дерево блоков MSS
    object(MSSLib\Structure\Document)[64]
      protected '_docFilePath' => null
      protected 'children' => 
        array (size=5)
          0 => 
            object(MSSLib\Structure\Ruleset)[98]
              private '_selectors' => 
                array (size=1)
                  0 => 
                    object(MSSLib\Structure\Selector)[99]
                      private '_mssPath' => string 'html' (length=4)
                      private '_cssPathGroup' => 
                        object(MSSLib\Structure\CssSelectorGroup)[180]
                          private 'paths' => 
                            array (size=1)
                              0 => string 'html' (length=4)
                      private '_ruleset' => 
                        &object(MSSLib\Structure\Ruleset)[98]
                      private '_isFullSelector' => null
                      private '_isParsed' => boolean true
                      private '_handlerMap' => null
              protected '_parentRuleset' => null
              protected 'children' => 
                array (size=3)
                  0 => 
                    object(MSSLib\Structure\Declaration)[100]
                      private 'ruleName' => string 'color' (length=5)
                      private 'ruleValue' => 
                        object(MSSLib\Structure\RuleValue)[101]
                          private 'params' => 
                            array (size=1)
                              0 => 
                                object(MSSLib\EmbeddedClasses\ColorClass)[104]
                                  protected 'type' => string 'html' (length=4)
                                  protected 'color' => 
                                    array (size=1)
                                      0 => string 'red' (length=3)
                                  protected '_colorLib' => null
                          private '_parentDeclaration' => 
                            &object(MSSLib\Structure\Declaration)[100]
                          private '_flags' => 
                            array (size=0)
                              empty
                      private 'ruleEnabled' => boolean true
                      private 'parent' (MSSLib\Structure\Block) => null
                      private '_handlerMap' => null
                      private '_handlerMap' (MSSLib\Structure\Block) => null
                  1 => 
                    object(MSSLib\Structure\Declaration)[102]
                      private 'ruleName' => string 'text-align' (length=10)
                      private 'ruleValue' => 
                        object(MSSLib\Structure\RuleValue)[103]
                          private 'params' => 
                            array (size=1)
                              0 => 
                                object(MSSLib\EmbeddedClasses\NonQuotedStringClass)[107]
                                  protected 'text' => string 'center' (length=6)
                          private '_parentDeclaration' => 
                            &object(MSSLib\Structure\Declaration)[102]
                          private '_flags' => 
                            array (size=0)
                              empty
                      private 'ruleEnabled' => boolean true
                      private 'parent' (MSSLib\Structure\Block) => null
                      private '_handlerMap' => null
                      private '_handlerMap' (MSSLib\Structure\Block) => null
                  2 => 
                    object(MSSLib\Structure\Declaration)[105]
                      private 'ruleName' => string 'margin' (length=6)
                      private 'ruleValue' => 
                        object(MSSLib\Structure\RuleValue)[106]
                          private 'params' => 
                            array (size=2)
                              0 => 
                                object(MSSLib\EmbeddedClasses\MetricClass)[110]
                                  protected 'metric' => float 0
                                  protected 'unit' => null
                              1 => 
                                object(MSSLib\EmbeddedClasses\NonQuotedStringClass)[111]
                                  protected 'text' => string 'auto' (length=4)
                          private '_parentDeclaration' => 
                            &object(MSSLib\Structure\Declaration)[105]
                          private '_flags' => 
                            array (size=0)
                              empty
                      private 'ruleEnabled' => boolean true
                      private 'parent' (MSSLib\Structure\Block) => null
                      private '_handlerMap' => null
                      private '_handlerMap' (MSSLib\Structure\Block) => null
              private 'parent' (MSSLib\Structure\Block) => 
                &object(MSSLib\Structure\Document)[64]
              private '_handlerMap' (MSSLib\Structure\Block) => null
          1 => 
            object(MSSLib\Plugins\Mixin\Mixin)[97]
              protected 'name' => string 'rounded-corners' (length=15)
              protected 'locals' => 
                array (size=4)
                  0 => string 'top' (length=3)
                  1 => string 'right' (length=5)
                  2 => string 'bottom' (length=6)
                  3 => string 'left' (length=4)
              protected 'children' => 
                array (size=3)
                  0 => 
                    object(MSSLib\Structure\Declaration)[109]
                      private 'ruleName' => string '-webkit-border-radius' (length=21)
                      private 'ruleValue' => 
                        object(MSSLib\Structure\RuleValue)[113]
                          private 'params' => 
                            array (size=2)
                              0 => 
                                object(MSSLib\EmbeddedClasses\MathExprClass)[122]
                                  protected 'expressionTree' => 
                                    object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[116]
                                      private 'value' (Tree\Node\Node) => null
                                      private 'parent' (Tree\Node\Node) => null
                                      private 'children' (Tree\Node\Node) => 
                                        array (size=3)
                                          0 => 
                                            object(MSSLib\Essentials\ExpressionTree\ParamNode)[117]
                                              private 'value' (Tree\Node\Node) => 
                                                object(MSSLib\EmbeddedClasses\VariableClass)[119]
                                                  private 'varName' => string 'left' (length=4)
                                              private 'parent' (Tree\Node\Node) => 
                                                &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[116]
                                              private 'children' (Tree\Node\Node) => 
                                                array (size=0)
                                                  empty
                                          1 => 
                                            object(MSSLib\Essentials\ExpressionTree\OperatorNode)[118]
                                              private 'value' (Tree\Node\Node) => 
                                                object(MSSLib\Operators\PlusOperator)[120]
                                              private 'parent' (Tree\Node\Node) => 
                                                &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[116]
                                              private 'children' (Tree\Node\Node) => 
                                                array (size=0)
                                                  empty
                                          2 => 
                                            object(MSSLib\Essentials\ExpressionTree\ParamNode)[121]
                                              private 'value' (Tree\Node\Node) => 
                                                object(MSSLib\EmbeddedClasses\VariableClass)[123]
                                                  private 'varName' => string 'right' (length=5)
                                              private 'parent' (Tree\Node\Node) => 
                                                &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[116]
                                              private 'children' (Tree\Node\Node) => 
                                                array (size=0)
                                                  empty
                              1 => 
                                object(MSSLib\EmbeddedClasses\MathExprClass)[130]
                                  protected 'expressionTree' => 
                                    object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[124]
                                      private 'value' (Tree\Node\Node) => null
                                      private 'parent' (Tree\Node\Node) => null
                                      private 'children' (Tree\Node\Node) => 
                                        array (size=3)
                                          0 => 
                                            object(MSSLib\Essentials\ExpressionTree\ParamNode)[125]
                                              private 'value' (Tree\Node\Node) => 
                                                object(MSSLib\EmbeddedClasses\VariableClass)[127]
                                                  private 'varName' => string 'top' (length=3)
                                              private 'parent' (Tree\Node\Node) => 
                                                &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[124]
                                              private 'children' (Tree\Node\Node) => 
                                                array (size=0)
                                                  empty
                                          1 => 
                                            object(MSSLib\Essentials\ExpressionTree\OperatorNode)[126]
                                              private 'value' (Tree\Node\Node) => 
                                                object(MSSLib\Operators\PlusOperator)[128]
                                              private 'parent' (Tree\Node\Node) => 
                                                &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[124]
                                              private 'children' (Tree\Node\Node) => 
                                                array (size=0)
                                                  empty
                                          2 => 
                                            object(MSSLib\Essentials\ExpressionTree\ParamNode)[129]
                                              private 'value' (Tree\Node\Node) => 
                                                object(MSSLib\EmbeddedClasses\VariableClass)[131]
                                                  private 'varName' => string 'bottom' (length=6)
                                              private 'parent' (Tree\Node\Node) => 
                                                &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[124]
                                              private 'children' (Tree\Node\Node) => 
                                                array (size=0)
                                                  empty
                          private '_parentDeclaration' => 
                            &object(MSSLib\Structure\Declaration)[109]
                          private '_flags' => 
                            array (size=0)
                              empty
                      private 'ruleEnabled' => boolean true
                      private 'parent' (MSSLib\Structure\Block) => null
                      private '_handlerMap' => null
                      private '_handlerMap' (MSSLib\Structure\Block) => null
                  1 => 
                    object(MSSLib\Structure\Declaration)[114]
                      private 'ruleName' => string '-moz-border-radius' (length=18)
                      private 'ruleValue' => 
                        object(MSSLib\Structure\RuleValue)[115]
                          private 'params' => 
                            array (size=1)
                              0 => 
                                object(MSSLib\EmbeddedClasses\VariableClass)[134]
                                  private 'varName' => string 'arguments' (length=9)
                          private '_parentDeclaration' => 
                            &object(MSSLib\Structure\Declaration)[114]
                          private '_flags' => 
                            array (size=0)
                              empty
                      private 'ruleEnabled' => boolean true
                      private 'parent' (MSSLib\Structure\Block) => null
                      private '_handlerMap' => null
                      private '_handlerMap' (MSSLib\Structure\Block) => null
                  2 => 
                    object(MSSLib\Structure\Declaration)[132]
                      private 'ruleName' => string 'border-radius' (length=13)
                      private 'ruleValue' => 
                        object(MSSLib\Structure\RuleValue)[133]
                          private 'params' => 
                            array (size=5)
                              0 => 
                                object(MSSLib\EmbeddedClasses\VariableClass)[137]
                                  private 'varName' => string 'arguments' (length=9)
                              1 => 
                                object(MSSLib\EmbeddedClasses\VariableClass)[138]
                                  private 'varName' => string 'left' (length=4)
                              2 => 
                                object(MSSLib\EmbeddedClasses\VariableClass)[139]
                                  private 'varName' => string 'right' (length=5)
                              3 => 
                                object(MSSLib\EmbeddedClasses\VariableClass)[140]
                                  private 'varName' => string 'left' (length=4)
                              4 => 
                                object(MSSLib\EmbeddedClasses\VariableClass)[141]
                                  private 'varName' => string 'right' (length=5)
                          private '_parentDeclaration' => 
                            &object(MSSLib\Structure\Declaration)[132]
                          private '_flags' => 
                            array (size=0)
                              empty
                      private 'ruleEnabled' => boolean true
                      private 'parent' (MSSLib\Structure\Block) => null
                      private '_handlerMap' => null
                      private '_handlerMap' (MSSLib\Structure\Block) => null
              private 'parent' (MSSLib\Structure\Block) => 
                &object(MSSLib\Structure\Document)[64]
              private '_handlerMap' (MSSLib\Structure\Block) => null
              protected 'plugin' => 
                object(MSSLib\Plugins\Mixin\PluginMixin)[58]
                  private '_registeredMixins' => 
                    array (size=0)
                      empty
                  private '_systemMixins' => 
                    array (size=3)
                      'border-radius' => 
                        array (size=2)
                          0 => 
                            object(MSSLib\Plugins\Mixin\EmbeddedMixins\BasicSet)[61]
                          1 => string 'border_radius' (length=13)
                      'transform' => 
                        array (size=2)
                          0 => 
                            object(MSSLib\Plugins\Mixin\EmbeddedMixins\BasicSet)[61]
                          1 => string 'transform' (length=9)
                      'filter-grayscale' => 
                        array (size=2)
                          0 => 
                            object(MSSLib\Plugins\Mixin\EmbeddedMixins\BasicSet)[61]
                          1 => string 'filter_grayscale' (length=16)
                  protected '_enabledMixinSetClasses' => 
                    array (size=1)
                      0 => string 'basic' (length=5)
          2 => 
            object(MSSLib\Plugins\Mixin\Mixin)[112]
              protected 'name' => string 'diagonal-border-radius' (length=22)
              protected 'locals' => 
                array (size=2)
                  0 => string 'left' (length=4)
                  1 => string 'right' (length=5)
              protected 'children' => 
                array (size=1)
                  0 => 
                    object(MSSLib\Structure\Declaration)[136]
                      private 'ruleName' => string 'border-radius' (length=13)
                      private 'ruleValue' => 
                        object(MSSLib\Structure\RuleValue)[142]
                          private 'params' => 
                            array (size=3)
                              0 => 
                                object(MSSLib\EmbeddedClasses\VariableClass)[145]
                                  private 'varName' => string 'arguments' (length=9)
                              1 => 
                                object(MSSLib\EmbeddedClasses\VariableClass)[146]
                                  private 'varName' => string 'right' (length=5)
                              2 => 
                                object(MSSLib\EmbeddedClasses\VariableClass)[147]
                                  private 'varName' => string 'left' (length=4)
                          private '_parentDeclaration' => 
                            &object(MSSLib\Structure\Declaration)[136]
                          private '_flags' => 
                            array (size=0)
                              empty
                      private 'ruleEnabled' => boolean true
                      private 'parent' (MSSLib\Structure\Block) => null
                      private '_handlerMap' => null
                      private '_handlerMap' (MSSLib\Structure\Block) => null
              private 'parent' (MSSLib\Structure\Block) => 
                &object(MSSLib\Structure\Document)[64]
              private '_handlerMap' (MSSLib\Structure\Block) => null
              protected 'plugin' => 
                object(MSSLib\Plugins\Mixin\PluginMixin)[58]
                  private '_registeredMixins' => 
                    array (size=0)
                      empty
                  private '_systemMixins' => 
                    array (size=3)
                      'border-radius' => 
                        array (size=2)
                          0 => 
                            object(MSSLib\Plugins\Mixin\EmbeddedMixins\BasicSet)[61]
                          1 => string 'border_radius' (length=13)
                      'transform' => 
                        array (size=2)
                          0 => 
                            object(MSSLib\Plugins\Mixin\EmbeddedMixins\BasicSet)[61]
                          1 => string 'transform' (length=9)
                      'filter-grayscale' => 
                        array (size=2)
                          0 => 
                            object(MSSLib\Plugins\Mixin\EmbeddedMixins\BasicSet)[61]
                          1 => string 'filter_grayscale' (length=16)
                  protected '_enabledMixinSetClasses' => 
                    array (size=1)
                      0 => string 'basic' (length=5)
          3 => 
            object(MSSLib\Structure\AtRule)[135]
              protected '_name' => string 'page' (length=4)
              protected '_parameters' => string '' (length=0)
              protected 'children' => 
                array (size=1)
                  0 => 
                    object(MSSLib\Structure\Declaration)[143]
                      private 'ruleName' => string 'padding' (length=7)
                      private 'ruleValue' => 
                        object(MSSLib\Structure\RuleValue)[148]
                          private 'params' => 
                            array (size=1)
                              0 => 
                                object(MSSLib\EmbeddedClasses\MetricClass)[151]
                                  protected 'metric' => float 5
                                  protected 'unit' => string 'px' (length=2)
                          private '_parentDeclaration' => 
                            &object(MSSLib\Structure\Declaration)[143]
                          private '_flags' => 
                            array (size=0)
                              empty
                      private 'ruleEnabled' => boolean true
                      private 'parent' (MSSLib\Structure\Block) => 
                        &object(MSSLib\Structure\AtRule)[135]
                      private '_handlerMap' => null
                      private '_handlerMap' (MSSLib\Structure\Block) => null
              private 'parent' (MSSLib\Structure\Block) => 
                &object(MSSLib\Structure\Document)[64]
              private '_handlerMap' (MSSLib\Structure\Block) => null
          4 => 
            object(MSSLib\Structure\Ruleset)[144]
              private '_selectors' => 
                array (size=1)
                  0 => 
                    object(MSSLib\Structure\Selector)[150]
                      private '_mssPath' => string 'body' (length=4)
                      private '_cssPathGroup' => 
                        object(MSSLib\Structure\CssSelectorGroup)[96]
                          private 'paths' => 
                            array (size=1)
                              0 => string 'body' (length=4)
                      private '_ruleset' => 
                        &object(MSSLib\Structure\Ruleset)[144]
                      private '_isFullSelector' => null
                      private '_isParsed' => boolean true
                      private '_handlerMap' => null
              protected '_parentRuleset' => null
              protected 'children' => 
                array (size=3)
                  0 => 
                    object(MSSLib\Structure\Declaration)[152]
                      private 'ruleName' => string 'rounded-corners' (length=15)
                      private 'ruleValue' => 
                        object(MSSLib\Structure\RuleValue)[153]
                          private 'params' => 
                            array (size=4)
                              0 => 
                                object(MSSLib\EmbeddedClasses\MetricClass)[156]
                                  protected 'metric' => float 1
                                  protected 'unit' => null
                              1 => 
                                object(MSSLib\EmbeddedClasses\MetricClass)[157]
                                  protected 'metric' => float 2
                                  protected 'unit' => null
                              2 => 
                                object(MSSLib\EmbeddedClasses\MetricClass)[158]
                                  protected 'metric' => float 3
                                  protected 'unit' => null
                              3 => 
                                object(MSSLib\EmbeddedClasses\MetricClass)[159]
                                  protected 'metric' => float 4
                                  protected 'unit' => null
                          private '_parentDeclaration' => 
                            &object(MSSLib\Structure\Declaration)[152]
                          private '_flags' => 
                            array (size=0)
                              empty
                      private 'ruleEnabled' => boolean true
                      private 'parent' (MSSLib\Structure\Block) => null
                      private '_handlerMap' => null
                      private '_handlerMap' (MSSLib\Structure\Block) => null
                  1 => 
                    object(MSSLib\Structure\Declaration)[154]
                      private 'ruleName' => string 'transform' (length=9)
                      private 'ruleValue' => 
                        object(MSSLib\Structure\RuleValue)[155]
                          private 'params' => 
                            array (size=1)
                              0 => 
                                object(MSSLib\EmbeddedClasses\FunctionClass)[167]
                                  protected 'name' => string 'rotate' (length=6)
                                  protected 'arguments' => 
                                    array (size=1)
                                      0 => 
                                        object(MSSLib\EmbeddedClasses\MathExprClass)[174]
                                          protected 'expressionTree' => 
                                            object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[170]
                                              private 'value' (Tree\Node\Node) => null
                                              private 'parent' (Tree\Node\Node) => null
                                              private 'children' (Tree\Node\Node) => 
                                                array (size=2)
                                                  0 => 
                                                    object(MSSLib\Essentials\ExpressionTree\OperatorNode)[171]
                                                      private 'value' (Tree\Node\Node) => 
                                                        object(MSSLib\Operators\UnaryMinusOperator)[172]
                                                      private 'parent' (Tree\Node\Node) => 
                                                        &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[170]
                                                      private 'children' (Tree\Node\Node) => 
                                                        array (size=0)
                                                          empty
                                                  1 => 
                                                    object(MSSLib\Essentials\ExpressionTree\ParamNode)[173]
                                                      private 'value' (Tree\Node\Node) => 
                                                        object(MSSLib\EmbeddedClasses\MetricClass)[175]
                                                          protected 'metric' => float 5
                                                          protected 'unit' => string 'deg' (length=3)
                                                      private 'parent' (Tree\Node\Node) => 
                                                        &object(MSSLib\Essentials\ExpressionTree\ExpressionNode)[170]
                                                      private 'children' (Tree\Node\Node) => 
                                                        array (size=0)
                                                          empty
                                  protected '_functionRenderer' => 
                                    object(MSSLib\Essentials\FunctionRenderers\DefaultFunctionRenderer)[166]
                          private '_parentDeclaration' => 
                            &object(MSSLib\Structure\Declaration)[154]
                          private '_flags' => 
                            array (size=0)
                              empty
                      private 'ruleEnabled' => boolean true
                      private 'parent' (MSSLib\Structure\Block) => null
                      private '_handlerMap' => null
                      private '_handlerMap' (MSSLib\Structure\Block) => null
                  2 => 
                    object(MSSLib\Structure\Ruleset)[149]
                      private '_selectors' => 
                        array (size=1)
                          0 => 
                            object(MSSLib\Structure\Selector)[161]
                              private '_mssPath' => string '.wrapper' (length=8)
                              private '_cssPathGroup' => 
                                object(MSSLib\Structure\CssSelectorGroup)[176]
                                  private 'paths' => 
                                    array (size=1)
                                      0 => string 'body .wrapper' (length=13)
                              private '_ruleset' => 
                                &object(MSSLib\Structure\Ruleset)[149]
                              private '_isFullSelector' => null
                              private '_isParsed' => boolean true
                              private '_handlerMap' => null
                      protected '_parentRuleset' => 
                        &object(MSSLib\Structure\Ruleset)[144]
                      protected 'children' => 
                        array (size=2)
                          0 => 
                            object(MSSLib\Structure\Declaration)[168]
                              private 'ruleName' => string 'diagonal-border-radius' (length=22)
                              private 'ruleValue' => 
                                object(MSSLib\Structure\RuleValue)[169]
                                  private 'params' => 
                                    array (size=2)
                                      0 => 
                                        object(MSSLib\EmbeddedClasses\MetricClass)[178]
                                          protected 'metric' => float 6
                                          protected 'unit' => string 'px' (length=2)
                                      1 => 
                                        object(MSSLib\EmbeddedClasses\MetricClass)[179]
                                          protected 'metric' => float 10
                                          protected 'unit' => string 'px' (length=2)
                                  private '_parentDeclaration' => 
                                    &object(MSSLib\Structure\Declaration)[168]
                                  private '_flags' => 
                                    array (size=0)
                                      empty
                              private 'ruleEnabled' => boolean true
                              private 'parent' (MSSLib\Structure\Block) => null
                              private '_handlerMap' => null
                              private '_handlerMap' (MSSLib\Structure\Block) => null
                          1 => 
                            object(MSSLib\Structure\Ruleset)[164]
                              private '_selectors' => 
                                array (size=1)
                                  0 => 
                                    object(MSSLib\Structure\Selector)[177]
                                      private '_mssPath' => string 'h1 span' (length=7)
                                      private '_cssPathGroup' => 
                                        object(MSSLib\Structure\CssSelectorGroup)[183]
                                          private 'paths' => 
                                            array (size=1)
                                              0 => string 'body .wrapper h1 span' (length=21)
                                      private '_ruleset' => 
                                        &object(MSSLib\Structure\Ruleset)[164]
                                      private '_isFullSelector' => null
                                      private '_isParsed' => boolean true
                                      private '_handlerMap' => null
                              protected '_parentRuleset' => 
                                &object(MSSLib\Structure\Ruleset)[149]
                              protected 'children' => 
                                array (size=1)
                                  0 => 
                                    object(MSSLib\Structure\Declaration)[181]
                                      private 'ruleName' => string 'color' (length=5)
                                      private 'ruleValue' => 
                                        object(MSSLib\Structure\RuleValue)[182]
                                          private 'params' => 
                                            array (size=1)
                                              0 => 
                                                object(MSSLib\EmbeddedClasses\ColorClass)[185]
                                                  protected 'type' => string 'html' (length=4)
                                                  protected 'color' => 
                                                    array (size=1)
                                                      0 => string 'blue' (length=4)
                                                  protected '_colorLib' => null
                                          private '_parentDeclaration' => 
                                            &object(MSSLib\Structure\Declaration)[181]
                                          private '_flags' => 
                                            array (size=0)
                                              empty
                                      private 'ruleEnabled' => boolean true
                                      private 'parent' (MSSLib\Structure\Block) => null
                                      private '_handlerMap' => null
                                      private '_handlerMap' (MSSLib\Structure\Block) => null
                              private 'parent' (MSSLib\Structure\Block) => 
                                &object(MSSLib\Structure\Ruleset)[149]
                              private '_handlerMap' (MSSLib\Structure\Block) => null
                      private 'parent' (MSSLib\Structure\Block) => 
                        &object(MSSLib\Structure\Ruleset)[144]
                      private '_handlerMap' (MSSLib\Structure\Block) => null
              private 'parent' (MSSLib\Structure\Block) => 
                &object(MSSLib\Structure\Document)[64]
              private '_handlerMap' (MSSLib\Structure\Block) => null
      private 'parent' (MSSLib\Structure\Block) => null
      private '_handlerMap' (MSSLib\Structure\Block) => null
    


    Что в конечном итоге компилируется в следующий CSS-код:
    html {
        color: #ff0000;
        text-align: center;
        margin: 0 auto
    }
    
    @page  {
        padding: 5px
    }
    
    body {
        -webkit-border-radius: 6 4;
        -moz-border-radius: 1 2 3 4;
        border-radius: 1 2 3 4 4 2 4 2;
        -ms-transform: rotate(-5deg);
        -moz-transform: rotate(-5deg);
        -o-transform: rotate(-5deg);
        -webkit-transform: rotate(-5deg);
        transform: rotate(-5deg)
    }
    
    body .wrapper {
        border-radius: 6px 10px 10px 6px
    }
    
    body .wrapper h1 span {
        color: #0000ff
    }
    

  • Формат вывода CSS
    Форматом выходного CSS-кода можно управлять с помощью настроек библиотеки. Для этого следует задать предпочтительные префиксы и суффиксы к строкам:

    $mysheet = MySheet::Instance();
    $mysheet->setActiveDirectory(realpath('./'));
    $mysheet->getAutoload()->registerAutoload();
    $settings = new MSSettings();
    $settings->set('cssRenderer', [
        'prefixRule' => '   ',
        'suffixRule' => ' /* this is a real CSS rule */',
        'sepSelectors' => ', ',
        'sepRules' => '; ',
        'prefixOCB' => ' ',
        'suffixOCB' => "\n",
        'prefixCCB' => "\n",
        'suffixCCB' => ''
    ]);
    …
    $mysheet->init($settings);
    

    Приведу таблицу всех возможных префиксов и суффиксов:
    Название Значение по-умолчанию Описание
    prefixRule 4 пробела Строка, вставляемая перед каждым правилом
    suffixRule Пустая строка Строка, вставляемая после каждого правила
    sepSelectors , Разделитель между селекторами
    sepRules ;\n Разделитель между правилами
    prefixOCB Пробел Строка, вставляемая перед открывающейся фигурной скобкой (OCB – opening curly bracket)
    suffixOCB \n Строка, вставляемая после открывающейся фигурной скобки
    prefixСCB \n Строка, вставляемая перед закрывающейся фигурной скобкой (CCB – closing curly bracket)
    suffixCCB \n Строка, вставляемая после закрывающейся фигурной скобки
    prefixAtRuleLine 4 пробела Строка, вставляемая перед каждой строкой внутри @-правила
    suffixAtRuleLine Пустая строка Строка, вставляемая после каждой строки внутри @-правила

  • Другие возможности
    В библиотеке есть и другие возможности и функции, которые я просто перечислю списком. К ним относятся:
    — Включение и отключение компиляции правила (через символ ~, добавляемый перед правилом)
    — Автоматическое встраивание мелких изображений в код CSS с помощью data: URL
    — Импортирование других MSS и CSS файлов с помощью директивы @ import (над этой возможностью я еще работаю; в частности, нужно добавить возможность задания опций импортирования)
    — Преобразование всех цветов к одному формату
    — 2 поддерживаемых языка для текста ошибок компиляции: английский (en_us) и русский (ru_ru)
    — Включение и отключение расширений парсера библиотеки (можно отключить поддержку функций, переменных, цветов и т.п.)

Как это было…


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


Рис. 1 – Моя любимая девчонка

С того самого момента, как я затеял свой мини-проектик, прошло уже немало времени. Возникало много разных сомнительных ситуаций и вопросов. И оно не мудрено – до этого я никогда ничего подобного не делал. Никакой специальной литературы по компиляторам я не читал и сначала делал все исключительно на свое усмотрение. Я не хвастаюсь, а даже наоборот говорю, что зря я этого не сделал. Возможно, так бы я избежал некоторых возникавших проблем.

Я почему-то сразу решил делать библиотеку в виде open-source проекта и сразу сказал себе, что одной гитхаб-странички будет недостаточно. После того как часть библиотеки была уже написана, я начал делать для нее сайт, чтобы в «режиме онлайн» находить дефекты и исправлять их, а также сразу на практике осознавать, что было бы приятно видеть конечному пользователю.

Название для своей библиотеки я придумывал, действуя от обратного. Я решил не отходить от примера самых известных на сегодняшний день препроцессоров SASS и LESS, и подумал, что MSS – неплохое сочетание букв, в конце концов. А чтобы название было запоминающимся, я решил назвать свой проект MySheet. И в аббревиатуру укладывается (MySheet Styles), и лёгкая изюминка в названии есть.

Название придумано, пора начинать проектировать корабль. Первое, что я начал делать – это был парсер исходного кода. Вот тут-то я и просчитался в первый раз. Я начал делать его без разбития исходного кода на токены и ключевые слова, и ориентировался на то, что в библиотеке будет фигурировать в-основном работа со строками. Конечно, минусы этого подхода я видел уже на этапе его выбора, но ничего лучшего, к сожалению, я придумать не смог. Уже потом, во время того как в университете у нас читался курс по компиляторам, я понял, что лучше было бы ввести этап предварительного лексического анализа. Хотя бы, потому что сейчас я столкнулся с проблемой распознавания и запоминания комментариев для последующего их вывода в скомпилированный код CSS. Или, например, теперь мне бы хотелось добиться нечувствительности расширений парсера (которые подключаются к библиотеки в виде дополнительных модулей) к наличию нежданных переносов строк и отступов (тех самых, которые часто добавляются для удобочитаемости кода). В ближайшем будущем, я хочу включить в парсер этап разбития на токены, что, по моему мнению, должно разрешить эти проблемы.

Идея с арифметикой цветов мне пришла в голову, когда я реализовывал поддержку математических выражений. Я подумал, что неплохо было бы иметь возможность осветления и затемнения цветов в дизайне сайта, чтобы подобрать сочетающийся цвет можно было без необходимости открытия color picker’а. Сейчас в библиотеке реализована работа с HSLA, RGBA, HEX и HTML форматами цветов. К каждому из цветов, заданных в данных форматах, можно добавить дельту любого канала из какого-либо другого формата цвета. Например, к цвету записанному как #000 можно добавить 255 пунктов синего канала и 40 пунктов зеленого, получив при этом цвет #0028ff. Арифметическое выражение будет выглядеть в данном случае следующим образом: #000 + 255b + 40g.

Реализовывая работу с цветами, я решил не изобретать свой велосипед и использовать уже существующую библиотеку MrColor (хотя без «допиливания» этой библиотеки не обошлось).

Кстати, в этот момент я задумался над оптимизацией библиотеки. В качестве профилировщика кода я использовал Webgrind. Не самый лучший профилировщик, но дело свое он делает, и он мне очень помог в обнаружении слабых мест в моем коде.

Хочу рассказать одну интересную штуку. Когда парсер в препроцессоре начинает парсить какое-либо правило, он проходит по всем зарегистрированным модулям и, грубо говоря, вызывает в каждом из них метод parse. А так как арифметическое выражение и, например, функция – две разные сущности, то библиотеке приходилось парсить одно и то же два раза. Мне это очень не нравилось, и в один прекрасный день я придумал решение. Когда парсер арифметического выражения обнаруживает, что перед ним все ж таки никакое не выражение, а простая функция, он не возвращает false, а возвращает этот самый объект функции. Тем самым я избавился от этого изъяна и увеличил производительность процесса парсинга исходного кода.

Еще хочу рассказать, как я делал свой собственный первый логотип. В поисках идеи для логотипа, я набрел на картинку с тюбиками краски, и подумал: «Тысяча чертей! Да это же просто замечательная идея!». Я посмотрел несколько уроков по рисованию в Фотошопе и, в итоге, у меня получилась вот такая клякса:


Рис. 2 – Моя клякса

Мои дальнейшие планы


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

Потом, я хочу пойти в сторону развития функционала по редактированию стилей и поиску блоков в коде MSS из бэкэнда. Например, можно добавить поддержку условных комментариев IE прямо в исходном файле (не знаю как вас, но меня всегда раздражало, что патчи для IE нужно сохранять и включать на страницу в виде отдельных файлов, тем более, если это всего полтора CSS-правила).

Если у кого-то есть какие-то ещё идеи по совершенствованию моего проекта, я всегда буду рад их услышать.

Вместо заключения


Ну вот, наверное, и всё, что я хотел бы рассказать в своей первой статье про мою библиотеку. Думаю, я смог немного прорекламировать мой продукт. Еще хотелось, так сказать, зафиксировать точку в разработке и получить отзывы о уже проделанной работе.

Буду благодарен вам за возможные советы и рекомендации, а также за всевозможную поддержку и просто теплые слова.

Если вам понравилась моя статья, то я обязательно буду писать ещё.

Ссылки


GitHub: https://github.com/Dobby007/mysheet
Оффициальный сайт: http://mss.flydigo.com/
Документация: http://mss.flydigo.com/docs

P.S. Я извиняюсь, но как кто-то заметил в комментариях — мой хостинг не выдержал напора. Про это я даже и подумать не мог. Сейчас попробуем что-нибудь сделать с этим…

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

P.P.P.S. Доступ к сайту будет полностью восстановлен после обновления всей цепочки DNS. Сейчас сайт доступен по адресу: dobby007_h5a5nu.radius-host.net

Similar posts

Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 64

    +3
    color #a50c5b - 50sat /* decrease saturation by 50% */
    background-color #a50c5b + 50lt /* make color lighter by 50 percent */
    
    Вот это ад.
      +4
      да, согласен, чем не устроил синтаксис less: lighten(#a50c5b, 10%)?
        0
        Ну вот вы захотите одновременно добавить яркости и подмешать красного цвета и у вас получится конструкция make_redder(lighten(#aaa, 10%), 10%). А так: #aaa + 10lt + 40r. По мне, такие выражения дают больше возможностей. Но, впрочем, для простых операций можно и функции сделать.
          +13
          Ваш синтаксис нарушает правило перестановки слагаемых, поэтому функции предпочтительнее.
            –2
            А зачем тут коммутативность?
              –2
              При том можно даже сделать синтаксис следующего вида: «rgb(10, 10, 10) + rgb(10, 10, 19) + red(10) — hue(20) — 20light» и будет тогда та самая коммутативность, не мозолящая глаз. А вообще, я считаю, что это можно рассматривать как неявное преобразование типов в арифметических выражениях в C++. Вы же не задумываетесь, что будет если написать следующее в Си: auto result = 3.5 + 2 + 3 — 2. Хотя на самом деле то компилятор в таком выражении учитывает приоритет двух операндов и приводит один из них к типу с высшем приоритетом. Так и здесь то же самое.
                0
                3.5 + 2 + 3 == 3 + 2 + 3.5
                Но:
                #000 + 10r + 10lt != #000 + 10lt + 10r
                Это ещё можно было бы сделать через символ умножения, если бы вы делали корректную операцию изменения яркости, а не линейную, но в текущем состоянии это очень и очень плохое решение.

                А ещё у вас hsla(45, 60%, 20%, 1) + 10lt не работает. И rgb(1,1,1) + rgb(1,1,1) тоже.
                  –1
                  #000 + 10r + 10lt != #000 + 10lt + 10r

                  А что же вы хотели получить? Одинаковый цвет? Вы суммируете каналы из разных форматов цвета, поэтому и получаете разные результаты. Попробуйте сделать эти же операции в той же последовательности на hslpicker.com и вы получите ровно то же самое.

                  Зато сравните результаты со следующими выражениями: #000 + 10r + 10g и #000 + 10g + 10r. Они будут одинаковыми.

                  Я так понимаю вам смущает знак "+"?

                  rgb(1,1,1) + rgb(1,1,1)

                  Такое и не будет работать, так как я реализовывал только суммирование цвета и какого-либо одного канала.

                  hsla(45, 60%, 20%, 1) + 10lt

                  Это глюки библиотеки MrColor, которые я пока что не успел преодолеть.
      +8
      Название на слух какое-то двусмысленное :) Это было так задумано?
        +1
        Да) Хотелось сделать его запоминающимся :)
        +2
        Mixin’ы — Ну, вот, не смог я написать это слово на русском

        Есть вполне употребимое слово «примеси».
          +7
          Вы же знаете о существовании Rework, Gonzales и PostCSS? Просто на всякий случай. Даже при написании велосипеда стоит оценивать конкурентов или просто аналогичные существующие решения.

          Следующий вопрос по префиксам. Эти примеры говорят о том, что всё плохо:

          @mixin filter-grayscale(percent)
              -webkit-filter: grayscale($percent);
              -ms-filter: grayscale($percent);
              -o-filter: grayscale($percent);
              filter: grayscale($percent);
          
          @mixin border-radius(topleft, topright)
              -webkit-border-radius $topleft $topright 4px 5px
              border-radius $arguments
          
          html {
              -ms-width: 50px !important;
              -moz-width: 50px !important;
              width: 50px !important;
          }


          Несуществующее свойство -o-filter, -webkit-border-radius в 2015 году, -ms-width? Писать префиксы руками сегодня — это очень странное решение. Но то, что вы их выносите в абстракцию, ничего, по сути, не меняет — они всё равно написаны руками. Префиксы нужно добавлять только а) существующие и б) на основе статистики — и это давно уже делает Автопрефиксер. То есть префиксы вообще не нужно писать, нужно только пропускать через него CSS-файл на выходе — он сам знает что нужно, что актуально и как правильно на основе базы Can I use. И конкурировать с этим, даже в рамках велосипеда, я бы не стал.
            –3
            Собственно говоря, во встроенной версии примеси filter-grayscale нет свойства -o-filter. И делал я его как раз на основе базы caniuse. Вы можете проверить это, удалив определение mixin'а из исходного кода. -ms-width был тоже придуман примера ради. А хотя, кто его знает — может быть IE поймет :)

            Автоматическое проставление префиксов не всегда спасает. Иногда нужно написать заплатку под конкретный браузер. Для того примеси и нужны. Например, Firefox начал поддерживать фильтры только с 35 версии. И чтобы сделать изображение черно-белым в нем, нужно включать svg-картинку.
              0
              > Автоматическое проставление префиксов не всегда спасает. Иногда нужно написать заплатку под конкретный браузер

              Автопрефиксер в настройках позволяет перечислить нужные браузеры, хоть самые устаревшие
                0
                Автопрефиксер не добавит вам фоллбэки. Поэтому примеси предпочтительней.
                  0
                  а что вы скажите на счет postcss? мне как-то этот подход нравится больше, когда мы просто прописываем пару строчек на JS, который пройдется по всем стилям и, согласно каким-то вашим правилам, добавит кастылей. В итоге у нас чистый код, примиси используются только там где это требуется, и пользователи старых браузеров довольны.

                  Хотя конечно все это дело вкуса. Но некоторые фэлбэки крайне не удобно делать через миксины, код стилей усложняется и поддерживать подобное бывает тяжко. Потому мне лично нравится именно тот подход, который я описал.
                    0
                    Да. Автоматическое проставление браузерных префиксов — вещь удобная. Здесь спору нет. Я хочу это хорошенько обдумать и добавить в мой препроцессор. Но нужно хорошо провести грань между примесями и автопрефиксизацией — когда необходимо использовать одно, а когда другое.

                    Я даже, честно говоря, не думаю, что нужно усложнять эту функцию правилами типа "> 0.2%" и т.п. Я склоняюсь к тому мнению, что в стилях для сайта нужно учитывать как можно больше браузеров и нужно делать стили совместимыми с большинством из них. Пусть это будет 0.1% посетителей вашего сайта — зато этот 0.1% не закроет вашу страницу после того как увидит что-то несуразное.
                      0
                      Мне нравится в postcss как раз таки противоположное — по умолчанию оно ничего не делает. А далее вы просто добавляете плагины, правила и т.д. и можно сделать хоть свой sass/less. А можно взять простенький препроцессор с поддержкой миксинов (я не отрицаю что они нужны, но я из обычно применяю не для хаков/префиксов, а для минимизации какой-то рутины. Спратять какой-нибудь страх и ужас. Ну и комбинировать поведение.

                      Лучше подумайте над тем, как добавить возможность расширять синтаксис вашего препроцессора без необходимость ковыряться в тоннах кода. Скажем node-visitor-ы свои добавлять и т.д.
                        0
                        Да, конечно. Для того плагины и нужны в моем препроцессоре.
                      0
                      Ну так Stylus так и работает: вы пишете «чистый код», который примесями разворачивается в лапшу — где-то просто префиксы добавляются, где-то транслируется в разные нотации, а где-то вообще разные правила вставляются. При этом вы не скованы синтаксисом CSS, а можете использовать всякие клёвые штуки типа вложенных правил, вложенных media-queries и прочего.
                      0
                      Для фоллбэков есть другие плагины для PostCSS, например, CSS Grace: github.com/cssdream/cssgrace
                        –1
                        Проблема фолбэков в их неуниверсальности. В разных проектах нужны разные виды фолбэков и многие из них специфичны для конкретных проектов. Например, на одном из проектов мне надо было реализовать прозрачность. Но для браузеров её не поддерживающих (старые версии ие) нельзя было применять фильтры, а необходимо было использовать упрощённый дизайн без прозрачности, что было не так красиво (фоновая картинка, если она была установлена, не просвечивала), зато не тормозило.
                          +1
                          Всё верно, именно поэтому фоллбэки не часть Автопрефиксера, так как у него нет API, чтобы их отменять. Но у остальных плагинов PostCSS такой API бывает.
                    +3
                    Меня всегда удивляли эти «примера ради», люди ведь копируют код не глядя, забывая читать описания. И потом вы видим <div class="button"> и прочие безумства. Так что осторожнее. А что касается Автопрефиксера, он позволяет гибко указывать любые целевые версии браузеров и, если нужно, форсить прямо в коде нужные заплатки и префиксы, почитайте об этом в документации.
                    0
                    Дак это вроде же пример, нет?

                    Понятно, что в примеси потом выносится лишь то, что решит фронтендщик потом, зачем придираться.
                    • UFO just landed and posted this here
                    +1
                    hostinger не выдержал напора :)
                      0
                      Я даже и не думал, что я в ближайшее время вылезу за пределы их ограничений. Придется, что-нибудь с этим сделать… Только пока что непонятно что…
                        0
                        У гитхаба есть возможность хостить статические сайты.
                          0
                          Да, но, к сожалению, у меня сайт не статический и поэтому пришлось искать что-то более подходящее.
                            0
                            Ясно. Обычно у таких вещей сайты статические. Возможно имеет смысл подумать об отказе от динамики в пользу статики. А пока взять какую нибудь не дорогую VDS.
                              0
                              Я делал сайт для демонстрации возможностей библиотеки, а это не сделаешь со статическим контентом. На моем текущем сайте можно протестировать библиотеку, а также увидеть «арифметику цветов» в боевом действии.
                                –1
                                Мне кажется вы не верно интерпретируете фразу «статический сайт». Можно было бы конвертацию вашего Г (простите, не удержался от дословного перевода) в css средствами браузера организовать.

                                p.s. поздравляю, у вас велосипед.
                                  –1
                                  Воу, не заметил что оно еще и на PHP… и всю-ду трейты…
                                    –4
                                    Мне даже вам сказать нечего. Вы даже статью не прочитали, а делаете выводы. Бог простит
                                      0
                                      И кстати дословный перевод вовсе не такой.
                                        0
                                        дословный перевод созвучной фразы.
                          0
                          У вас вместо сайта главная страница хостера открывается.
                            +5
                            Ну, конечно же, мне пришлось написать небольшой сайтик для презентации своего продукта (вы уже догадались, какой язык программирования был выбран мной для этой цели?).


                            Превышен Лимит Процессорной Памяти


                            Отличный выбор :)
                              0
                              Доступ к сайту будет полностью восстановлен после обновления всей цепочки DNS. Сейчас сайт доступен по адресу: http://dobby007_h5a5nu.radius-host.net
                              0
                              Но… Но… Но… зачем??

                              После двухдневного (а может и меньшего) просмотра результатов с гугла, я ничего интересного для себя не нашел.


                              Я, конечно, прощу прощения, но… ЧТО именно не нашлось интересного, что сподвигло писать свой компилятор?
                                –1
                                Во-первых, мне было интересно написать компилятор самому. Во-вторых, мне позарез как нужно было что-то, что позволило бы мне пройтись по файлику стилей и изменить семейство шрифтов на Arial для селектора body и цвет шрифта на #333. И желательно было бы не плодить технологии, а сделать все на родном PHP.
                                  0
                                  портировали бы postcss на php, было бы интереснее.
                                    –1
                                    Так сделайте, если вам это интереснее.
                                +1
                                border-radius с префиксами в примере — полный ужас :).

                                Ну и какой смысл сейчас делать препроцессоры, когда уже во всю развиваются постпроцессоры ;).
                                  0
                                  А что на счет иерархичности?
                                    0
                                    Вы имеете в виду древовидную структуру селекторов? Тогда, да. В MySheet есть данная возможность. В статье есть пример с этой функцией. Для наглядности приведу еще один:
                                    html
                                        height 100%
                                        body
                                            color #777
                                            height 100%
                                            font-family: 'Open Sans', sans-serif;
                                            .wrapper
                                                position relative
                                                min-height 100%
                                                #header
                                                    color #fff
                                                    background-color rgba(0, 0, 0, 60%)
                                                    #logo
                                                        float left
                                                        .title
                                                            padding 4px 5px
                                                            font-weight bold
                                                            font-size 14pt
                                                    #main-menu
                                                        overflow hidden
                                                        ul
                                                            float right
                                                            li
                                                                float left
                                                                padding 8px 6px
                                    

                                    компилируется в следующий CSS-код:
                                    html {
                                        height: 100%
                                    }
                                    
                                    html body {
                                        color: #777777;
                                        height: 100%;
                                        font-family: "Open Sans",sans-serif
                                    }
                                    
                                    html body .wrapper {
                                        position: relative;
                                        min-height: 100%
                                    }
                                    
                                    html body .wrapper #header {
                                        color: #ffffff;
                                        background-color: rgba(0, 0, 0, 0.6)
                                    }
                                    
                                    html body .wrapper #header #logo {
                                        float: left
                                    }
                                    
                                    html body .wrapper #header #logo .title {
                                        padding: 4px 5px;
                                        font-weight: bold;
                                        font-size: 14pt
                                    }
                                    
                                    html body .wrapper #header #main-menu {
                                        overflow: hidden
                                    }
                                    
                                    html body .wrapper #header #main-menu ul {
                                        float: right
                                    }
                                    
                                    html body .wrapper #header #main-menu ul li {
                                        float: left;
                                        padding: 8px 6px
                                    }
                                    
                                      0
                                      html body .wrapper #header #main-menu ul li
                                      

                                      я надеюсь что это только для примера и в реальной верстке подобного не будет.
                                        –1
                                        Вы знаете?.. Я так всегда пишу :) И этот код сейчас с моего сайта. И это работает!
                                          +1
                                          почитайте про БЭМ, про smacss и т.д… В конце концов разберитесь как браузер селекторы применяет (для каждого элемента справа на лево, так что указывая html в селекторах вы буквально заставляете браузер для каждого элемента все траверсить вверх по DOM. При том что у вас где-то по середине айдишник который уже должен быть только один на странице, и дальше писать вложенности смысла нет.
                                            0
                                            Я согласен насчет производительности. Даже не думал про нее. Я подумал, вы про именование и формат говорите, поэтому сказал, что всегда так пишу. Без препроцессоров это просто нереально поддерживать. Я никогда так не делаю, если пишу на чистом CSS.
                                          0
                                          А что вам не понравилось?
                                            +1
                                            попробуйте погуглить CSS performance optimisations. Узнаете много интересного.
                                              0
                                              Да дело даже не столько в производительности, сколько в излишней специфичности селекторов. Это как минимум не разумно и я не представляю как такую верстку поддерживать.
                                                0
                                                На уровне исходных стилей поддерживать легко. Переносишь блок стилей на уровень корневого элемента и получаешь совсем другие конечные селекторы. А на уровне HTML поддерживать точно также как и все остальное. Эти вещи поддерживать мало влияют друг друга в случае использования препроцессоров, поддерживающих древовидную структуру.
                                                  0
                                                  получаешь совсем другие конечные селекторы.

                                                  проблемы со специфичностью селекторов это не решает.

                                                  Что вы скажите о БЭМ как о другой крайности?
                                                    0
                                                    Вы ошибаетесь. Это напрямую влияет на специфичность селекторов.
                                                    О БЭМ я думаю как о решении, разработанном конкретной компанией под их задачи, с которыми мне еще не приходилось сталкиваться. Это нестандартный взгляд на вещи со своими плюсами и минусами.
                                                      0
                                                      Это напрямую влияет на специфичность селекторов.

                                                      Специфичность селекторов и есть проблемы в долгосрочной перспективе.

                                                      Это нестандартный взгляд на вещи со своими плюсами и минусами.

                                                      Рекомендую к просмотру доклад Вадима Макеева о различных методологиях верстки и про БЭМ в частности. Может будет интересно.
                                                  +1
                                                  мне стало плохо, когда я увидел два айдишника подряд. с еще тремя селекторами перед ними.
                                                  0
                                                  Про производительность я как-то даже не думал. Спасибо.
                                              0
                                              Да, именно это я и имел ввиду. Спасибо за пояснение. Как-то пропустил этот участок в статье.

                                              А есть ли поддержка «ссылок» на «текущий селектор» в дереве, как в less? Например:
                                              body {
                                                a {
                                                    color: red;
                                                    &:visited {
                                                         color: blue;
                                                    }
                                                    .some-parent & {
                                                         font-weight: bold;
                                                    }
                                                }
                                              }
                                              
                                                0
                                                Да. Только не на текущий, а на родительский селектор. Вот только ваш код моя библиотека не переваривает из-за неравномерно расставленных пробелов в строке. Будем разбираться. Попробуйте пока этот:
                                                body {
                                                    a {
                                                        color: red;
                                                        &:visited {
                                                             color: blue;
                                                        }
                                                        .some-parent & {
                                                             font-weight: bold;
                                                        }
                                                    }
                                                }
                                                
                                                  0
                                                  На mss.flydigo.com есть блок Try it now внизу страницы. Попробовать ввести свой код можете там.
                                            • UFO just landed and posted this here

                                              Only users with full accounts can post comments. Log in, please.