UK
Форум

Методологія

Інструментарій

Платформа

Історія створення БЕМ

Типова верстка в Яндексі 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) */

Одночасне використання id і тегів:

/* Картинки на тлі (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) */

Верстка большого проекту була некерованою. Щоб уникнути цього, потрібно було визначити правила роботи з поняттями класу, tag, візуального компонента і не тільки.

Поява блоків

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

Щоб прискорити розробку, потрібно полегшити підтримку HTML і CSS окремих компонентів сторінки. Для цього ми ввели нове поняття – блок.

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

У більшості випадків будь-який компонент на сторінці (складний або простий) розглядався як блок. HTML-контейнер кожного блоку отримував унікальний CSS-клас з тим же ім'ям, що і у блоку.

Класів блоків ми додали префікси (b-, с-, 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 у наведеному нижче прикладі).

Це було початком роботи нашого общепортального фреймворка. Стилі з нього підключалися в основний проектний файл за допомогою импортов безпосередньо з сервера:

@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-коду. На той момент ми вважали, що це дорого, і застосовували такий підхід у виняткових випадках.

Перші правила іменування — префікси

Так як поширеною проблемою у програмуванні є підбір імен змінних. Ми вирішили задавати імена блоків з допомогою різних префіксів з різною семантикою:

Поява модифікації блоків

Працюючи з блоками, ми зрозуміли, що вони можуть мати різні стани.

Наприклад, блок «Кнопка» може бути представлений в трьох станах:

Замість того, щоб створювати три різних блоку, ми почали робити модифікації одного.

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

Модифікація складалася з імені (наприклад, 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), винесено в директорії (arr/b-dropdown.arr.css). В основному файлі блоку стало менше коду.

Правила іменування

Файли для 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">

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:

Резюме

Появі БЕМ у тому вигляді, що ми маємо зараз, передував довгий період проб і експериментів.

Хочеться звернути вашу увагу, що на всіх етапах свого розвитку це все ж був БЕМ.

Тот БЭМ, что мы используем сейчас, — не единственное верное решение. Ми рекомендуємо використовувати БЕМ у ваших проектах в тому обсязі, в якому він принесе найбільшу користь. Можна пробувати застосовувати його тільки для верстки. Ми самі починали саме з цього. Гнучкість БЕМ-методології дозволяє налаштувати її під свої поточні процеси та організовувати роботу над проектом.

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

Якщо у вас виникнуть питання, обов'язково задавайте їх на нашому форумі.

doc-rating:rate
Повідомити про помилку на Гітхабі або поправити за допомогою prose.io.