Page Content
Занудный взгляд на БЭМ⁠, размышления о том⁠, что есть в БЭМ и о том⁠, чего ей не хватает⁠, с картинками и аргументами⁠.
Публикация от ⁠. Крайнее обновление ⁠.
Методология BEM
БЭМ — воплощение принципа Абсолютно Независимого Блока⁠. Все его сущности должны быть переносимыми и независимыми от других сущностей страницы без прямого на то указания⁠. Согласно определению сущностей в БЭМ от 2015 года⁠:
- Block / Блок
- логически и функционально независимый компонент страницы, аналог компонента в Web Components⁠, блок инкапсулирует в себе поведение (JavaScript), шаблоны, стили (CSS) и другие технологии реализации;
- Element / Элемент
- составная часть блока, которая не может использоваться в отрыве от него⁠;
- Modificator / Модификатор
- БЭМ-сущность⁠, определяющая внешний вид⁠, состояние и поведение блока или элемента⁠.
Если проще⁠, то Блок — это более-менее целостный компонент страницы, с описанными выше признаками, Элемент — его составная часть⁠, а Модификатор — CSS-сущность модифицирующая / дополняющая стили Элемента.
В БЭМ Блоком могут являться и логотип⁠, и форма авторизации, и шапка сайта⁠.
Если по ходу прочтения статьи вы обнаружили расхождение с актуальной версией методологии БЭМ⁠, пишите — будем вместе править и дополнять⁠.
Что не так в BEM⁠?
Этот вопрос мучает меня пару лет⁠. И, знаете, мой ответ⁠, похоже, вас удивит⁠:
БЭМ прекрасен⁠!
Прекрасен в рамках своего применения. Главная сложность в том⁠, чтобы осознать те самые рамки⁠. То что⁠, в БЭМ Блоком могут являться и логотип⁠, и форма авторизации, и шапка сайта⁠. Это не плохо и не хорошо⁠, — это неоднозначно⁠.
В методологии БЭМ⁠, есть упоминание принципа разделения компонентов интерфейса по функциональному признаку. Он находит отражение в определении Блока на 2015 год⁠:
Блок — логически и функционально независимый компонент страницы
Во-первых⁠, меня смущает фраза «логически независимый»⁠: «логически» в интерфейсе или в функциональной плоскости?
Конечно, нельзя говорить о пользовательском интерфейсе программного обеспечения в отрыве от функциональности самого ПО⁠, ибо интерфейс — это и есть средство управления / общения с данным функционалом ПО⁠, в частности⁠, и программным обеспечением, в целом⁠. Но вопрос⁠, всё же⁠, остаётся.
Во-вторых⁠, Блок не просто «логически независимый»⁠, он «логически и функционально независимый компонент страницы». Тогда какая именно «логическая независимость» предполагалась авторами? И функционально независим от чего⁠? Автор, конечно же⁠, имеет в виду независимость от соседних / сестринских компонентов страницы.
Необходимо более явно обозначить признак разделения компонентов интерфейса.
Да, я откровенно раздуваю маленькие неоднозначности в определениях⁠. Но ведь четкое и понятное формулирование терминов — это залог правильного пониманию всей методологии. Допускаю, что для многих эти вопросы существуют, как и для меня⁠.
Зачастую, при попытке отнесения компонента к одному из типов сущностей БЭМ⁠, были доводы как в пользу Блока⁠, так и в пользу Элемента.
Помню, что несколько лет назад⁠, в документации БЭМ⁠, определение Блока было иным⁠. Там не было упоминания Web Components⁠, инкапсуляции и логической независимости. А было старое определение БЭМ от 2011 года вот что⁠:
- Block / Блок
- некая самостоятельная сущность, кирпичик проекта. Блок содержит информацию о самом себе и может знать о своих детях — элементах блока. Они могут использоваться сами по себе или внутри других блоков⁠;
- Element / Элемент
- часть блока⁠, которая отвечает за какую-то отдельную функцию. Элементы блока имеют смысл только в рамках своего родителя. Могут быть обязательными и не обязательными⁠;
- Modificator / Модификатор
- это свойство блока или элемента⁠, которое меняет внешний вид или поведение⁠, модификатор имеет имя и значение⁠, одновременно может использоваться несколько разных модификаторов.
В терминах упоминался функциональный признак, но теперь только для Элемента⁠. В целом⁠, признак разделения также был туманным⁠.
Общей, однозначной и бесспорной является идея наличия подчинённости «Блок–Элемент»⁠.
БЭМ-дерево описанной структуры:
block
├──block__element
├──block__element
└──block__element
└──block2
Данная подчинённость безумно проста и тем прекрасна⁠. Реализация этой идеи позволяет вывести код из состояния «лапшички» в куда более структурированный, как внутри файла⁠, так и на уровне структуры проекта на файловой системе.
А что нужно-то еще⁠? БЭМ хорош⁠: оброс богатым инструментарием, поддерживается достаточная для использования документация, существует большое сообщество.
Но в своей сути БЭМ порой неоднозначен, мне хочется видеть в методологии более четкий признак деления компонентов интерфейса по типам⁠.
Анализируя свой код⁠, я раз за разом обнаруживаю папки /layout/
⁠, /pages/
⁠, /modules/
⁠, /UI/
⁠, /content/
и т.п. Если по существу⁠, то хочется более осмысленной системы упорядочивания кода согласно какому-либо очевидному признаку.
Я согласен с разработчиками БЭМ⁠, что главным признаком тут должен стать функциональный признак, упоминаемый в определении Блока в текущей редакции и в определении Элемента в БЭМ от 2011 года⁠.
Одна из идей заключается в том⁠, чтобы трансформировать связь «Блок–Элемент» в связь «Родитель–Потомок»⁠, где
Родителем может быть любой компонент интерфейса стоящий выше по иерархии⁠, согласно Методологии.
А за Элемент принимаем любой DOM-элемент⁠, находящийся в непосредственной подчинённости своего Родителя, прежде всего Блока⁠.
parent
├──parent__child
├──parent__child
└──parent__child
Таким образом схема «Блок–Элемент» «Родитель–Потомок» становится более прозрачной и очевидной для применения⁠.
Ниже, представляю вашему вниманию видение разделения веб-страницы на компоненты пользовательского интерфейса. Речь не о замене БЭМ⁠, а о его дополнении⁠.
BEM + Module = MoBE
По БЭМ веб-страницу разделяют на следующие компоненты интерфейса: Блок и Элемент⁠.
В ходе своей деятельности в качестве разработчика, у меня появился некий диалект методологии БЭМ⁠, назвал я его MoBE (Модуль-Блок-Элемент)⁠.
Предлагаемая мной схема проще⁠, используемые в ней сущности идентифицируются однозначно.
Я использую бОльшее количество типов компонентов интерфейса, согласно, прежде всего⁠, их функционального предназначения, вот главные из них⁠:
- Module / Модуль
- компонент интерфейса комплексно решающий задачу пользователя за счёт имеющегося функционала, состоит из Блоков и Элементов⁠, не может включать другие Модули⁠;
- Block / Блок
- компонент интерфейса, реализующий часть функционала Модуля, состоит из Элементов⁠, может включать другие Блоки⁠, экземпляры Блока могут повторяться;
- Element / Элемент
- компонент интерфейса, является составной частью других компонентов других типов⁠.
Важно отметить, что вывод полезной информации — это тоже функционал приложения.
Модуль, является сутью интерфейса, является средством в работе пользователя для достижения цели⁠.
Блок несет в себе функционал необходимый пользователю для решения конкретной пользовательской задачи в рамках Модуля⁠.
Блок, и его набор функциональности, сам по себе не нужен пользователю или недостаточен для полноценного взаимодействия пользователя с интерфейсом⁠. И Элемент⁠, сам по себе⁠, в отрыве от Блока и Модуля⁠, не нужен пользователю. Элемент — это атомарный DOM-элемент⁠, лишённый всякого функционала полезного пользователю.
В качестве рекомендации предлагаю, в дополнение⁠, использовать другие сущности: Страница и Лейаут⁠.
- Page / Страница
- компонент интерфейса представляющий полный перечень целевого функционала всех Модулей на веб-странице⁠, состоит из Модулей⁠, Блоков и Элементов⁠;
- Layout / Лейаут
- компонент интерфейса не представляющий самостоятельно целевого функционала, включает полезный функционал только за счет включаемой Страницы, может также включать в себя Блоки и Элементы⁠.
По БЭМ⁠, данные сущности являются Блоками, и имеют реализацию через префиксы: линк⁠, линк⁠, линк⁠.
Страница включает в себя все Модули⁠. Блоки и Элементы могут включаться на всех уровнях иерархии компонентов интерфейса.
MoBE-дерево компонентов страницы выглядит, примерно, следующим образом:
layout
├── page
| ├── module
| ├── module
| | ├── block
| | └── block
| | └── block
| ├── block
| └── block
| └── block
├── block
└── block
└── block,
где layout, page, module и block — сущности в терминах MoBE.
*Элементы в схеме не отражены, но они существуют.
Отражение Элементов в MoBE-дереве не имеет смысла: Элементы не существуют без Блока⁠, определяя Блок мы заведомо определяем Элементы. Да и учитывая⁠, что Элемент в терминах MoBE — это DOM-элемент⁠, MoBE-дерево превратится в DOM-дерево⁠.
А структура шаблонов проекта выглядит, примерно, так:
mySuperDuperProject__Views
├── layouts
├── page
└── modules
└── blocks,
где
layouts — папка с Лейаутами,
page — папка с общими (конструктивными) Блоками Страниц,
modules — папка с Модулями,
blocks — папка с Блоками
Контекст иерархии
Стоит обратить внимание, что выше⁠, описывая связи MoBE-сущностей⁠, я не упомянул про DOM-структуру данных связей⁠. На самом деле⁠, я рекомендую расширять один Блок на другой Блок через Элемент первого, т.е. используется промежуточный DOM-элемент⁠, который является Элементом расширяемого Блока⁠.
MoBE-дерево описанной структуры:
block
└──block2
DOM-дерево описанной структуры:
block
├──block__element
├──block__element
└──block__element
└──block__element
└──block2
Мы можем прозрачно влиять на представление расширяющего Блока (положение, размеры) в контексте Родителя через Элемент расширяемого Блока⁠.
Цель — сохранить расширяющий Блок в исходном виде⁠.
Задав положение, поведение и размеры области доступной для расширяющего Блока⁠, мы определяем представление самого Блока в контексте Родительского Блока без модификации⁠.
Дело в том⁠, что Элемент одного Блока вкупе с вложенным Блоком создаёт новый контекст иерархии. Данное соглашение позволяет сохранять прозрачность связи «Родитель–Потомок»⁠.
А вот смешивать Элемент и Блок на одном DOM-элементе не рекомендую по описанным выше причинам.