bem-site / bem-forum-content-ru

Content BEM forum for Russian speak users
MIT License
56 stars 6 forks source link

Problems? POBEMS! PostCSS плагин для BEM #1061

Open belozer opened 8 years ago

belozer commented 8 years ago

Всем привет. Благодаря репозиторию @tadatuta наткнулся на интересный плагин к PostCSS - rebem-css. Изначально показался он показался менее удобным, чем написание по-старинке.

// по-старинке
.object-item
  &_status_bad
    .object-item__extended
      background: #000

// rebem-css
:block(object-item)
  &:mod(status bad)
    :block(object-item):elem(extended)
      background: #000

Проблема 1

Подсветка синтаксиса при таком написание режет глаза...

Проблема 2

Код становится ещё длиннее... Утешает только то, что можно применять bem-naming

Проблема 3

Писали, что такой синтаксис ближе к BEMHTML. Но как по мне, он не дотягивает немного.

В итоге я решил сделать PR. В репозиторий rebem/css, переписав практически весь плагин. Но т.к. на слитие нужно время, а мне он нужен уже сейчас, был сделан новый плагин с расширенным функционалом на основе rebem-css.

POBEMS поддерживает как синтаксис rebem-css, так и свой.

Решение 1-й проблемы

Добавлена возможность написания через кавычки

:block('object-item')

Решение 2-й проблемы

Добавлен контекст блока при объявлениях элементов и модификаторов. Теперь можно писать так

:block('block')
  &:mod('mod val')

    :elem('elem1')
      background: #000

    :elem('elem2'):mod('mod val')
      width: 10px

      :elem('elem3')

.block_mod_val .block__elem1 
.block_mod_val .block__elem2_mod_val .block__elem3

Решение 3-й проблемы

Для объявления модификаторов добавил более привычный синтаксис через запятую :mod('mod', 'value')

:block('block')
  &:mod('mod' -> 'val')

    :elem('elem1')
      background: #000

    :elem('elem2'):mod('mod' -> 'val'):mod('mod2' -> 'val2')
      width: 10px

.block_mod_val .block__elem1 
.block_mod_val .block__elem2_mod_val.block__elem2_mod2_val2

Или в LESS стиле

:block('block') {
    &:mod('mod', 'val') {
        :elem('elem1') { }
        :elem('elem2'):mod('mod', 'val'):mod('mod2', 'val2') {}
    }
}

Что дальше...

Далее часть изменений отправлю в репозиторий rebem/css, а в pobems буду добавлять более "горячие" нововведения. Репозиторий POBEMS

tadatuta commented 8 years ago

Мы у себя сошлись на rebem-css + postcss-nested, что в итоге позволяет писать полностью валидный sass, так что с подсветкой все хорошо во всех редакторах (и даже на github):

:block(afisha) {
    padding-bottom: 40px;

    color: #f2f2f2;
    background: #800;
    text-shadow: 0 0 5px rgba(255, 255, 255, 0.5);

    &:before {
        background: linear-gradient(to right, rgb(136, 0, 0), rgba(136, 0, 0, 0));
    }

    &:after {
        background: linear-gradient(to left, rgb(136, 0, 0), rgba(136, 0, 0, 0));
    }

    &:elem(title) {
        position: relative;
        z-index: 3;
    }

    &:elem(movie) {
        position: relative;

        width: 130px;
        margin-right: 9px;
        padding: 5px 0 0 5px; /* In order to move 'premiere' out-of-box */

        text-align: left;
        vertical-align: top;

        color: #fff;

        &:first-child {
            margin-left: 15px;
        }

        &:last-child {
            margin-right: 20px;
        }
    }

    &:elem(premiere) {
        position: absolute;
        top: 1px;
        left: 0;

        padding: 5px 8px;

        color: rgba(0, 0, 0, 0.6);
        background: #fc0;
        text-shadow: none;
    }

    &:elem(premiere):first-letter {
        text-transform: capitalize;
    }

    &:elem(premiere):after {
        position: absolute;
        left: 0;
        bottom: -5px;

        width: 5px;
        height: 5px;

        content: '';

        background: 0 0 no-repeat url(afisha__premiere.svg);
    }

    &:elem(poster) {
        width: 130px;
        height: 195px;
        margin-top: 1px;
    }

    &:elem(name) {
        font-size: 17px;
        line-height: 21px;

        display: -webkit-box;

        margin-top: 3px;

        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: normal;
    }

    &:elem(genre) {
        margin-top: 4px;

        color: #fff;
        opacity: .5;
    }

    &:elem(shadow) {
        position: absolute;
        z-index: 2;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;

        pointer-events: none;

        box-shadow: inset 0 0 80px rgba(0, 0, 0, 0.5);
    }
}
belozer commented 8 years ago

Меня rebem-css большего всего раздрожал постоянным дублированием объявления блока у элементов, когда элементы зависят от модификатора блока (или 2-х модификаторов). А в целом быстро на него перешёл. Код стал более понятным сходу.

belozer commented 8 years ago

Обновил плагин, добавил поддержку разделителя '->' модификатор -> значение специально для stylus, т.к. он запятую не так как надо обрабатывает.

:block('checkbox'):mod('theme' -> 'dark')

  :elem('box')
    background: #353535
    border: 3px solid #ffe93b

  :elem('control')
    position: absolute
    z-index: -1
awinogradov commented 8 years ago

@belozyorcev а зачем тебе stylus? В postcss есть все необходимые плагины ;)

a-x- commented 8 years ago

А может быть так? (замутить плагин поверх postCss sugarSs)

.block
    ._mod_val
        ._mod2_val2
            foo: bar
    .block2_mod
        baz: qux

Давно так хочу сделать.

belozer commented 8 years ago

@awinogradov Куча кода уже была на stylus написана. Лень переписывать. В будущем буду от него уходить.

@a-x- мало знаком с sugarSS.

Плагин работает с такими строками, которые генерируются после: postCSS nested / Stylus / Less и т.д.:

:block('bl1'):mod('mod', 'v'):mod('mod2', 'v2') :elem('elem')
:block(bl1):mod(mod v):mod(mod2 v2) :elem('elem')

.bl_mod_v.bl_mod2 .bl__elem
belozer commented 8 years ago

Думаю ещё реализовать более "чистый" синтаксис:

block('header') {
  width: 100%
  background: var(--color-base1)
  position: relative
  lost-utility: clearfix

  elem('logo-box') {
    lost-column: 3/12
  }

  elem('main').mod('list', true) {
    mod('m', 'v') {

    }
    lost-column: 9/12
    height: 110px
    background: var(--color-base2)

    elem('next-child') {
      color: #422;
    }
  }
}

block('header').elem('main').mod('list', true) {
  block('icon') {}
}
tadatuta commented 8 years ago

@belozyorcev последний вариант выглядит симпатично. если будет нормально поддерживать в том числе всякие псевдоселекторы и вложенные селекторы, то 👍. ну и кавычки для имен сущностей нужно делать опциональными, не вижу смысла их писать в CSS.

belozer commented 8 years ago

@tadatuta они опциональны. И разделитель между mod - val тоже. Может использоваться либо mod, val, либо mod -> val (для Stylus). Или же в стиле rebem-css - пробел mod val

upd. Мне кавычки добавляют более привычный синтаксис для моего восприятия и идентичности с BEMHTML.

По поводу псевдо-селекторов и вложенных селекторов уже на данной стадии всё работает. Эти моменты покрыты тестами. pobems v0.3.1 полностью совместим с rebem-css v0.2.0. https://github.com/belozyorcev/pobems/blob/master/test/lib/index.js#L46 https://github.com/belozyorcev/pobems/blob/master/test/lib/index.js#L53

belozer commented 8 years ago

@a-x- SugarSS на сколько мне известно эмитирует Stylus (со слов Андрея Ситника). Значит можно попробовать так:

:block(block)
    &:mod(mod val)
        &:mod(mod2 val2)
            foo: bar
    :block(block2):mod(mod)
        baz: qux

на выходе будут селекторы

.block_mod_val.block_mod2_val2
.block .block2_mod
a-x- commented 8 years ago

А точно нельзя избавиться от всего этого шума :block :elem и сделать плагин чтобы записывать так, как я выше показал?

belozer commented 8 years ago

@a-x- можно, но это другая история. Целью данного плагина является изоляция от стиля именования блоков и добавление большей идентичности с BEMHTML, BEMTREE.

Начиная с версии 1.0.0 можно будет писать так

block(block)
    mod(mod val)
        mod(mod2 val2)
            foo: bar
    block(block2).mod(mod)
        baz: qux

ну или так

block(block) {
    mod(mod val) {
        mod(mod2 val2) {
            foo: bar
        }
    }
    block(block2).mod(mod) {
        baz: qux
    }
}

или так

block('block') {
    mod('mod', 'val') {
        mod('mod2', 'val2') {
            foo: bar
        }
    }
    block('block2').mod('mod') {
        baz: qux
    }
}

и это не предел )

belozer commented 8 years ago

Также думаю над нужностью/ненужностью короткого синтаксиса.

b(block) {
    m(mod, val) {
        m(mod2, val2) {
            foo: bar
        }
    }
    b(block2).m(mod) {
        baz: qux
    }
}
vithar commented 8 years ago

Может:

.block.mod_val {
    .elem.mod_val {
    }
}
vithar commented 8 years ago

Или

block-name:mod-name(mod-val) {
    elem-name:mod(val) {
    }
}
awinogradov commented 8 years ago

Внесу таки 5 копеечек)

Мне кажется такой вариант оптимальным:

block('block').mod('mod', 'val') {
}

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

Guria commented 8 years ago

А ещё можно моду css в bemhtml, чтобы само в файл эмитилось :)

belozer commented 8 years ago

@vithar при таком стиле могут возникнуть проблемы при парсинге, если в проекте используется что-то помимо БЭМ.

vithar commented 8 years ago

Ну для варианта, когда используется кто-то кроме БЭМ, ты уже сделал.

vithar commented 8 years ago

Я ищу наиболее лаконичный вариант писать именно BEM CSS код, чтобы он был максимально близок к валидному CSS, при этом легко читался.

Например так:

informers { /* block */
    padding: 30px 0 20px;

    :(size large) { /* modifier */

    }

    informer { /* element */
        position: relative;

        @tablet, @phone { /* predefined media query */
            display: none;
        }

        :first-child { /* pseudo-class */
            margin-left: 19px;
        }
    }

    icon {
        position: absolute;
    }

    text {
        margin-top: 8px;

        :(important) { /* element modifier */
            font-size: 17px;
        }
    }
}
vithar commented 8 years ago

@awinogradov ни разу не сталкивался с необходимостью переносить селекторы из bemhtml в css и обратно.

belozer commented 8 years ago

@vithar сейчас я тебя больше понимаю.

belozer commented 8 years ago

@vithar думаю такое можно аккуратно реализовать , если скармливать плагину список блоков в проекте.

awinogradov commented 8 years ago

@vithar понятное дело) у тебя такой возможности не было) а теперь вот есть

belozer commented 8 years ago

Я так подумал... Синтаксис, который предложил @vithar будет прививать правильное написание стилей.

Если надо сдвинуть блок - сделай его элементом родителя и двигай в нём.

т.е. так правильней

.block2 {
  width: 100px;
  height: 100px;
}

.block1__item {
  margin-left: -100px
}

чем

.block1 .block2 {
  margin-left: -100px
}

в итоге

block1 {
  item {
    margin-left: -100px
  }
}

объявление блока происходит только на 1-ом уровней, дальше - всё элементы.

vithar commented 8 years ago

Список блоков не надо скармливать, первый уровень — блоки.

belozer commented 8 years ago

@vithar да, я тебя просто не сразу понял.

vithar commented 8 years ago

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

Можно, кстати, так

block-name another-block {
}

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

belozer commented 8 years ago

при склеивании селекторов получается

block1 item {}

и тут не понятно... Элемент это или блок.

Единственный вариант я вижу - это указание класса

block1 .block2

vithar commented 8 years ago

В общем про это можно думать отдельно и может даже сделать отдельный синтаксис, это же редкий кейс.

belozer commented 8 years ago

А ещё можно имена блоков с заглавной обозначать

Block1 {
  item {
    margin-left: -100px
  }
}
belozer commented 8 years ago

Block { // .block
  :active: { // .block_active
    ..div {  //.block_active div

    }
    .boot {  //.block_active .boot

    }
  }

  :type:list { // .block_mod_val
    item { //.block_mod_val .block__item

    }
  }

  item { // .block__item
    Block2 { //.block__item  .block2
      item { // .block__item .block2__item

      }
    }
  }
}
belozer commented 8 years ago

Правда так читается более явно

block('block').mod('mod', 'val') {
}
vithar commented 8 years ago

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

belozer commented 8 years ago

ну можно например так

block { // .block
  :active: { // .block_active
    ..div {  //.block_active div

    }
    .boot {  //.block_active .boot

    }
  }

  :type:list { // .block_mod_val
    item { //.block_mod_val .block__item

    }
  }

  item { // .block__item
    >block2 { // .block__item .block2
      item { // .block__item .block2__item

      }
    }
  }
}
tadatuta commented 8 years ago

tadatuta commented 8 years ago

Я, конечно, и сам грешу всякими https://github.com/tadatuta/bem-indent-syntax, но не нужно экономить символы в ущерб очевидности кода и гибкости. Лень лишний раз нажать на кнопку — запилите лайв-темплейты.

Всегда ваш, горький опыт.

belozer commented 8 years ago

Скоро приступлю над разработкой версии 1.0.0.

block(bl).elem(el).mod(m v)  {
  mod(m2 v) {}
  elem(elem2) {}
}

стоит ли добавлять в него short-syntax?

b(block) {
    m(mod, val) {
        m(mod2, val2) {
            foo: bar
        }
    }
    b(block2).m(mod) {
        baz: qux
    }
}
a-x- commented 8 years ago

стоит ли добавлять в него short-syntax?

awinogradov commented 8 years ago

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

@vithar Что это значит?

Чем это:

block(bl).elem(el).mod(m v) {
}

Консистентнее чем это:

block('block').mod('mod', 'val') {
}
belozer commented 8 years ago

@awinogradov синтаксис с ковычками тоже будет, просто они опциональны.

a-x- commented 8 years ago

@vithar, Мне кажется, что наделять индентацию БЭМ-смыслом плохо, т.к. у неё уже есть смысл в рамках css-процессора (stylus, postcss+sugarss, etc.).

Часто будет хотеться сделать например так:

.b-page
    .button2
        foo: bar
    .radiobox
        baz: qux

Поэтому я и предлагаю так (с подчёркиваниями):

.block._mod1_val1 {
    .__elem._mod2_val2 {
        // ...
        ._mod3_val3 {
            ...
        }
    }
}
a-x- commented 8 years ago

А консистентная с bemhtml форма мне тоже очень понравилась сейчас. Где-то можно будет ей пользоваться.

p.s. забегая вперёд, думаю что короткий по inv* синтаксис не постигнет судьба dsl-bemhtml, т.к. для транспиляции .block(block).elem(elem) нужно будет примерно столько же времени, как и .block.__elem

belozer commented 8 years ago

@a-x- проблема в переносимости блоков. Многим нравится стиль Гарри Роббертса.

block-name__elem-name--mod-name

А так у всех один инструмент(шаблонизатор CSS) и в своих проектах они используют нужный им синтаксис. При этом блоки могут использоваться в проектах с историческим синтаксисом БЭМ block-name__elem-name_mod-name и наоборот

a-x- commented 8 years ago

Конвертор легко сделать

Внутри проекта вряд ли кто-то будет смешанные стили нейминга использовать

p.s. + адаптеры к будущему плагину.

belozer commented 8 years ago

@a-x- в целом выглядит интересно. Но воспринимается тяжелей. Точнее завязка идёт на синтаксисе, а не на декларациях. Визуально мыслишь классами, а не сущностями. Коротко - да, быстро - да. Но именно идея деклараций блоков теряется.

классы

.block {
  .__elem {}
  ._mod_val {
    width: 100px;
    ._active {
      .__elem5 {}
    }
  }
  .__elem1 {}
  .__elem3 {}
}

.block__elem {
  width: 10px;
  ._mod {
    ._mod2 {
      ._mod3 {}
    }
  }
}

декларации

block(block) {
  elem(elem) {}
  mod(mod val) {
    width: 100px;
    mod(active) {
      elem(elem5) {}
    }
  }
  elem(elem1) {}
  elem(elem3) {}
}

block(block).elem(elem) {
  width: 10px;
  mod(mod) {
    mod(mod2) {
      mod(mod3) {}
    }
  }
}
belozer commented 8 years ago

@a-x- вообще в идеале сделать 2 подхода. И там уже какой победит.

qfox commented 8 years ago

А вы правда у себя в проектах используете стили на теги? А посчитайте селекторы с тегами?

vithar commented 8 years ago

но не нужно экономить символы в ущерб очевидности кода и гибкости

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

vithar commented 8 years ago

Часто будет хотеться сделать например так:

.b-page
    .button2
        foo: bar
    .radiobox
        baz: qux

Ни разу так не хочется делать. Блок описывает себя и свои элементы и в идеале не трогает другие блоки.