Следуете ли вы БЭМу, и насколько он востребован вне Яндекса?

БЭМ расшифровывается как: блок, элемент, модификатор. Это такой набор абстракций, на который можно разбить интерфейс и разрабатывать всё в одних и тех же терминах. Как говорит сайт bem.info, БЭМ предлагает единые правила написания кода, помогает его масштабировать и повторно использовать, а также увеличивает производительность и упрощает командную работу.


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


В основу того, что мы сейчас называем БЭМом, легла идея независимых блоков, которую Виталий Харисов сформулировал и презентовал в 2007 году на первой российской конференции по фронтенду. Это было настолько давно, что тогда даже слова «фронтенд» ещё не было, тогда это называлось клиент-сайд.


Идея была в том, чтобы ограничить возможности CSS для более предсказуемых результатов. Использовать минимум глобальных стилей и каждый отдельный элемент страницы делать блоком со своим уникальным классом и стилями, которые полностью его описывают. Селекторы по элементам и ID, хрупкие связки вложенности — всё это заменялось на простые селекторы по классам. Каждый класс в стилях — это блок. Благодаря этому блоки можно легко менять местами, вкладывать друг в друга и не бояться конфликтов или влияния.


#menu ul > li {
  color: old;
}
.menu-item {
  color: bem;
}

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


.block {}
.block_mod {}
.block__element {}
.block__element_mod {}

Эта многословность гарантирует уникальность элементов и модификаторов в рамках одного проекта. За уникальностью имён блоков вы следите сами, но это довольно просто, если каждый блок описан в отдельном файле. Глядя на такой класс в HTML или CSS, можно легко сказать, что это, и к чему оно относится.


Изначально АНБ, а потом и БЭМ, решали задачу важную для вёрстки любых масштабов: предсказуемое поведение и создание надёжного конструктора. Вы же хотите, чтобы ваша вёрстка была предсказуемой? Вот и Яндекс тоже. Ещё это называется «модульность» — о ней хорошо написал Филип Уолтон в «Архитектуре CSS», ссылка на перевод в описании.


Через пару лет в Яндексе окончательно сформулировали нотацию БЭМ. Любой интерфейс предлагалось разделять на блоки. Неотделимые части блоков — элементы. У тех и других есть модификаторы.


<ul class="menu">
  <li class="menu__item"></li>
  <li class="menu__item menu__item_current"></li>
</ul>

Например, блок поиска по сайту. Он может быть в шапке и в подвале — значит это блок. В нём есть обязательные части: поле поиска и кнопка «найти» — это его элементы. Если поле нужно растянуть на всю ширину, но только в шапке, то блоку или элементу, который отвечает за это растягивание, даётся модификатор.


Для полной независимости блоков мало назвать классы правильно или изолировать стили, нужно собрать всё, что к нему относится. В проекте по БЭМу нет общего script.js или папки images со всеми картинками сайта. В одной папке с каждым блоком лежит всё, что нужно для его работы. Это позволяет удобно добавлять, удалять и переносить блоки между проектами. Потом, конечно, все ресурсы блоков при сборке объединяются в зависимости от задач проекта.


Когда БЭМ вышел за пределы Яндекса, сначала его воспринимали как магию и старались воспроизводить дословно, вплоть до префиксов b- у каждого блока. Такой смешной карго-культ.


.b-block {}
.b-block--mod {}
.b-block__element {}
.b-block__element--mod {}

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


.block {}
.block--mod {}
.block__element {}
.block__element--mod {}

А зачем вообще все эти нотации — я ведь один верстальщик на проекте, помните? Помню. А ещё помню, как сам верстал до БЭМа: в каждом проекте придумывал что-нибудь такое новенькое. А потом открывал код годичной давности и удивлялся — какой идиот это написал?


Возьмём, к примеру, русский язык. Мы же пишем с прописной имена людей, названия и прочее такое? Пишем. Чтобы легко потом считывалось: это Надя или надежда на лучшее? Уж не говоря про знаки препинания и другие полезные договорённости. Вот буква «ё», например, смягчает… так, о чём мы? Да, БЭМ.


До БЭМа были проекты с портянками стилей, которые нельзя трогать. Они копились годами, слой за слоем, как обои в древней коммуналке. Их просто подключали первыми, а потом перезаписывали что мешало. Когда у вас есть div { font-size: 80% } — это даже уже не смешно.


/* Не смешно */

div {
  font-size: 80%;
}

БЭМ продолжил развиваться в Яндексе и вырос за пределы вёрстки: появились уровни переопределения, богатый инструментарий, JS-библиотека для работы с БЭМ-классами, шаблонизаторы и целый БЭМ-стэк. БЭМу даже жест придумали, но это совсем другая история, специфичная для Яндекса.


Были и другие методологии: OOCSS Николь Салливан, SMACSS Джонатана Снука, вариации БЭМа и целые фреймворки. Даже у нас на продвинутом интенсиве можно было выбрать любую методологию для вёрстки учебного проекта.


Но выжил только БЭМ и его вполне можно назвать стандартом де-факто современной разработки интерфейсов. И что — это финальная ступень эволюции? Нет конечно. Как ни автоматизируй, многое в БЭМе приходится делать руками, и возможны конфликты.


Модульность и изоляцию стилей блока можно решить ещё лучше. Есть веб-компоненты, CSS-модули, куча решений CSS-в-JS и даже, прости господи, атомарный CSS. Но нет единого решения накопившихся проблем на следующие 10 лет, каким когда-то стал БЭМ. Что это будет? Посмотрим. Я ставлю на веб-компоненты.


А пока, если вы опишете интерфейс по БЭМу — код будет организован, предсказуем, расширяем и готов для повторного использования. Не только для вас, но и для других.


Видеоверсия



Вопросы можно задавать здесь.

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


  1. Aingis
    20.09.2017 19:38
    +8

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


  1. Varim
    20.09.2017 19:44

    блок отделяется двумя подчёркиваниями, а модификатор — двумя дефисами.
    .block {}
    .block--mod {}
    .block__element {}
    .block__element--mod {}

    Элемент, а не блок, отделяется двумя подчеркиваниями?
    Может я не прав, но поскольку определяется модификатор «block__element--mod», то возможно правильно написать — "имя элемента отделяется двумя подчёркиваниями, а имя модификатора — двумя дефисами"

    https://ru.bem.info/methodology/quick-start/#Модификатор
    В документации, в примерах, пишут без дефиса.
    Меньше путаницы — легче новичкам.
    Лучше бы либо доку переписать либо в учебных статьях подправить.

    Подскажите, в «search-form search-form_focused», имя модификатора это «search-form search-form_focused» или «search-form_focused»?


    1. Varim
      20.09.2017 20:07

      del


    1. Costigans
      21.09.2017 11:46

      В данном примере названием модификатора выступает «search-form_focused».


      1. korovnikiss
        21.09.2017 15:48

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


    1. vithar
      23.09.2017 09:40

      Тут имя модификатора — focused


  1. Reey
    20.09.2017 19:57
    +14

    Для меня "ave" моментом помню стало, когда мне объяснили, что не обязательно соблюдать иерархию ноды. Это вообще, как я понял, один из самых путающих моментов для новичков.
    Раньше я думал, что БЭМ это:


    <div class="note">
        <div class="note__sidebar">
            <div class="note__sidebar__title">
                <img class="note__sidebar__title__icon"/>
            </div>
        </div>
    </div>

    И думал что это какая-то фигня, а потом я понял, что надо так:


    <div class="note">
        <div class="note__sidebar">
            <div class="note__title">
                <img class="note__icon"/>
            </div>
        </div>
    </div>

    И я прозрел


    1. psFitz
      22.09.2017 10:54

      О да, помню как первый раз услышал о БЭМ и писал классы длиной в киллометры)


    1. Headmaster11
      22.09.2017 11:37

      Теперь и я прозрел. Спасибо.

      Удивительно, как такой лаконичный пример никто раньше не привёл


    1. Shifty_Fox
      22.09.2017 23:51

      Я тоже в свое время прозрел на этот счет
      Отдельно добавлю, что если все таки нужно положить элемент в элемент, то для этого можно использовать одинарное подчеркивание. В связи с этим и удобно использовать именно — как разделитель для модификатора:
      note__sidebar_title--size_m


      1. vithar
        23.09.2017 09:42

        Мы для отделения слов внутри имени используем -, а не _


        1. Shifty_Fox
          24.09.2017 17:28

          Тогда все сольется в один большой элемент
          front-page__section-promo-subinfo-title
          против
          front-page__section-promo_subinfo-title
          или даже
          front-page__section-promo_subinfo_title
          Необходимость возникает когда title в блоке действительно не один, а делить все на микроблоки уже слишком


  1. jakobz
    21.09.2017 02:29

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

    А мы не так делаем. Мы разделяем «внешние свойства» — все что за border-ом — типа padding, position, width; и «внутренние свойства» — все остальные. Блок верстается резиновым, и не задает сам «внешние» свойства. А его потребителям — позволено невозбранно навешивать на чужой блок селекторы своих элементов — которые его ставят как им надо. Очень удобственно. Ну типа так:

    <div class='my-panel'> <!-- .my-panel { display: flex } -->
      <!-- .my-panel__item { flex: 1 1 auto; margin: 10px; } -->
       <div class='my-panel__item ui-button'>
         <div class='ui-button__caption'>HI</div>
       </div>
    </div>
    


    Ну и CSS Modules пробуем. Есть и плюсы, и минусы.


    1. Varim
      21.09.2017 06:59

      навешивать на чужой блок селекторы своих элементов
      Не могли бы пояснить что это значит, JavaScript пишет типа block.className += " button_selector"?
      Или копируется разметка, затем верстальщик ручками вписывает нужные селекторы?
      Может вы о чем то другом.


      1. jakobz
        21.09.2017 11:02

        Суть не в том, как именно class навешивается. Суть в том, что есть договоренность:
        — компонент (блок) сам себе не задает margin/position/flex/width
        — компонент верстается так, чтобы если его растянули — он бы нормально выглядел
        — потребитель компонента сам вешает ему margin/flex/и т.п., чтобы его как нужно вставить

        Т.е. если у нас кнопочка — она без паддингов и резиновая. Если кнопочку надо вставить и, скажем, растянуть пошире и отодвинуть справа — мы на нее вешаем flex и padding снаружи. Можно классом, можно инлайном — тут не суть. Это, вроде как, не чистый BEM, но нам так сильно проще жить.


        1. vithar
          23.09.2017 09:44

          Это чистый БЭМ, называется миксы и так и рекомендуется делать в документацмм :


          https://ru.bem.info/methodology/css/#Миксы


  1. Fibril
    21.09.2017 04:00
    +1

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


    1. anttoshka
      21.09.2017 09:43

      БЭМ решает проблему того, что в процессе разработки вдруг необходимо на страницу добавить блок, который есть на соседней. А этот блок разработчик мог написать с селектором типа .my-page .my-block и в итоге нужно или на новой странице добавлять класс .my-page, что может дать проблемы или оставлять в стилях только .my-block, но тогда его стили могут уже как-то переопределяться в будущем. Разработка же не заканчивается на этом.
      Кроме того одним махом решается вопрос поддержки — намного проще изменить внутри блока какой-то элемент зная, что он точно изменится везде, а не нужно перепроверять все страницы, где этот элемент может быть.


      1. W3C
        21.09.2017 11:46

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


        1. Shifty_Fox
          22.09.2017 23:56

          БЭМ задает удобные и простые правила как верстать _все_ компоненты полностью самостоятельными и независимыми из коробки


    1. jt3k
      21.09.2017 11:46

      Это не надуманная проблема если в твоей жизни была фирма с общим фирменным стилем на всех сайтах.


  1. neurocore
    21.09.2017 11:46

    Пока что единственные 2 момента меня останавливают от использования БЭМа:
    1. Трудно читаемый код с такими длинными классами (в частности html-код)
    2. Нет проектов с более, чем 2-мя верстальщиками


    1. jakobz
      21.09.2017 15:42
      -1

      Там тулинг же надо, руками это писать — мазохизм. В LESS и прочих — дублирование убирается через вложенность правил. А в шаблонах или коде — тоже придумываются хелперы. Скажем, в реакте у нас:
      const b = new Bem('my-button'); // один раз вверху модуля

      b.div({ element: 'caption', mods: [isRed && 'red'] }); // my-button__caption my-button--red -->

      У нас нет выделенных верстаков вообще, разработчики всё делают — и JS, и верстку. И всем удобно. Правда, мы уже теперь на CSS-модули переезжаем — они фичи БЭМ-а перекрывают, но суть та же.


  1. igontarev
    21.09.2017 11:46
    +1

    идея бема хорошая, а реализация неудобная, вот почему бы не договориться, что модификаторы должны содержать глаголы is, has, и не использоваться самостоятельно, тогда можно писать типа

    .button.is-disabled {}
    

    <button class="button is-disabled" />
    

    вместо

    .button--disabled {}
    

    <button class="button button--disabled" />
    


    1. knotri
      21.09.2017 13:35

      Я с бемом использую модификаторы — полет нормальный


    1. Aingis
      21.09.2017 13:46

      DOM-элемент может представлять собой несколько БЭМ-сущностей (миксы). Например, это может быть одновременно элементы раскладки (page__elem), ответственный за размещение (position, margin, flex — все дела) и непосредственно какой-то блок (block). В таком случае ваши модификаторы-классы могут создавать коллизии. Но если избавиться от миксов, то можно и так. Правда, в таком случае вы увеличите уровень вложенности, и не исключены сложности с раскладкой, там где важен уровень вложенности.


      (P.S. А ещё так нельзя было делать в IE6 — во времена, когда создавался БЭМ.)



  1. Kasheftin
    21.09.2017 17:12
    +1

    Зачем в модификаторе повторять всю цепочку названия? Моя верстка в общем следует бэму, только все модификаторы, и только они, начинаются с `-` т.е. вместо

    <button class="search__button search__button--primary">
    идет просто
    <button class="search__button -primary">


    1. neurocore
      21.09.2017 20:24

      Полагаю, чтобы не напороться на глобальный -primary


      1. Kasheftin
        22.09.2017 09:55

        На глобальный search__button--primary тоже можно напороться. Но так да, название длиннее, вероятность меньше.


    1. anttoshka
      22.09.2017 22:57

      Эта проблема уходит вместе с использованием jade.
      Код по типу
      +b.block
      +e.element_mod

      преобразуется в
      <div class='block__element block__element_mod>


      БЭМ совместно с препроцессорами очень удобно использовать.


  1. GoodGod
    22.09.2017 11:37

    А как вы относитесь к css-modules? Ничего придумывать не надо, простые названия, но никогда не пересекутся в двух компонентах и стек технологий чтобы использовать уже есть.


  1. vintage
    22.09.2017 12:14

    Некоторые неверно понимали идею и появлялись даже элементы элементов.

    Когда элемент сам по себе является блоком с элементами внутри и нужно их стилизовать, то тут как нельзя кстати приходятся "элементы элементов":


    [block1] {}
    [block1~="mod"] {}
    [block1_element1] {}
    [block1_element1~="mod"] {}
    
    [block2] {}
    [block2~="mod"] {}
    [block2_element2] {}
    [block2_element2~="mod"] {}
    
    [block1_element1_element2] {}
    [block1_element1_element2~="mod"] {}

    Более живой пример:


    [header]{}
    [header~="big"]{}
    [header_query]{}
    
    [suggest] {}
    [suggest_option] {}
    [suggest_option~="first"] {}
    
    [header_query_option~="first"] {}

    Ну и плюс ваши "уровни переопределения", только без лишней абстракции:


    /* Первая подсказка в поле поиска на главной странице */
    [serp_header_query_option~="first"] {}

    Фактически получается каскад, но не на уровне ДОМ, а на уровне дерева блоков, и без проблем со специфичностью. Сами компоненты в доме могут располагаться друг относительно друга как угодно. Например, suggest может рендериться в body.


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

    Мы БЭМ классы генерируем автоматически на основе:


    1. Имени блока (mol_button, например).
    2. Имени блока в контексте другого блока (блок mol_button, например, может иметь имя option в контексте mol_select, что даст селектор mol_select_option).
    3. И так далее по дереву компонент.

    Пример из жизни:


    <input
        id="$mol_app_todomvc.Root(0).Task_row(1).Title()"
        mol_app_todomvc_task_row_title
        mol_string
        mol_view
        placeholder="Task title"
    >

    [mol_view] {
        /* Общие стили для всех блоков, на этот селектор можем повесить всякие css-reset */
    }
    
    [mol_string] {
        /* Стили для блоков ввода строки текста */
    }
    
    [mol_app_todomvc_task_row_title] {
        /* Стили для блоков ввода текста задач в конкретном ToDoMVC приложении */
    }


    1. vintage
      22.09.2017 12:20

      Ну а в "шаблоне", конечно, никакого БЭМ-а:


      <= Title $mol_string
          hint <= title_hint @ \Task title
          value?val <=> title?val \


    1. vithar
      23.09.2017 09:54
      +1

      Это не БЭМ. Ключевое отличие БЭМ от любого другого именования с кучей подчёркиваний — можно в любой момент программно определить что есть блок, что элемент, а что модификатор в любом произвольном идентификаторе.


      https://ru.bem.info/methodology/naming-convention/


      1. vintage
        23.09.2017 11:02
        -1

        Я вам предлагаю сделать шаг вперёд и понять, что элемент — это тоже блок, только в пространстве имён другого блока.


        1. vithar
          23.09.2017 11:11
          +2

          В этом нет смысла. Текущая методология достаточно обрезана бритвой Оккама, чтобы быть способной выразить всё, что нужно.


          1. vintage
            23.09.2017 11:36

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


            1. vithar
              23.09.2017 16:03

              У нас всё сделано из компонент (блоков) у каждого из которых свой шаблон. И мы можем добавить произвольный класс элементу или в шаблон компонента (bemhtml) или на уровне данных (bemjson).


              1. vintage
                23.09.2017 16:28

                Можно пример?


  1. vithar
    23.09.2017 11:11

    del