В разработке интерфейсов отдельные фреймворки уже не так важны: когда инструменты доступны, наша задача сводится к выбору нужных. Чтобы сделать правильный выбор, нужно начать с общего подхода, с методологии. Большинство методологий, однако, разработаны крупными компаниями. Применимы ли они в маленьких проектах или их нужно «переизобретать» заново для успешного использования?
Скорее всего, вы уже знаете об одной из таких методологий, разработанной Яндексом — БЭМ. БЭМ утверждает, что трёх сущностей (блоков, элементов и модификаторов) достаточно для написания HTML и CSS, задания структуры кода и компонентной структуры с последуюшим масштабированием проекта до самого высокого уровня.
Я проработал в Яндексе достаточно долго и видел, как эта методология работает на больших проектах. В Яндексе БЭМ используют для разработки CSS- и JavaScript-компонент, с помощью этой методологии также пишут шаблоны и задают зависимости между компонентами. Есть БЭМ-инструменты, поощряются различные эксперименты с кодом, исследования. В масштабах большой компании эти трудозатраты окупаются и дают Яндексу возможность разрабатывать сотни сервисов одновременно — быстро и качественно.
Могут ли маленькие команды получить от БЭМ то же самое? В этом я совершенно не был уверен. Всё же БЭМ — абстракция, которая поставляется вместе с инструментами и технологиями. Для маленькой компании польза от переключения на «полный стек» этих технологий — сомнительна, многие из инструментов изначально приспособлены под крупные и сложные задачи. Быть может тогда сама идея, сама методология окажется полезной?
Изначально эта моя статья была опубликована в известном многим журнале Smashing Magazine. Но я решил, что и на Хабре она может быть интересна, ведь многие здесь занимаются собственными небольшими проектами.
К вопросу о том, может ли BEM применяться в таковых, я вернулся чуть более года назад, когда переехал в Берлин для работы в небольшой стартап-компании Deltamethod. Планы на разработку у компании были смелые, и мы с командой решили попробовать БЭМ-подход. Хотелось получить те же бонусы, что были у Яндекса: переиспользование кода, «живой» Style Guide, масштабируемость и быструю разработку. В дополнение к этому, мы хотели в целом сохранить используемый стек технологий и улучшать код постепенно, а не переписывать весь сервис с нуля.
Мы потратили какое-то время на архитектурные, базовые вещи, внедряя БЭМ шаг за шагом, проверяя результат и снова двигаясь вперед. Мы продолжаем записывать идеи, полезные советы, создаём небольшие туториалы. Сейчас я уверен, что БЭМ можно применять и на маленьких проектах. Ниже я расскажу о наших наработках, может быть это будем вам полезно.
Основы БЭМ
Давайте вспомним основы.
Утверждается, что семантика — это фундамент веб разработки, но различные frontend-технологии используют разные семантические модели. Современное веб-приложение на уровне HTML чаще всего — просто нагромождение тегов div и span. CSS как технология вообще не предлагает какой-либо структуры. Высокоуровневые JavaScript-компоненты используют те или иные абстракции, но они слабо связаны с CSS или HTML-разметкой. С точки зрения дизайнеров и UX-специалистов, интерфейсы вообще описываются терминами, далекими от технической реализации. Тем не менее, все эти предметные области мы используем вместе. Самое время вспомнить про БЭМ: ведь это общая семантическая модель для разметки, стилей, кода и UX. Посмотрим поближе.
Блоки
Блок — это независимая сущность со своим собственным смыслом, он представляет на странице отдельный «кирпичик» интерфейса.
Например, блоками могут быть:
- заголовок,
- кнопка,
- навигационное меню.
Чтобы задать блок, вам нужно придумать ему имя и определить его назначение. В интерфейсе может одновременно присутствовать несколько экземпляров одного и того же блока (например, разные кнопки или несколько меню).
Любой веб-интерфейс можно описать как иерархическую структуру блоков. Простейший пример — само HTML-описание страницы, если представить, что каждый тег — это блок. Правда, с точки зрения семантики это малоосмысленно, потому что HTML был разработан для представления и оформления текстов, а не интерактивных веб-приложений.
Элементы
Элемент — это часть блока, связанная с ним и по смыслу, и функционально. Без блока, к которому он относится, элемент не существует и не используется. Но не у всех блоков должны быть элементы.
Примеры элементов:
- навигационное меню (блок), содержащее пункты меню (элементы),
- таблица (блок), внутри которой строки, ячейки и заголовки (элементы).
У элементов тоже есть названия. Если внутри блока несколько одинаковых элементов, то они идут под одним именем (например, ячейки таблицы или пункты списка). Элементы определяются по смыслу, а не исходя из HTML-вёрстки блока; так, отдельный элемент может быть представлен сложной HTML-структурой.
Модификаторы
Модификаторы — флаги у блоков или элементов, они определяют свойства или состояния. Модификаторы могут быть булевыми (например, visible: true
или false
) или представлять из себя пару ключ-значение (code>size: large, medium
, small
). Это чем-то похоже на атрибуты в HTML, но всё-таки не то же самое. У сущности может быть несколько модификаторов, если эти модификаторы описывают разные вещи.
Блоки в DOM
Как использовать БЭМ, если мы всё ещё вынуждены писать HTML? Нужно соглашение об именовании, которое свяжет DOM-узлы с БЭМ-сущностями.
Для задания этой связи БЭМ использует CSS-классы. При этом блоки, элементы и модификаторы не размещаются на DOM-узлах «эксклюзивно»; на одном теге может быть определено сразу несколько блоков либо же элемент блока может быть объединен с контейнером другого блока. Использование одного и того же DOM-узла для размещения нескольких сущностей называется «микс». Помните, что «миксы» сделаны для удобства, совмещать можно только совместимое: смешивая блоки и элементы, не преврашайте всё в кашу.
Дерево БЭМ (BEM tree)
Формируя документ из БЭМ-сущностей, начиная с корневого блока (<body>
или даже <html>
) и заканчиая самыми глубоко вложенными блоками, вы создаёте семантический слой поверх существующей DOM структуры.
Этот слой называется БЭМ-деревом (или BEM tree по-английски).
БЭМ-дерево даёт возможность взаимодействовать с целым документом в терминах БЭМ с точки зрения семантики, сильно абстрагируясь от особенностей DOM-реализации.
Первые шаги
Если вы уже задумываетесь, не попробовать ли БЭМ на проекте, возникает закономерный вопрос: «Как перевести на БЭМ существующий проект, можно ли это сделать постепенно?»
Конечно, можно. Давайте начнем с того, чтобы выделить несколько блоков. Мы начнём только с семантики, а конкретные технологии (такие как CSS и JavaScript) обсудим позже.
Как вы помните, только самостоятельная сущность может быть блоком.
Например, заголовки – это блоки. У них нет внутренних элементов, но уровни заголовков (от самого большого до самого маленького) могут быть определены как модификаторы вида ключ-значение.
Если позже понадобятся ещё уровни, мы определим дополнительные модификаторы. Тут я скажу, что разработчики HTML4 были скорее всего неправы, изобретая <h1>
...<h6>
. Они создали разные теги, в то время как требовались модификаторы одного и того же блока-сущности. В HTML5 это пытаются исправить с помощью секционных элементов, но с поддержкой в браузерах пока не очень хорошо.
Скажем, мы получили такое:
BLOCK heading
MOD level: alpha, beta, gamma
В качестве второго примера можно взять форму. Её элементы управления (или, как их называют, контролы: поля ввода, кнопки, чекбоксы и пр.) тоже являются блоками. В HTML это всё реализовано довольно беспорядочно. Разные по сымслу вещи (поля ввода, радио-кнопки и флажки-checkbox'ы) объединены в один тег <input>
, a другие (сильно похожие на них) определены отдельными тегами <select>
и <textarea>
. Некоторые сущности, такие как метки <label>
или автоматические подсказки datalist
, вообще не имеют смысла без привязки к конктерным контролам, и подходят на роль элементов внутри блоков, составляющих форму.
Посмотрим, можно ли это исправить:
BLOCK text-input
MOD multiline
MOD disabled
ELEMENT text-field
ELEMENT label
Поле ввода нужно для того, чтобы туда можно было написать текст. Если же нам нужен многострочный текст, семантически ничего не меняется, поэтому multiline
— это только модификатор. В HTML у этих двух случаев по техническим причинам разметка будет разной, но это нормально — сейчас мы сосредоточены на определении семантики, а не конкретной реализации. Ещё у блока есть элементы textfield
и label
. Позже может понадобиться добавить и другие элементы, такие как иконка статуса, место для сообщения об ошибке или автоподсказка.
BLOCK checkbox
ELEMENT tick-box
ELEMENT label
BLOCK radio
ELEMENT radio-button
ELEMENT label
Эти два блока довольно очевидны. По-прежнему есть элемент <label>
и элемент, представленный тегом <input>
.
BLOCK select
MOD disabled
MOD multiple
ELEMENT optgroup
ELEMENT option
MOD disabled
MOD selected
В случае select, метки label нам не нужны, а все остальное более-менее похоже на обычный контрол select. Технически можно переиспользовать существующий тег <select>
и его структуру. Обратите внимание, что и у блока select
, и у его элемента option
может быть модификатор disabled
. Важно, что это разные модификаторы: первый выключает весь контрол целиком, а второй выключает только конкретный option
(кстати, отличный пример модификатора на элементе!).
Попробуйте и в своём проекте найти подобные примеры блоков.
Для первого раза это может быть непросто. Если нужна помощь, о ней можно попросить команду БЭМ!
Пусть ваш CSS говорит сам за себя
Наверняка вы слышали, что БЭМ сильно помогает организовывать и писать CSS. А как именно, и почему?
Как я писал выше, БЭМ использует CSS-классы для хранения информации о блоках, элементах и модификаторах. С помощью нехитрых соглашений об именовании, БЭМ превращает CSS из простого языка описания стилей в инструмент, описыващий семантику вашего проекта.
Система именования БЭМ для CSS
Договоримся о следующем:
- Имена блоков, элементов и модификаторов должны быть недлинными и семантически значимыми.
- Используем только латинские буквы, символ тире и цифры.
- Не используем символ подчёркивания (
_
), он нам понадобится как специальный разделитель.
Контейнеры блоков получают CSS-класс, состоящий из префикса и имени блока:
.b-heading
.b-text-input
Префикс b-
означает «блок» и используется по умолчанию во многих реализациях БЭМ. Можно выбрать свой собственный префикс, но он должен быть максимально коротким. Можно обойтись и вообще без префиксов, для самого БЭМ они не нужны, но помогают реализовать отсутствующие в стандарте CSS (и очень нужные порой!) пространства имён.
Контейнеры элементов внутри блока получают CSS-класс из имени блока, двух подчёркиваний и имени элемента:
.b-text-input__label
.b-text-input__text-field
Элементы элементов не используются (например, имя класса .b-block__elem1__elem2
не соответствует БЭМ-подходу).
Модификаторы относятся или к блоку, или к элементу конкретного блока. Их CSS-класс формируется из класса их «владельца», далее следует подчёркивание, и имя модификатора:
.b-text-input_disabled
.b-select__option_selected
Для булевых модификаторов этого достаточно. Если модификатор — пара «ключ-значение», добавляем ещё одно подчёркивание для отделения значения модификатора:
.b-heading_level_alpha
Классы-модификаторы используются совместно с классом блока или элемента:
<div class="b-heading b-heading_level_alpha">BEM</div>
Чем привлекателен CSS по БЭМ-методологии
Достаточно одного класса
Нередко CSS сильно зависит от структуры документа. Меняется структура — «ломается» CSS. С BEM мы перестаём использовать имена тегов и ID, а опираемся только на имена классов. Это позволяет нам минимально зависеть от структуры документа.
Специфичность CSS-правил: проблема и решение
Большие объёмы CSS сложно поддерживать в том числе из-за того, что они могут взаимно влиять друг на друга, порой непредсказуемо доопределяя или переопределяя уже имеющиеся правила.
У этой «проблемы» есть имя: специфичность CSS-правил. Исходно, наличие как имён тегов, так и идентификаторов меняет специфичность правил таким образом, что при наследовании свойств (которое используется в CSS очень часто) переопределение правил возможно только с селекторами той же самой или более высокой специфичности. Проекты, сделанные на БЭМ, практически не страдают от этой проблемы.
Давайте рассмотрим пример:
Пусть, скажем, у вас есть таблица с такими вот стилями:
td.data { background-color: white }
td.summary { background-color: yellow }
Однако в другом компоненте вам потребовалось переопределить цвет фона отдельной ячейки:
.final-summary { background-color: green }
Это не сработает, потому что tag.class
всегда будет иметь более высокую специфичность по сравнению с .class
, независимо от их взаимного положения в CSS-коде.
Вам придётся добавить имя тега, чтобы всё заработало:
td.final-summary { background-color: green }
Поскольку в БЭМ все ключевые стили задаются только через классы с уникальными именами, ключевую роль начинает играть порядок следования правил в таблице стилей, который легко контролировать, а не селекторы.
Прощай, каскад?!
Селекторы большой вложенности — не лучший способ ускорить ваш сайт, особенно если вы поддерживаете старые браузеры. Кроме этого, такие селекторы больше опираются на структуру документа и часто создают конфликты стилей, влияя на элементы, которые изначально затрагивать не планировалось. Однако, каскад в CSS не так ценен и нужен, как мы привыкли думать.
Как это возможно, и почему это важно? Разве неверно, что каскад должен использоваться в CSS, иначе зачем они называются «Cascading» Style Sheets?
Вернёмся к правилам именования и вспомним, что каждый БЭМ-класс имеет уникальное имя и является «самодостаточным». За редким исключением, он не зависит ни от имён тегов, ни от идентификаторов, и разные блоки никогда не пересекаются по именам классов. Это означает, что вам, скорее всего, достаточно указать один (и только один!) класс, чтобы:
- описать стили самого блока,
- описать стили любого элемента внутри блока,
- добавить дополнительные стили или переопределения с помощью модификатора.
Это покрывает большую часть CSS-задач, которые возникают при вёрстке средней сложности. При этом мы максимально упрощаем браузеру работу по разбору селектора и нахождению элементов, которые ему соответствуют. Большинство браузеров начинают применять селектор с «правой части» (охватывающей чаще всего бОльшее множество узлов) и затем уточняют полученную выборку, фильтруя её применением оставшихся правил. Чем больше шагов фильтрации требуется, тем больше времени это занимает. Современные браузеры очень хорошо оптимизированы для этих задач, но не у всех установлены последние версии, и мобильные устройства всегда могут вести себя иначе — а селекторы, состоящие из одного класса, относятся к самым быстрым селекторам из всех возможных.
На маленьких и средних страницах CSS редко является главной причиной замедления быстродействия, но набор CSS-правил должен применяться заново при каждом изменении документа. Поскольку ваш проект будет расти, скорость работы CSS рано или поздно станет важным фактором. Специалисты по юзабилити любят повторять, что 250 миллисекунд — это граница восприятия, за которой действие больше не воспринимается как «мгновенное». Чем быстрее ваш CSS изначально, тем больше пространства для манёвра будет у вас, чтобы поддерживать ощущение «всё летает».
Получается, БЭМ отменяет каскад в CSS?! Если всерьёз, то конечно же нет. Есть случаи, когда каскад нужен — если требуется указать два или более класса в одном селекторе. Например, если модификатор блока влияет на стили отдельных его элементов:
.b-text-input_disabled .b-text-input__label
{
display: none;
}
Но поскольку мы имеем не просто набор классов, а семантическую модель, другое правило, которому может потребоваться переопределить стили, заданные здесь, тоже с высокой вероятностью будет зависеть от модификатора (но другого), но специфичность-то всё равно останется та же самая! А это значит, что мы опять можем опираться только на порядок следования правил.
Есть и другие примеры, когда каскад необходим (внутренние нетривиальные зависимости элементов, сочетания модификаторов, CSS-хаки). Живые проекты всегда богаче и сложнее, чем любая методология (даже БЭМ!), но подобные стили вряд ли потребуются вам часто.
Абсолютно Независимые Блоки (коцепция АНБ)
Если стили блоков зависят друг от друга, как выразить это в CSS?
Ответ прост: лучше делать так, что зависимостей не было.
Блоки на то и «независимые сущности», чтоб содержать все нужные для своего отображения стили.
Это обычно означает некоторое количество «лишних» правил, но в виде бонуса вы получаете возможность свободно перемешать блоки на странице (или даже между разным страницами или сайтами) без учёта внешних зависимостей. По этой же причине в БЭМ-методологии рекомендуется избегать глобальных CSS-ресетов или минимизировать их количество.
Элементы блока не следуют этому принципу; им можно зависеть от стилей самого блока. Если элемент «напрашивается» на независимость, попробуйте выделить его в отдельный блок.
Альтернативные соглашения именования классов на основе БЭМ
Описанная в статье система именования классов по БЭМ — не единственная. Возможно, вы слышали о других вариантах. Что лучше выбрать?
К примеру, Nicolas Gallagher предложил некоторые улучшения, и этот пример не единственный. Многие предлагали использовать атрибуты вместо классов для обозначения модификаторов, синтаксис разделителей может быть разным, префиксы имён можно не использовать вообще или вводить много разных префиксов для разных целей.
У схемы именования, которое предложено командой разработки БЭМ из Яндекса, есть один несомненный плюс: все БЭМ-инструменты, в том числе и с открытыми исходниками, предлагаемые этой командой, ориентируются именно на такое именование. Если вы рассматриваете в будущем возможность использования этих инструментов, совместимость синтаксиса вам поможет.
Разумеется, сама БЭМ-методология на порядок важнее того, сколько чёрточек мы напишем в имени класса. Если вам нравится другой способ именования — используйте его, только убедитесь, что у вас на это есть технологическая причина.
Семантический JavaScript и БЭМ-ориентированный код
Как применить БЭМ-модель к JavaScript коду?
Многие авторы и разработчики видят в БЭМ только соглашение по именованию классов в CSS, но это заведомо упрощённый подход. Методология БЭМ была разработана, чтоб на всех уровнях (HTML, CSS, JavaScript, шаблоны, дизайн интерфейсов) можно было вводить единую семантику — своего рода «polyfill», устроенный по такому же принципу, как jQuery, предоставляющий единое гибкое API поверх пёстрого набора методов для работы с DOM.
HTML был изначально разработан как язык разметки текста, но мы сейчас используем его для построения интерактивных интерфейсов. Экспериментальные стандарты, такие как Web Components, пытаются вернуть нам контроль над семантикой, но БЭМ можно использовать уже сейчас во всех браузерах, сохраняя потенциал для интеграции с новыми современными технологиями, т.к. сама идея не зависит ни от конкретного API, ни от конкретной технологии.
Я представлю парадигму разработки с минимальными примерами кода. Наверное, выйдет очень «высокоуровневое» объяснение, но может быть сама идея станет от этого яснее. Также я ввожу термин «БЭМ-ориентированный код» — подробнее о ней ниже.
Учимся декларировать
Первый шаг — принять декларативную парадигму. Декларативное программирование — подход, который делает упор на «что», а не «как». Регулярные выражение, SQL и XSLT — хорошие примеры декларативных технологий, т.к. они определяют не последовательность выполнения низкоуровневых операций, а логику, стоящую за ними. Декларативное программирование есть описание набора условий, каждому из которых соответствуют определённые действия.
В БЭМ условия могут быть выражены через модификаторы, а действия могут совершаться только над блоками и элементами. Примеры кода в этой статье «вдохновлены» фреймворком i-bem.js
, который создал и опубликовал под свободной лицензией Яндекс, но я уверен, что любой продвинутый фреймворк позволяет реализовать схожие идеи, в том числе потому, что декларативный подход вообще очень близок веб-технологиям.
BEMDOM.decl('b-dropdown', {
onSetMod: {
disabled: function(modName, modVal) {
this.getLabel().setMod('hidden', 'yes');
if (modVal === 'yes') {
this.getPopup().hide();
}
},
open: {
yes: function() {
this.populateList();
}
}
},
/* … */
Этот пример кода определяет действия по факту установки двух модификаторов на блоке b-dropdown
.
Очень похоже на обработчики событий, но все состояния немедленно отражаются ещё и на уровне CSS, т.к. модификаторы выражаются CSS-классами, добавляемыми к соответствующим экземплярам блоков или элементов.
Другой пример с модификаторами на блоке b-editor
:
BEMDOM.decl('b-editor', {
onSetMod: {
hotkeys: {
windows: function() {
this.delMod('theme');
this.loadKeyMap('windows');
},
emacs: function() {
this.setMod('theme', 'unix');
this.loadKeyMap('emacs');
enableEasterEgg();
},
'': function() {
this.clearKeyMaps();
this.delMod('theme');
}
}
}
/* … */
Этот пример помогает понять, как модификаторами можно описать логику перехода между состояниями.
Методы
С декларативным подходом методы не всегда привязаны к компоненту «автоматически», их наличие также можно декларировать у определённых экземпляров, отвечающих набору критериев (в нашем случае — набору модификаторов):
BEMDOM.decl({ block : 'b-popup', modName : 'type', modVal : 'inplace' }, {
appear: function() {
// makeYouHappy();
}
});
Этот метод определён только у блоков, имеющих модификатор type
: inplace
.
Как и в «классическом» ООП-подходе, можно расширять семантически определённые методы, указывая ещё более специфичные декларации, уточняющие ранее заданное. Возможны как переопределения, так и доопределения. Пример:
BEMDOM.decl({ block: 'b-link', 'modName': 'pseudo', 'modVal': 'yes' }, {
_onClick : function() {
// выполняет базовый _onClick, определённый
// для всех экземпляров блока b-link
this.__base.apply(this, arguments);
// изменить внешний вид средствами CSS,
// семантически описывая смену статуса и
// оставляя конкретную реализацию автору таблицы стилей
this.setMod('status', 'clicked');
}
});
В этом доопределении метод _onClick
расширяется только для экземпляров блока b-link
, имеющих модификатор _pseudo_yes
. В остальных случаях используется реализация метода по умолчанию.
Семантика постепенно переходит из HTML-разметки в JavaScript код, при этом мы используем всё те же БЭМ-сущности.
Развесистое БЭМ-дерево
В чём смысл декларативного подхода, если вернуться от теории к практике? Главная идея — начать работать с БЭМ-деревом, которое построено и управляется вами, а не с деревом DOM, которое отражает лишь разметку (вещь, в сущности, глубоко техническую):
BEMDOM.decl('b-checkbox-example', {
onSetMod: {
js: {
inited: function() {
var checkbox = this.findBlockInside({
block: 'b-form-checkbox',
modName: 'type',
modVal: 'my-checkbox'
});
BEMDOM.append(this.domElem, 'Checkbox value: ' + checkbox.val());
}
}
}
}
);
Конечно же, есть богатый набор других API-методов, например this.elem('name')
и this.findBlockOutside('b-block')
. Я не хочу копировать документацию (она доступна онлайн), а всего лишь показываю, как вокруг БЭМ-дерева строятся методы работы с веб-приложением.
Модификация модификаторов и контроль за контролами
Предыдущий текст недостаточно осветил тему управления состояниями приложения.
После того, как состояния описаны, нужно научиться выполнять переходы между ними.
Поскольку мы работаем с БЭМ-деревом, нам помогут модификаторы как основные носители информации о состояниях.
Установка модификаторов есть установка CSS-классов, но мы не можем эффективно отслеживать эти действия (по технологическим причинам). Поэтому, вместо прямой установки классов в CSS, i-bem.js
предлагает простое API (его легко воспроизвести и в других фреймворках):
// setter
this.setMod(modName, modVal);
// getter
this.getMod(modName);
// проверка наличия
this.hasMod(modName, modVal);
// переключение
this.toggleMod(modName, modVal);
// удаление
this.delMod(modName);
Теперь мы можем изнутри реагировать на любое изменение модификатора и выполнить все действия, задекларированные для каждого конкретного случая.
БЭМ-ориентированный код
Многие JavaScript-библиотеки уже обладают достаточным потенциалом, чтобы поддержать БЭМ-методологию без привлечения внешнего сложного инструментария.
Вот короткий «чеклист» того, что должен уметь фреймворк:
- Поддерживать в том или ином виде декларативный подход
- Позволять работать с БЭМ-деревом, абстрагируясь от DOM-дерева
Независимо от встроенного API, важна возможность в нужный момент уйти от прямого взаимодействия с DOM,
по возможности ориентируясь на БЭМ-сущности. - Позволять использовать модификаторы для описания состояний
Разумеется, может потребоваться разработка дополнительных методов или плагинов.
Также очевидно, что не надо описывать модификаторами все состояния до последнего.
Начните с тех, которые удобно выражать в CSS (связанные с показом или скрытием элементов, изменением внешнего вида
в зависимости от состояний и т.п.). Не работайте с inline CSS из кода напрямую.
Если ваш любимый фреймворк может вас поддержать в этом, то БЭМ-ориентированный код можно пробовать писать прямо сейчас.
Если вы используете jQuery, можно попробовать один из этих несложных проектов для
БЭМ-ориентированной разработки:
- jQuery BEM plugin
- jQuery BEM Helpers (реализация
setMod
иgetMod
)
От правил именования классов — к Style Guide
Если вы много работаете с дизайнерами, БЭМ-методология также будет полезна.
Представьте, что у вас есть руководство по стилям проекта, сделанное Настоящим Профессиональным Дизайнером™. Скорее всего, вам его отдадут в виде увесистого PDF-файла, из которого вы узнаете об используемых в проекте шрифтах, цветовых схемах, принципах взаимодействия элементов интерфейса и прочих умных терминах. Такой стайлгайд может быть интересно прочесть, особенно в свободное от работы время, но для обычных фронтенд-разработчиков пользы от такого руководства мало: на уровне кода они оперируют совсем-совсем иными понятиями и терминами, и никакой особой связи между ними и глянцевым «стайлгайдом» не ощущают.
Может, было бы лучше, если вы как разработчик могли говорить с дизайнером «на одном языке»? Не всем программистам интересно слушать «про фотошоп», не все дизайнеры хотят программировать. А что, если стайлгайд мог бы быть библиотекой интерфейсных блоков, описанных в терминах БЭМ? Такая библиотека включала бы в себя все основные «кирпичики», из которых и строится интерфейс сайта или приложения.
Если дизайнер начинает использовать терминологию БЭМ, он может очень быстро перейти от дизайна «экранов» к работе с конкретными блоками и элементами.
Это также поможет выделить и лучше описать похожие компоненты из различных частей интерфейса. Визуальные вариации одного и того же компонента можно сразу начать называть модификациями (модификаторами), а затем с их помощью описать и основные состояния каждого блока (если блок интерактивен или меняет поведение в зависимости от условий).
Кроме этого, интерфейс почти что «сам собой» поделится на небольшие «кирпичики», которые упростят оценку времени и сил, необходимых на их реализацию (согласитесь, с этим проще работать, чем с «неделимым» экраном-макетом). Вы довольно быстро придёте к прототипированию с помощью схем или скетчей: если блоки имеют имя, описанный внешний вид и поведение, отрисовывать их в стиле «pixel perfect» во многих случаях вовсе необязательно. Ещё важнее, что эта модель напрямую (порою — один-в-один) соответствует организации вашего кода! В вашем коде — все те же самые блоки и элементы, с теми же именами и тем же поведением. Если вы с БЭМ не первый день, может оказаться, что многие блоки уже реализованы и их достаточно просто переиспользовать или расширить.
Главное достижение, конечно, в исчезновении пропасти между кодом и дизайном, в использовании одних и тех же сущностей при проектировании интерфейсов и написании кода — притом, что дизайнерам не надо учить программирование, а разработчикам — осваивать UX-инструментарий. Дизайнер может вообще не понимать, как работает ваш код, но говорить вы уже будете «на одном языке».
В большй команде, разделение на блоки (модульный подход) позволяет легче распараллеливать разработку, и защищает от ситуации «единоличного контроля» разработчика над важной частью проекта, поскольку сам БЭМ-подход поощряет модульность. Кроме этого, в любом самом сложном коде базовые приёмы работы с данными (БЭМ-дерево, модификаторы, взаимодействие блоков) будут одинаковыми; разбираться в таком коде проще, поддерживать его — легче. Отсюда, кстати, переходим к следующему разделу:
БЭМ как высокоуровневая документация проекта
Давайте признаемся честно: разработчики редко пишут документацию в достаточном объёме. Передача проектов между командами, смена разработчиков — процесс нетривиальный. В поддержке кода важно минимизировать время, которое разработчик тратит на то, чтоб понять, как всё устроено, где что лежит и как вообще это работает — порой именно на это уходит львиная доля времени, а вовсе не на реализацию функциональности или исправление ошибки.
Конечно, очень круто иметь документацию для всего, но в реальном мире её частенько не бывает. Но даже когда документация есть, она описывает методы, свойства или API модулей, но редко касается «цикла работы» компонента, его возможных состояний и переходов между ними. Причина проста: это высокоуровневая семантика, и если не применять никакой методологии, то сам по себе код не даёт инструментов для её описания, поэтому и документация к коду часто не помогает: все методы описаны досконально, а как что работает — непонятно.
Однако, если проект использует принципы БЭМ-ориентированного кода, описанные выше, вы сразу же сможете понять следующее:
- с какими элементами компонента (блока) вы работаете,
- с какими внешними блоками взаимодействует компонент,
- какие состояния (модификаторы) нужно учесть, если добавляется новое или исправляются ошибки.
С примерами обычно проще. Что вы можете сказать о том, как устроен и работает описанный ниже блок?
// блок
b-popup
// модификаторы и их значения
_hidden
_size _big
_medium
_large
_direction _left
_right
_top
_bottom
_color-scheme _dark
_light
// элементы
__anchor-node
__popup-box
__close-btn
__controls
__ok
__cancel
Вы прочли только лишь описание БЭМ-структуры (за считанные секунды), и даже самого кода не видели, но наверняка можете уже рассказать мне, автору примера, что делает этот блок и как примерно он может работать!
Заметьте, документации вы в этом примере тоже не видели. Такое БЭМ-описание можно сгенерировать автоматически силами CSS-препроцессора, описать в YAML или ему подобных языках.
БЭМ и файловая структура
Когда проект быстро растёт, неконсистентная файловая структура может вас сильно задерживать. Чем сложнее проект, тем более сложной и менее гибкой будет его структура. Инструменты и фреймворки не всегда хорошо помогают организовать файлы в проекте, т.к. часть из них навязывает свою, специфичную строго для данного инструмента структуру файлов и папок (а если потом переходить на что-то новое?!), а некоторые фреймворки вообще ничего на эту тему не предлагают — делай как знаешь, мол.
Кроме вас самих, выбрать структуру проекта никто не может. Идеи БЭМ и тут могут оказаться полезными, потому что БЭМ — не фреймворк, а принцип.
Библиотека блоков
Папка с блоками — «базовое понятие» любой файловой структуры, ориентированной на БЭМ-подход. Имена блоков коротки, описательны и уникальны в пределах проекта — и прекрасно подходят для именования вложенных папок. Сами по себе блоки внутри проекта равноправны, поэтому их проще хранить в виде плоской структуры как набор папок на одном уровне:
/blocks
/b-button
/b-heading
/b-flyout
/b-menu
/b-text-field
Внешние инструменты (фреймворки и т.п.) также могут быть определены как блоки.
Пример:
/blocks
…
/b-jquery
/b-model
Внутри каждой папки блока проще всего выделить каждой «технологии» по отдельному файлу:
/b-menu
b-menu.js
b-menu.css
b-menu.tpl
Более «продвинутый» подход — вынести данные для некоторых элементов и модификаторов в отдельные подпапки, реализуя модульный подход:
/b-menu
/__item
b-menu__item.css
b-menu__item.tpl
/_horizontal
b-menu_horizontal.css
/_theme
/_dark
b-menu_theme_dark.css
/_light
b-menu_theme_light.css
b-menu.css
b-menu.js
b-menu.tpl
Это даёт вам больше контроля над кодом, но такая структура требует времени на создание и поддержку. Выбирайте сами.
Уровни переопределения
Что если вам требуется расширить стили или функциональность компонент, или задействовать код в нескольких проектах одновременно? Общая (разделяемая) библитека блоков должна поддерживать переопределение и расширение функциональности. В случае JS-кода нам помогут принципы ООП, но как быть со стилями и шаблонами? БЭМ решает эту проблему, ввода для всех используемых технологий (JS, HTML, CSS) понятие уровней переопределения.
Когда вы определитесь с файловой структурой, она будет одинаковой у каждого блока. Поэтому несколько разных библиотек блоков могут легко находиться на разных уровнях приложения.
Например, можно иметь библиотеку общих блоков и несколько более специфичных библиотек для отдельных страниц или разделов сайта:
/common
/blocks
/b-heading
/b-menu
…
/pages
/intro
/blocks
/b-heading
b-heading_decorated.css
/b-demo
/b-wizard
…
При таком подходе в /common/blocks
мы будем складывать блоки, используемые во всём приложении.
Для каждой страницы (в нашем примере /pages/intro
) мы вводим новый уровень переопределения: отдельную библиотеку, /pages/intro/blocks
, которая добавляет новые блоки и расширяет (если требуется) некоторые «общие» (в нашем примере обратите внимание на дополнительный модификатор _decorated
для общего блока b-heading
).
Инструменты сборки проекта могут использовать уровни переопределения для создания ресурсов, специфичных только для одной страницы или раздела (к примеру, набор стилей, используемых только на этой странице).
Разделение библиотек может быть основано на форм-факторе устройства:
/common.blocks
/desktop.blocks
/mobile.blocks
Библиотека common
находится на верхнем уровне, а mobile
или desktop
расширяют её, являясь следущими уровнями переопределения. Этот же механизм позволяет нескольким проектам использовать общие блоки, в том числе и общий набор интерфейсных компонент, реализующий единый стиль на нескольких сайтах или сервисах.
Сборка
БЭМ-подход довольно гранулярен (много мелких файлов). Это удобно для разработки, а в продакшне представляет проблему. Почти наверняка мы хотим загружать фронтенд-ресурсы минимальным числом сетевых запросов, сжимать их и кэшировать. Для этого нужно определить и реализовать понятие «процесс сборки проекта».
Яндекс выпустил инструмент-сборщик с открытыми исходниками по имени Борщик, который поможет собрать вместе файлы JavaScript и CSS и облегчит подключение внешних инструментов для их дальнейшей оптимизации, например UglifyJS или CSS Optimizer. Есть решения типа RequireJS, которые также помогают реализовать сборку, описание и отслеживание зависимостей.
Хочется ещё больше возможностей и больше фокуса на БЭМ-методологию? Попробуйте bem-tools.
Очень важный урок, который я усвоил, работая с БЭМ: не надо бояться гранулярности, если есть чёткое понимание, как собрать всё воедино.
Утилиты, библиотеки, фреймворки… что дальше?
Довольно долго я скептически относился к идее применения БЭМ на небольших проектах. Но недавний опыт работы в стартап-проекте показал, что мы можем сместить фокус в сторону идей, не привязываясь к конкретной реализации или фреймворку, и получить главный бонус, ради которого БЭМ и был придуман: единую семантику во всех фронтенд-технологиях.
БЭМ научил меня мыслить вне рамок конкретных фреймворков и утилит.
Я хорошо помню время, когда разработчики всерьёз обсуждали наилучшие способы назначить обработчик событий в браузере. Помню, как библиотеки для работы с DOM боролись за мировое господство. Помню, как стало модно использовать фреймворки, предложившие высокоуровневые подходы. БЭМ я воспринимаю как следующий уровень, как «фреймворк для идей» в веб-разработке, не привязывающий никого к конкретным инструментам.
Хотите узнать больше? Заходите на сайт БЭМ, читайте статьи, знакомьтесь с кодом, загружайте полезные модули, задавайте вопросы и помогайте развивать проект.
Автор: ingdir