EN RU
Форум

Методология

Технологии

Инструментарий

Библиотеки

Учебные материалы

История создания БЭМ

Типичная верстка в Яндексе 2005 года

История БЭМ началась в 2005 году. Тогда, с точки зрения интерфейса, обычный проект Яндекса был набором статических HTML-страниц, которые использовались как основа для создания шаблонов на XSL.

HTML-страницы хранились в отдельной директории, которая имела подобную структуру:

about.html
index.html
…
project.css
project.js
i/
  yandex.png

Сверстанные статические HTML-страницы нарезались в XSL-шаблоны. Если HTML изменялся, все правки было необходимо переносить вручную в XSL. И наоборот, изменения в шаблонах требовали правок в HTML (для поддержания статического HTML в актуальном состоянии).

Зарождение основ методологии

В 2006 году началась работа над первыми большими проектами - Яндекс.Музыка и Я.ру. Эти проекты с десятками страниц выявили основные недостатки текущего подхода к разработке:

Типичный CSS того времени содержал длинный каскад:

/* Albums (begin) */
    .result .albums .info
    {
        padding-right: 8.5em;
    }

    .result .albums .title
    {
        float: left;
        padding-bottom: 0.3em;
    }

    .result .albums .album .listen
    {
        float: left;
        padding: 0.3em 1em 0 1em;
    }
/* Albums (end) */

Совместно использовались селекторы по тегам и идентификаторам:

/* Картинки на фоне (begin) */
    #foot div
    {
        height: 71px;
        background: transparent url(../i/foot-1.png) 4% 50% no-repeat;
    }

    #foot div div
    {
        background-position: 21%;
        background-image: url(../i/foot-2.png);
    }

    #foot div div div
    {
        background-position: 38%;
        background-image: url(../i/foot-3.png);
    }
/* Картинки на фоне (end) */

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

Появление блоков

Основное время разработчиков тратилось на создание HTML-структуры страницы и написание CSS-стилей для нее. JavaScript воспринимался лишь как сопутствующая технология.

Чтобы ускорить разработку, требовалось облегчить поддержку HTML и CSS отдельных компонентов страницы. Для этого мы ввели понятие блока.

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

В большинстве случаев любой компонент на странице (сложный или простой) рассматривался как блок. HTML-контейнер каждого блока получал уникальный CSS-класс с тем же именем, что и у блока.

Классам блоков мы добавили префиксы (b-, c-, g-), чтобы отличать их от внутренних классов:

Кроме префиксов использовались постфиксы, например:

Стиль применяется в отсутствие JavaScript. Если JavaScript включен, то при загрузке страницы вызывается метод init() в onload, и постфикс удаляется из всех классов. Таким образом «включался» JavaScript для блоков.

Появление элементов

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

Ключевое различие между блоком и элементом в тот момент:

Если элемент способен существовать вне блока, он становится блоком.

Позже стало возможным вынимать некоторые элементы из блока, сохраняя при этом рабочее состояние самого блока.

Элементы с большим количеством кода выделялись комментариями.

/* Head (begin) */
    .b-head { … }

    /* Logo (begin) */
        .b-head .logo { … }
        .b-head .logo a { … }
    /* Logo (end) */

    /* Right side (begin) */
    .b-head .right { … }

        /* Info (begin) */
            .b-head .info { … }
            .b-head .info .exit a { … }
        /* Info (end) */

        /* Search (begin) */
            .b-head .search { … }
            .b-head .search div div, .b-head .search div div i { … }
        /* Search (end) */
    /* Right side (end) */
/* Head (end) */

Унификация файловой структуры проекта

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

Начали с того, что CSS, JavaScript и картинки стали складывать в отдельные директории.

JavaScript применялся все чаще, в проект подключались дополнительные компоненты и библиотеки.

Типичная структура верстки проекта 2006 года:

index.html
css/
  yaru.css
  yaru-ie.css
js/
  yaru.js
i/
  yandex.png

Основной код для IE мы писали в общем CSS-файле, например, yaru.css.

/* Common definitions (begin) */
    body
    {
        font: 0.8em Arial, sans-serif;

        padding: 0 0 2em 0;
        background: #fff;
    }

    * html body
    {
        font-size: 80%;
    }

Специфичные правила (временные решения), работающие только в IE, создавались в отдельном файле. В имя файла добавлялся специальный указатель ie — yaru-ie.css.

/* Common blocks (begin) */
    /* Artist (begin) */
        .b-artist .i i
        {
            top: expression(7 + (90 - this.parentNode.getElementsByTagName('img')[0].height)/2);
            filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='../i/sticker-lt.png', sizingMethod='crop');
        }

Зачатки общепортального фреймворка

При верстке нескольких проектов с похожим дизайном появлялись общие блоки.

Портал Яндекса в то время содержал больше 100 разных сервисов, выполненных в одном стиле. Для такого объема данных «Copy-Paste» из проекта в проект уже не подходил.

Появилось общее хранилище повторно используемых компонентов, которое называлось общая библиотека блоков или просто — Common.

Первые блоки, которые вошли в Common: шапка, подвал и стили для статического текста.

Файлы блоков хранились на выделенном внутреннем сервере разработчиков (common.cloudkill.yandex.ru в примере ниже).

Это было началом работы нашего общепортального фреймворка. Cтили из него подключались в основной проектный файл при помощи импортов непосредственно с сервера:

@import url(http://common.cloudkill.yandex.ru/css/global.css);
@import url(http://common.cloudkill.yandex.ru/css/head/common.css);
@import url(http://common.cloudkill.yandex.ru/css/static-text.css);
@import url(http://common.cloudkill.yandex.ru/css/list/hlist.css);
@import url(http://common.cloudkill.yandex.ru/css/list/hlist-middot.css);
@import url(slider.css);

/* Header (begin) */
    /* Service (begin) */
        .b-head .service h1 { … }
        .b-head .service h1, .b-head .service h1 a, .b-head .service h1 b { … }

Возникла проблема: большое количество импортов замедляло загрузку страницы. Было принято решение прекомпилировать стили (и позже JavaScript-файлы) перед выкладкой.

Компиляция заменяет @import на содержимое внешних файлов (это называется inlining) и оптимизирует код. Например, убирает ненужные браузеру пробелы и комментарии.

Наш внутренний инструмент для оптимизации вырос из простого Perl-скрипта в отдельный open-source-проект borschik.

Верстка независимыми блоками

К осени 2007 года правила верстки устоялись. Мы увидели практическую пользу от нового подхода, поэтому было решено рассказать об этом вне Яндекса.

На ClientSide'07 был сделан доклад про верстку независимыми блоками, которая на тот момент составляла основу наших HTML-страниц.

В докладе официально вводилось понятие блока:

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

Более позднее описание.

Блоки делились на простые и составные.

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

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

Любой блок должен позволять вкладывать в него другой блок, когда это возможно.

Правила независимости блоков

Сформировались первые правила независимости блока:

Важным решением был отказ от id.

Теперь мы могли:

Правила полной независимости блоков

С текущей схемой оставался ряд проблем с CSS:

Поэтому мы сформулировали правила более строгой независимости блоков под названием абсолютно независимые блоки (АНБ):

.b-user b -> .b-user .first-letter

.b-user .first-letter -> .b-user-first_letter

Мы понимали, что наличие класса у каждого DOM-узла существенно увеличивает объем HTML-кода. На тот момент мы считали, что это дорого, и применяли такой подход в исключительных случаях.

Первые правила именования — префиксы

Так как распространенной проблемой в программировании является подбор имен, мы решили задавать имена блоков с помощью разных префиксов с разной семантикой:

Появление модификации блоков

Работая с блоками, мы поняли, что они могут иметь разные состояния.

Например, «Кнопка» (блок button) может быть:

Вместо того, чтобы создавать три разных блока, мы начали делать модификации.

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

Модификация определялась именем (например, size) и значением (например, small, normal или big).

Возможные варианты модификации:

class="b-block b-block-postfix"

Общепортальный фреймворк — Лего

Весной 2008 года была поставлена задача создать брендбук, описывающий наш портальный стиль. Решили начать работу с написания HTML/CSS кода.

Проект получил название Лего.

Структура репозитория

На верхнем уровне репозиторий разделен по технологиям:

css/
html/
js/
xml/
xsl/

Директория каждой технологии имеет свою структуру.

CSS распределяется на следующие директории:

Пример

css/
  block/
    b-dropdown/
      b-dropdown.css
  service/
    auto/
      block/
        b-head-logo-auto.css
      head.css
  util/
    b-hmenu/
      b-hmenu.css

Структура директории HTML аналогична CSS:

html/
  block/
    b-dropdown.html
  service/
    auto/
      l-head.html
  util/
    b-hmenu.html

JS находится в зачаточном состоянии и складывается в одну директорию:

js/
  check-is-frame.js
  check-session.js
  clean-on-focus.js
  dropdown.js
  event.add.js
  event.del.js

У каждого сервиса есть XML-файл, использующийся для построения шапки:

xml/
  block/
    b-head-tabs-communication.xml
    common-services.ru.xml
    head-messages.ru.xml
  service/
    auto/
      head.xml

XSL блоков находится в одной директории. Каждому блоку соответствует один файл:

xsl/
  block/
    b-dropdown.xsl
    b-head-line.xsl
    i-common.xsl
    i-locale.xsl
    l-foot.xsl
    l-head.xsl

Лего подключается в проекты с помощью svn:externals.

При финальной сборке проекта код библиотеки полностью включается в проект, что можно сравнить со статической линковкой.

Такой подход позволяет выпускать версии сервисов с разными версиями Лего и переходить на новую версию тогда, когда это удобно команде проекта.

CSS-файлы

CSS-файлы, подключавшиеся на страницах, состояли из @import'ов реализации блоков.

@import url(../../block/l-head/l-head.css);
@import url(../../block/b-head-logo/b-head-logo.css);
@import url(../../block/b-head-logo/b-head-logo_name.css);
@import url(block/b-head-logo-auto.css);

Эти @import'ы писались вручную.

Правила именования

Именование файлов еще не устоялось - мы пробуем разные варианты.

Общепортальный фреймворк — Лего 1.2 (2008)

Структура репозитория

В рамках версии Лего 1.2, был произведен рефакторинг, и структура репозитория проекта изменилась.

common/
  css/
  js/
  xml/
  xsl/
example/
  html/
service/
  auto/
    css/
    xml/

Убрано разделение на util и block, общий CSS находится в common/css.

От идеи выноса кода в open source на тот момент отказались и вернулись к ней только через два года.

common/
  css/
    b-dropdown/
      arr/
        b-dropdown.arr.css
        b-dropdown.arr.ie.css
      b-dropdown.css
      b-dropdown.ie.css

Всё, что находилось в опциональном CSS (файлах b-dropdown_arr.css, b-dropdown_arr.ie.css), вынесено в директории (arr/). В основном файле блока стало меньше кода.

Правила именования

Файлы для IE переименованы: указатель специфичности файла для IE был частью имени файла, а стал суффиксом. Было -ie.css, - стало .ie.css. Расширения файлов теперь могут состоять из нескольких слов.

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

Лего 2.0. Появление БЭМ

В марте 2009 года вышла версия Лего 2.0.

Этим событием оканчивается верстка независимыми блоками и начинается БЭМ.

БЭМ — аббревиатура от Блок, Элемент, Модификатор. Это три ключевые сущности, которые мы используем при разработке компонентов интерфейса.

Что же принципиально изменилось с выходом версии 2.0?

Основное изменение — мы вывели вперед блоки, а не технологии. Отныне блоки первичны, а технологии их реализации — вторичны.

Реализацию каждого блока разместили в отдельной директории, технологии — это файлы внутри нее. Также появилась документация к блоку — файл .wiki внутри блока.

Независимый блок

Может быть использован в любом месте страницы.

В XML блок представлен тегом в пространстве имен lego:

<lego:l-head>
<lego:b-head-logo>

HTML-класс блока соответствует имени этого тега:

<table class="l-head">
<div class="b-head-logo">

CSS-правила пишутся на класс:

.l-head
.b-head-logo

Все файлы (css, js, html, xsl), относящиеся к блоку, хранятся в его директории:

common/
  block/
    b-head-logo/
      b-head-logo.css
      b-head-logo.xsl
      b-head-logo.js
      b-head-logo.wiki

Элемент

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

В XML элемент представлен в пространстве имен lego без префикса:

<lego:b-head-logo>
    <lego:name/>
</lego:b-head-logo>

Класс в HTML соответствует имени этого элемента без префикса.

<div class="b-head-logo">
    <span class="name">Авто</span>
</div>

CSS-правила пишутся на класс:

.b-head-logo .name { ... }

Файлы элемента хранятся в отдельной директории.

common/
  block/
    b-head-logo/
      name/
        b-head-logo.name.css
        b-head-logo.name.png
        b-head-logo.name.wiki

Имена файлов элементов пишутся через точку: b-head-logo.name.css

Модификатор

Определяет внешний вид, состояние и реже поведение блока.

В XML модификатор представлен атрибутом в пространстве имен lego:

<lego:b-head-tabs lego:theme="grey">

В HTML используется дополнительный класс:

<div class="b-head-tabs b-head-tabs_grey">...</div>

CSS-правила пишутся на класс:

.b-head-tabs_grey { ... }

Файлы для модификатора находятся в отдельной директории. Имя директории модификатора начинается с подчеркивания:

common/
    block/
        b-head-logo/
            _theme/
                b-head-logo_gray.css
                b-head-logo_gray.png
                b-head-logo_gray.wiki

Декларация используемых блоков

Все компоненты Лего описываются в XML-файле.

<lego:page>
    <lego:l-head>
        <lego:b-head-logo>
            <lego:name/>
        </lego:b-head-logo>

        <lego:b-head-tabs type="search-and-content"/>

Из него генерируются CSS-файлы.

@import url(../../common/block/global/_type/global_reset.css);
@import url(../../common/block/l-head/l-head.css);
@import url(../../common/block/b-head-logo/b-head-logo.css);
@import url(../../common/block/b-head-logo/name/b-head-logo.name.css);
@import url(../../common/block/b-head-tabs/b-head-tabs.css);
@import url(../../common/block/b-dropdown/b-dropdown.css);
@import url(../../common/block/b-dropdown/text/b-dropdown.text.css);
@import url(../../common/block/b-pseudo-link/b-pseudo-link.css);
@import url(../../common/block/b-dropdown/arrow/b-dropdown.arrow.css);
@import url(../../common/block/b-head-search/b-head-search.css);
@import url(../../common/block/b-search/b-search.css);
@import url(../../common/block/b-search/input/b-search.input.css);
@import url(../../common/block/b-search/sample/b-search.sample.css);
@import url(../../common/block/b-search/precise/b-search.precise.css);
@import url(../../common/block/b-search/button/b-search.button.css);
@import url(../../common/block/b-head-userinfo/b-head-userinfo.css);
@import url(../../common/block/b-user/b-user.css);
@import url(block/b-head-logo/b-head-logo.css);
@import url(block/b-head-search/b-head-search.css);

На примере этого файла видно, что сначала указывается общий код, а потом добавляются стили, чтобы привести Лего-блоки к дизайну проекта.

Из XML-декларации генерируются и JS-файлы.

include("../../common/block/i-locale/i-locale.js");
include("../../common/block/b-dropdown/b-dropdown.js");
include("../../common/block/b-search/sample/b-search.sample.js");
include("../../common/block/b-head-userinfo/user/b-head-userinfo.user.js");

А также XSL-файлы.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:import href="../../common/block/i-common/i-common.xsl"/>
<xsl:import href="../../common/block/i-items/i-items.xsl"/>
<xsl:import href="../../common/block/l-head/l-head.xsl"/>
<xsl:import href="../../common/block/b-head-logo/b-head-logo.xsl"/>
<xsl:import href="../../common/block/b-head-logo/name/b-head-logo.name.xsl"/>
<xsl:import href="../../common/block/b-head-tabs/b-head-tabs.xsl"/>
<xsl:import href="../../common/block/b-dropdown/b-dropdown.xsl"/>
<xsl:import href="../../common/block/b-pseudo-link/b-pseudo-link.xsl"/>
<xsl:import href="../../common/block/b-head-search/b-head-search.xsl"/>
<xsl:import href="../../common/block/b-search/b-search.xsl"/>
<xsl:import href="../../common/block/b-search/input/b-search.input.xsl"/>
<xsl:import href="../../common/block/b-search/sample/b-search.sample.xsl"/>
<xsl:import href="../../common/block/b-search/precise/b-search.precise.xsl"/>
<xsl:import href="../../common/block/b-search/button/b-search.button.xsl"/>
<xsl:import href="../../common/block/b-head-userinfo/b-head-userinfo.xsl"/>
<xsl:import href="../../common/block/b-user/b-user.xsl"/>
<xsl:import href="../../common/block/b-head-userinfo/service/b-head-userinfo.service.xsl"/>
<xsl:import href="../../common/block/b-head-userinfo/setup/b-head-userinfo.setup.xsl"/>
<xsl:import href="../../common/block/b-head-userinfo/region/b-head-userinfo.region.xsl"/>

</xsl:stylesheet>

Мы перестали писать эти файлы руками, началась генерация кода.

Скорость селекторов (2009)

При реализации новой версии Яндекс.Почты была поставлена задача сделать ее быстрой.

Для решения задачи мы начали использовать XSL в браузере (и подгружать XML, необходимый для отрисовки данных на странице). Возникла проблема: трансформации отрабатывались быстро, но вставка в DOM полученного результата происходила очень медленно. При этом, отключение CSS решало проблему.

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

Результаты исследования подробно описаны в статье.

Решение проблемы было уже готово — это абсолютно независимые блоки (АНБ).

Мы перевели все блоки в Лего на АНБ-нотацию и с тех пор создаем их так, чтобы у каждого DOM-узла был свой class, на который можно написать стили. Также мы не используем Tag Rules в CSS.

В классы элементов вносится имя блока, селекторы получаются простыми и быстрыми.

<div class="b-head-logo">
    <span class="b-head-logo__name">
        Авто
    </span>
</div>

Стабилизация нотации

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

БЭМ и open source (2010)

В 2010 году мы снова вернулись к идее open source. Мы создали организацию bem на GitHub.

Библиотека bem-bl

Мы начали выносить блоки из Лего в bem-bl, проводя одновременно с этим рефакторинг.

Параллельно с переносом блоков в новую библиотеку публиковали информацию про них.

Инструменты

Для работы с файлами по БЭМ-методам нам понадобились свои инструменты. Началась реализация инструментов bem-tools на JavaScript под Node.js.

Уровни переопределения

Возникло новое понятие — уровень переопределения. Так мы стали называть директории с реализацией блоков.

Например, в проекте может быть:

Пример

bem-bl/
  b-logo/
lego/
  b-logo/
auto/
  blocks/
    b-logo/

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

.bem/
  level.js

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

Шаблонизатор BEMHTML

После экспериментов с разными шаблонизаторами, был разработан шаблонизатор BEMHTML, который позволяет:

Видео по BEMHTML:

Резюме

Появлению БЭМ в том виде, что мы имеем сейчас, предшествовал долгий период проб и экспериментов.

Хочется обратить ваше внимание, что на всех этапах своего развития это всё же был БЭМ.

Тот БЭМ, что мы используем сейчас, — не единственное верное решение.

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

Главное понять, какие плюсы БЭМ принесет в ваш проект, выбрать подходящую для вас схему и начать применять у себя!

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