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

Комментарии (64)


  1. a553
    25.04.2015 10:37
    +3

    color #a50c5b - 50sat /* decrease saturation by 50% */
    background-color #a50c5b + 50lt /* make color lighter by 50 percent */
    
    Вот это ад.


    1. Torm
      25.04.2015 11:02
      +4

      да, согласен, чем не устроил синтаксис less: lighten(#a50c5b, 10%)?


      1. Dobby007 Автор
        25.04.2015 11:37

        Ну вот вы захотите одновременно добавить яркости и подмешать красного цвета и у вас получится конструкция make_redder(lighten(#aaa, 10%), 10%). А так: #aaa + 10lt + 40r. По мне, такие выражения дают больше возможностей. Но, впрочем, для простых операций можно и функции сделать.


        1. brainunit
          25.04.2015 15:49
          +13

          Ваш синтаксис нарушает правило перестановки слагаемых, поэтому функции предпочтительнее.


          1. vintage
            26.04.2015 12:16
            -2

            А зачем тут коммутативность?


            1. Dobby007 Автор
              26.04.2015 16:26
              -2

              При том можно даже сделать синтаксис следующего вида: «rgb(10, 10, 10) + rgb(10, 10, 19) + red(10) — hue(20) — 20light» и будет тогда та самая коммутативность, не мозолящая глаз. А вообще, я считаю, что это можно рассматривать как неявное преобразование типов в арифметических выражениях в C++. Вы же не задумываетесь, что будет если написать следующее в Си: auto result = 3.5 + 2 + 3 — 2. Хотя на самом деле то компилятор в таком выражении учитывает приоритет двух операндов и приводит один из них к типу с высшем приоритетом. Так и здесь то же самое.


              1. a553
                26.04.2015 17:09

                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. Dobby007 Автор
                  26.04.2015 17:41
                  -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, которые я пока что не успел преодолеть.


  1. twentyfivesymbolsusername
    25.04.2015 12:14
    +8

    Название на слух какое-то двусмысленное :) Это было так задумано?


    1. Dobby007 Автор
      25.04.2015 18:45
      +1

      Да) Хотелось сделать его запоминающимся :)


  1. pepelsbey
    25.04.2015 12:48
    +2

    Mixin’ы — Ну, вот, не смог я написать это слово на русском

    Есть вполне употребимое слово «примеси».


  1. pepelsbey
    25.04.2015 13:02
    +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. И конкурировать с этим, даже в рамках велосипеда, я бы не стал.


    1. Dobby007 Автор
      25.04.2015 13:46
      -3

      Собственно говоря, во встроенной версии примеси filter-grayscale нет свойства -o-filter. И делал я его как раз на основе базы caniuse. Вы можете проверить это, удалив определение mixin'а из исходного кода. -ms-width был тоже придуман примера ради. А хотя, кто его знает — может быть IE поймет :)

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


      1. dshster
        25.04.2015 14:40

        > Автоматическое проставление префиксов не всегда спасает. Иногда нужно написать заплатку под конкретный браузер

        Автопрефиксер в настройках позволяет перечислить нужные браузеры, хоть самые устаревшие


        1. vintage
          26.04.2015 12:20

          Автопрефиксер не добавит вам фоллбэки. Поэтому примеси предпочтительней.


          1. Fesor
            26.04.2015 13:20

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

            Хотя конечно все это дело вкуса. Но некоторые фэлбэки крайне не удобно делать через миксины, код стилей усложняется и поддерживать подобное бывает тяжко. Потому мне лично нравится именно тот подход, который я описал.


            1. Dobby007 Автор
              26.04.2015 16:09

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

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


              1. Fesor
                26.04.2015 16:18

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

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


                1. Dobby007 Автор
                  26.04.2015 16:29

                  Да, конечно. Для того плагины и нужны в моем препроцессоре.


            1. vintage
              26.04.2015 19:43

              Ну так Stylus так и работает: вы пишете «чистый код», который примесями разворачивается в лапшу — где-то просто префиксы добавляются, где-то транслируется в разные нотации, а где-то вообще разные правила вставляются. При этом вы не скованы синтаксисом CSS, а можете использовать всякие клёвые штуки типа вложенных правил, вложенных media-queries и прочего.


          1. Iskin
            26.04.2015 19:14

            Для фоллбэков есть другие плагины для PostCSS, например, CSS Grace: github.com/cssdream/cssgrace


            1. vintage
              26.04.2015 19:53
              -1

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


              1. Iskin
                26.04.2015 19:55
                +1

                Всё верно, именно поэтому фоллбэки не часть Автопрефиксера, так как у него нет API, чтобы их отменять. Но у остальных плагинов PostCSS такой API бывает.


      1. pepelsbey
        26.04.2015 01:54
        +3

        Меня всегда удивляли эти «примера ради», люди ведь копируют код не глядя, забывая читать описания. И потом вы видим <div class="button"> и прочие безумства. Так что осторожнее. А что касается Автопрефиксера, он позволяет гибко указывать любые целевые версии браузеров и, если нужно, форсить прямо в коде нужные заплатки и префиксы, почитайте об этом в документации.


    1. ionicman
      25.04.2015 14:29

      Дак это вроде же пример, нет?

      Понятно, что в примеси потом выносится лишь то, что решит фронтендщик потом, зачем придираться.


      1. SelenIT2
        25.04.2015 15:46
        +6

        Если примеры оторваны от реальности, это наводит на сомнения о применимости к реальности самого инструмента, увы..:(


  1. imgen
    25.04.2015 16:39
    +1

    hostinger не выдержал напора :)


    1. Dobby007 Автор
      25.04.2015 18:14

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


      1. Fedot
        25.04.2015 18:21

        У гитхаба есть возможность хостить статические сайты.


        1. Dobby007 Автор
          25.04.2015 18:29

          Да, но, к сожалению, у меня сайт не статический и поэтому пришлось искать что-то более подходящее.


          1. Fedot
            25.04.2015 19:15

            Ясно. Обычно у таких вещей сайты статические. Возможно имеет смысл подумать об отказе от динамики в пользу статики. А пока взять какую нибудь не дорогую VDS.


            1. Dobby007 Автор
              25.04.2015 22:02

              Я делал сайт для демонстрации возможностей библиотеки, а это не сделаешь со статическим контентом. На моем текущем сайте можно протестировать библиотеку, а также увидеть «арифметику цветов» в боевом действии.


              1. Fesor
                25.04.2015 22:15
                -1

                Мне кажется вы не верно интерпретируете фразу «статический сайт». Можно было бы конвертацию вашего Г (простите, не удержался от дословного перевода) в css средствами браузера организовать.

                p.s. поздравляю, у вас велосипед.


                1. Fesor
                  25.04.2015 22:20
                  -1

                  Воу, не заметил что оно еще и на PHP… и всю-ду трейты…


                1. Dobby007 Автор
                  25.04.2015 22:35
                  -4

                  Мне даже вам сказать нечего. Вы даже статью не прочитали, а делаете выводы. Бог простит


                1. Dobby007 Автор
                  25.04.2015 22:36

                  И кстати дословный перевод вовсе не такой.


                  1. Fesor
                    26.04.2015 11:21

                    дословный перевод созвучной фразы.


  1. franzose
    25.04.2015 17:49

    У вас вместо сайта главная страница хостера открывается.


  1. Posia
    25.04.2015 17:50
    +5

    Ну, конечно же, мне пришлось написать небольшой сайтик для презентации своего продукта (вы уже догадались, какой язык программирования был выбран мной для этой цели?).


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


    Отличный выбор :)


    1. Dobby007 Автор
      25.04.2015 21:25

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


  1. Jabher
    26.04.2015 10:39

    Но… Но… Но… зачем??

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


    Я, конечно, прощу прощения, но… ЧТО именно не нашлось интересного, что сподвигло писать свой компилятор?


    1. Dobby007 Автор
      26.04.2015 11:08
      -1

      Во-первых, мне было интересно написать компилятор самому. Во-вторых, мне позарез как нужно было что-то, что позволило бы мне пройтись по файлику стилей и изменить семейство шрифтов на Arial для селектора body и цвет шрифта на #333. И желательно было бы не плодить технологии, а сделать все на родном PHP.


      1. Fesor
        26.04.2015 11:22

        портировали бы postcss на php, было бы интереснее.


        1. Dobby007 Автор
          26.04.2015 17:45
          -1

          Так сделайте, если вам это интереснее.


  1. Iskin
    26.04.2015 19:13
    +1

    border-radius с префиксами в примере — полный ужас :).

    Ну и какой смысл сейчас делать препроцессоры, когда уже во всю развиваются постпроцессоры ;).


  1. POPSuL
    28.04.2015 16:52

    А что на счет иерархичности?


    1. Dobby007 Автор
      28.04.2015 18:49

      Вы имеете в виду древовидную структуру селекторов? Тогда, да. В 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
      }
      


      1. Fesor
        28.04.2015 18:58

        html body .wrapper #header #main-menu ul li
        

        я надеюсь что это только для примера и в реальной верстке подобного не будет.


        1. Dobby007 Автор
          29.04.2015 05:07
          -1

          Вы знаете?.. Я так всегда пишу :) И этот код сейчас с моего сайта. И это работает!


          1. Fesor
            29.04.2015 10:22
            +1

            почитайте про БЭМ, про smacss и т.д… В конце концов разберитесь как браузер селекторы применяет (для каждого элемента справа на лево, так что указывая html в селекторах вы буквально заставляете браузер для каждого элемента все траверсить вверх по DOM. При том что у вас где-то по середине айдишник который уже должен быть только один на странице, и дальше писать вложенности смысла нет.


            1. Dobby007 Автор
              29.04.2015 15:38

              Я согласен насчет производительности. Даже не думал про нее. Я подумал, вы про именование и формат говорите, поэтому сказал, что всегда так пишу. Без препроцессоров это просто нереально поддерживать. Я никогда так не делаю, если пишу на чистом CSS.


        1. Dobby007 Автор
          29.04.2015 05:07

          А что вам не понравилось?


          1. Jabher
            29.04.2015 08:02
            +1

            попробуйте погуглить CSS performance optimisations. Узнаете много интересного.


            1. Fesor
              29.04.2015 10:19

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


              1. Dobby007 Автор
                29.04.2015 15:33

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


                1. Fesor
                  29.04.2015 16:09

                  получаешь совсем другие конечные селекторы.

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

                  Что вы скажите о БЭМ как о другой крайности?


                  1. Dobby007 Автор
                    04.05.2015 17:21

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


                    1. Fesor
                      04.05.2015 19:06

                      Это напрямую влияет на специфичность селекторов.

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

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

                      Рекомендую к просмотру доклад Вадима Макеева о различных методологиях верстки и про БЭМ в частности. Может будет интересно.


              1. Jabher
                29.04.2015 18:44
                +1

                мне стало плохо, когда я увидел два айдишника подряд. с еще тремя селекторами перед ними.


            1. Dobby007 Автор
              29.04.2015 15:25

              Про производительность я как-то даже не думал. Спасибо.


      1. POPSuL
        29.04.2015 01:58

        Да, именно это я и имел ввиду. Спасибо за пояснение. Как-то пропустил этот участок в статье.

        А есть ли поддержка «ссылок» на «текущий селектор» в дереве, как в less? Например:

        body {
          a {
              color: red;
              &:visited {
                   color: blue;
              }
              .some-parent & {
                   font-weight: bold;
              }
          }
        }
        


        1. Dobby007 Автор
          29.04.2015 05:21

          Да. Только не на текущий, а на родительский селектор. Вот только ваш код моя библиотека не переваривает из-за неравномерно расставленных пробелов в строке. Будем разбираться. Попробуйте пока этот:

          body {
              a {
                  color: red;
                  &:visited {
                       color: blue;
                  }
                  .some-parent & {
                       font-weight: bold;
                  }
              }
          }
          


        1. Dobby007 Автор
          29.04.2015 05:26

          На mss.flydigo.com есть блок Try it now внизу страницы. Попробовать ввести свой код можете там.


  1. Grawl
    02.05.2015 18:35

    Идея с флагами действительно интересная. Пока видел только “!default” в Sass.