Pull to refresh

Попробуй БЭМ на вкус!

Website development *JavaScript *HTML *
Tutorial
Эта статья рассказывает о том, как создать проект с использованием БЭМ-технологий.
Мы шаг за шагом создадим страничку каталога товаров, пользуясь принципами БЭМ в CSS, возможностями писать декларативный JavaScript на фреймворке i-bem.js и с использованием шаблонизатора BEMHTML. Помогать делать всё это будут bem tools, в частности — инструмент для разработки bem server.

Онлайн магазин

Важно: в статье нет особенных подробностей, её цель — получить проект максимально быстро. Текст, раскрывающий больше информации, пройдёт следующим постом.

Что такое БЭМ?


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

Если ранее вы не сталкивались с БЭМ, вам стоит вначале просмотреть материалы сайта bem.info, а затем вернуться к этой статье.
Для тех, кому больше нравится видео, могу предложить запись доклада с WebConf Riga 2012 (на английском) или выступление Сергея Бережного (@veged) на РИТ 2011.

Необходимые инструменты


Чтобы пройти по всем шагам этого мануала, вам нужно установить bem tools. Это набор инструментов с command line интерфейсом для оперирования БЭМ-сущностями и сборки проекта. Инструкция по установке есть в описании репозитория.
На момент написания этой статьи была актуальна версия 0.5.21.

Создание собственного репозитория проекта


Проще всего создать свой проект простым копированием существующего репозитория с подходящей структурой. Для проекта с использованием полного стека БЭМ подойдёт репозиторий project-stub. На момент напиания статьи использовалась ревизия 5ac5d2d2567ca6d52d82f95b219bca6f49ef5cc4.

$ git clone git://github.com/bem/project-stub.git my-pretty-project
$ cd my-pretty-project/
$ git reset --hard 5ac5d2d2567ca6d52d82f95b219bca6f49ef5cc4
$ rm -rf .git
$ git init

Затем проект нужно собрать. Для этого запускается команда

make

Это занимает некоторое время, потому что именно в этот момент в директорию проекта устанавливаются все необходимые npm-пакеты.
В конце вы увидите следующее сообщение:

info: Server is listening on port 8080. Point your browser to http://localhost:8080/

На вашем компьютере запустился bem server — инструмент для разработки, который будет автоматически пересобирать ваш проект, если вы внесете в него изменения.

Внесение изменения в страницы


Сейчас на вашем проекте есть одна страница index.html, которую вы можете открыть в браузере.
Первый запрос к странице будет обрабатываться заметное время, потому что в этот момент bem server подгружает необходмые для её сборки библиотеки.

Структура проекта предполагает, что блоки будут размещены в директории desktop.blocks, а страницы — в директории desktop.bundles.
Вообще, строго говоря, desktop.bundles хранит «набор» блоков. Это могут быть частоиспользуемые блоки нескольких страниц (то, что обычно называют common), наборы, объединяющие несколько страниц (all, если объединены все страницы) или — самый простой случай — наборы блоков, каждый из которых соответствует одной странице. Здесь будет рассматриваться последний, простой вариант.

Вы можете отредактировать страницу, меняя файл desktop.bundles/index/index.bemjson.js.

Описание блока в bemjson


Сначала мы разместим на странице Шапку. В терминах БЭМ это блок head:

{ block: 'head' }

Здесь и далее полный код страницы на разных стадиях можно будет находить на Gist: https://gist.github.com/4175550.

Перезагрузив страницу, вы увидите что в ней появился соответствующий <div>.

<!DOCTYPE html>
<html class="i-ua_js_yes i-ua_css_standard">
    <head>...</head>
    <body class="b-page b-page__body">
        <div class="head"></div>
    </body>
</html>

Шапку мы наполним содержанием: форма поиска, логотип и раскладка, располагающая содержание как нужно.

Сначала в BEMJSON-описании страницы внутрь блока head помещаем блок layout с двумя элементами: left и right.

content: [
    {
        block: 'head',
        content: {
            block: 'layout',
            content: [
                {
                    elem: 'left',
                    content: 'left here'
                },
                {
                    elem: 'right',
                    content: 'right here'
                }
            ]
        }
    }
]

https://gist.github.com/4175573

<!DOCTYPE html>
<html class="i-ua_js_yes i-ua_css_standard">
    <head>...</head>
    <body class="b-page b-page__body">
        <div class="head">
            <div class="layout">
                <div class="layout__left">left here</div>
                <div class="layout__right">right here</div>
            </div>
        </div>
    </body>
</html>

Это создаст необходимую разметку (вы можете увидеть её, обновив страницу), к которой нужно написать стили. То есть реализовать блок layout в технологии CSS.

Создание блока


Для создания файла технологии воспользуемся командой bem create.

$ bem create -l desktop.blocks/ -T css -b layout

Команда создаст файл desktop.blocks/layout/layout.css, в котором уже есть селектор, соответствующий файлу блока. Правило нужно дополнить соответственно назначению блока.
Сейчас можно просто скопировать: https://gist.github.com/4175598

Использование блоков из библиотек


Вложенные в layout блоки поисковой формы и логотипа реализовывать самостоятельно не нужно. Они уже реализованы в библиотеке bem-bl, достаточно просто задекларировать их на странице. То есть вставить BEMJSON-описание блока в страницу desktop.bundles/index/index.bemjson.js

Для нашей страницы мы воспользуемся блоками b-search и b-logo.
https://gist.github.com/4175640

Картинку для логотипа можно взять отсюда или указать свою.

Использование блоков из библиотеки

Доопределение блоков библиотек


Доопределение в CSS

Используемый нами блок b-logo предоставляет только нужную разметку. CSS для неё каждый разработчик может написать сам, потому что всем нужна разная разметка.

Эту разметку мы поместим в блок b-logo на своём уровне переопределения.

$ bem create -l desktop.blocks/ -T css -b b-logo

Разметку для блока можно взять отсюда: https://gist.github.com/4175675

То же самое для блока b-search:

$ bem create -l desktop.blocks/ -T css -b b-search

https://gist.github.com/4195433

Шапка со стилями

Доопределение BEMHTML



Чтобы сделать страницу центрированной, нужен дополнительный контейнер. Для этого мы доопределим шаблоны блока b-page, создав такой же блок на своём уровне. В качестве шаблонизатора используется BEMHTML.

$ bem create -l desktop.blocks/ -b b-page -T bemhtml

В получившемся файле desktop.blocks/b-page/b-page.bemhtml нужно написать код, оборачивающий контент блока в дополнительный контейнер.

block b-page, content: {
    elem: 'body-i',
    content: this.ctx.content
}

https://gist.github.com/4175742

<!DOCTYPE html>
<html class="i-ua_js_yes i-ua_css_standard">
    <head>...</head>
    <body class="b-page b-page__body">
        <div class="b-page__body-i">
            <div class="head">
                <div class="layout">...</div>
            </div>
        </div>
    </body>
</html>

Для получившейся разметки создаются свои CSS-правила:

$ bem create -l desktop.blocks/ -T css -b b-page

Контент для получившегося файла desktop.blocks/b-page/b-page.css можно скопировать отсюда: https://gist.github.com/4175763

А для того, чтобы блок шапки был заметен на странице, я задам ему border:

$ bem create -l desktop.blocks/ -T css -b head

Контент для файла desktop.blocks/head/head.css: https://gist.github.com/4175776.

Шапка с рамкой

BEMHTML шаблоны


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

Например, давайте разместим на странице список товаров. Он представлен в BEMJSON-декларации страницы блоком goods, и декларация содержит необходимые данные.

{
    block: 'goods',
    goods: [
        {
            title: 'Apple iPhone 4S 32Gb',
            image: '1450827127820366493466',
            price: '259',
            url: '/'
        },
        {
            title: 'Samsung Galaxy Ace S5830',
            image: 'http://mdata.yandex.net/i?path=b0206005907_img_id5777488190397681906.jpg',
            price: '73',
            url: '/'
        },
        ...
 }

https://gist.github.com/4176078

Для того, чтобы эти данные превратились в нужную разметку, блок должен быть реализован в технологии BEMHTML. Для внешнего вида — технология CSS. Поэтому можно создать блок во всех технологиях, предусмотренных по умолчанию.

$ bem create -l desktop.blocks -b goods

В BEMHTML шаблоне блока desktop.blocks/goods/goods.bemhtml нужно написать код, который превратит JSON с данными в элементы блока. А также, пользуясь модой tag указать, какими DOM-элементами представить блок и его элементы.

block goods {

    tag: 'ul'

    ...

    elem item, tag: 'li'

    elem title, tag: 'h3'

}

https://gist.github.com/4176118

<!DOCTYPE html>
<html class="i-ua_js_yes i-ua_css_standard">
    <head>...</head>
    <body class="b-page b-page__body">
        <div class="b-page__body-i">
            <div class="head">...</div>
            <ul class="goods">
                <li class="goods__item">
                    <h3 class="goods__title">Apple iPhone 4S 32Gb</h3>
                    <img class="goods__image" src="1450827127820366493466"/>
                    <span class="goods__price">259</span>
                </li>
                <li class="goods__item">...</li>
                <li class="goods__item">...</li>
            </ul>
        </div>
    </body>
</html>

Шаблон может создавать не только элементы блока, но и другие блоки. Например, цену товара можно завернуть в ссылку, используя для этого блок b-link из библиотеки bem-bl.

{
    elem: 'price',
    content: {
        block: 'b-link',
        url: item.url,
        content: item.price
    }
}

https://gist.github.com/4176996

Кроме того, для того, чтобы избежать каскада при оформлении этой ссылки стилями, её можно пометить как элемент блока goods.

{
    block: 'b-link',
    mix: [{ block: 'goods', elem: 'link' }],
    url: item.url,
    content: item.price
}

https://gist.github.com/4177113

...
<ul class="goods">
    <li class="goods__item">
        <h3 class="goods__title">Apple iPhone 4S 32Gb</h3>
        <img class="goods__image" src="1450827127820366493466"/>
        <span class="goods__price">
            <a class="b-link goods__link" href="/">259</a>
        </span>
    </li>
    <li class="goods__item">...</li>
    <li class="goods__item">...</li>
</ul>

Также нужно пометить элементы о новых товарах модификатором и добавить выравнивающих элементов.
https://gist.github.com/4177157

CSS для блока можно скопировать отсюда https://gist.github.com/4177163.
Создавать блок отдельно в технологии CSS не нужно, потому что он изначально был создан со всеми необходимыми файлами.

Список товаров

Понадобится и CSS для IE. Он не входит в список технологий по умолчанию.

$ bem create block -l desktop.blocks/ -T ie.css goods

Содержание для получившегося файла desktop.blocks/goods/goods.ie.css можно взять на Gist https://gist.github.com/4177174

Зависимости блоков


Помимо декларации нужно гарантировать подключение к странице шаблонов, CSS и JavaScript блока. Для этого у блока можно описать зависимости, это делается представлением блока в технологии deps.js.

$ bem create -l desktop.blocks/ -T deps.js -b goods

Можно воспользоваться нестрогой зависимостью shouldDeps, указав, что нужен блок b-link.

({
    shouldDeps: [
        { block: 'b-link' }
    ]
})

https://gist.github.com/4177031

Подключение библиотек


Хочется представить шапку и каждый товар модными прямоугольниками с тенью. Блок для этого я позаимствую из библотеки моего друга.
Там есть всего один блок, который называется box и делает то, что мне нужно.

Чтобы получить код библиотеки, мне нужно указать её адрес в ./bem/make.js, по аналогии с соседними библиотеками.

getLibraries: function() {

    return {
        'bem-bl': {
            type: 'git',
            url: 'git://github.com/bem/bem-bl.git',
            treeish: '0.3'
        },
        'bemhtml' : {
            type: 'git',
            url: 'git://github.com/bem/bemhtml.git'
        },
        'john-lib' : {
            type: 'git',
            url: 'git://github.com/john-johnson/j.git'
        }
    };

}

https://gist.github.com/4177229

А также указать в настройках бандлов (страниц), что этот уровень нужно использовать при сборке. Это делается в файле desktop.bundles/.bem/level.js.

exports.getConfig = function() {

    return BEM.util.extend(this.__base() || {}, {
        bundleBuildLevels: this.resolvePaths([
            '../../bem-bl/blocks-common',
            '../../bem-bl/blocks-desktop',
            '../../bemhtml/common.blocks',
            '../../john-lib/blocks/',
            '../../desktop.blocks'
        ])
    });

};

https://gist.github.com/4177250

К сожалению, пока что при изменении конфигурации проекта приходится перезапускать bem server. Текущий процесс придётся прервать и снова набрать команду make.
В будущих версиях необходимость перезапуска обещают убрать.

Миксы блоков и элементов


Теперь можно использовать блок box. Я могу просто обернуть им мои блоки. Но чтобы сэкономить разметку, можно смешать на одной DOM-ноде 2 блока. Это называется mix.

Один из способов смешения — описать его во входных данных (BEMJSON).
В данном случае нужно смешать блок head с блоком box, изменив код страницы.

{
    block: 'head',
    mix: [ { block: 'box' } ],
    content: ...
}

https://gist.github.com/4177292

<!DOCTYPE html>
<html class="i-ua_js_yes i-ua_css_standard">
    <head>...</head>
    <body class="b-page b-page__body">
        <div class="b-page__body-i">
            <div class="head box">
                <div class="layout">...</div>
            </div>
            <ul class="goods">...</ul>
        </div>
    </body>
</html>

Не забудьте записать блок box в зависимости блока head

$ bem create -l desktop.blocks/ -T deps.js -b head

({
    shouldDeps: [
        { block: 'box' }
    ]
})

https://gist.github.com/4235143

Шапка с блоком box

Смешивать можно не только блоки, но и элементы с блоками.
В шаблоне блока goods смешаем каждый элемент item с блоком box.

content.push({
    elem: 'item',
    mods: mods,
    mix: [{ block: 'box' }],
    content: ...

https://gist.github.com/4177350

<!DOCTYPE html>
<html class="i-ua_js_yes i-ua_css_standard">
    <head>...</head>
    <body class="b-page b-page__body">
        <div class="b-page__body-i">
            <div class="head box">
                <div class="layout">...</div>
            </div>
            <ul class="goods">
                <li class="goods__item box">...</li>
                <li class="goods__item box">...</li>
                <li class="goods__item box">...</li>
                <li class="goods__item goods__item_new_yes box">...</li>
                <li class="goods__item box">...</li>
                <li class="goods__sizer">...</li>
                ...
            </ul>
        </div>
    </body>
</html>

Товары в блоке box

Декларативный JavaScript


Блоки с JavaScript функциональностью


Блок box, который появился у меня на проекте благодаря подключенной сторонней библиотеке, предоставляет также и динамическую JavaScript-функциональность — он умеет сворачиваться.

Для сообщения, что я хочу использовать эту JavaScript-функциональность в шапке, мне нужно изменить описание блока head, указав, что у примешиваемого блока box есть JavaScript-реализация:

mix: [{ block: 'box', js: true }]

https://gist.github.com/4202622

Также требуется разместить внутри блока элемент switcher

content: [
    {
        block: 'layout',
        ...
    },
    {
        block: 'box',
        elem: 'switcher'
    }
]

https://gist.github.com/4202651

Получается блок со стрелочкой, которая умеет сворачивать и разворачивать его.

Стрелочка

Доопределение JavaScript


Мне недостаточно JavaScript-функциональности блока box. Я хочу, чтобы он сворачивался не только по вертикали, но и по горизонтали. При этом вносить изменения в чужую библиотеку я не могу. Но благодаря тому, что JavaScript блока написан с использованием декларативного фреймворка из блока i-bem, у меня есть возможность изменить (переопределить или доопределить) поведение блока на своём уровне.

bem create -l desktop.blocks -T js -b box

В получившемся файле desktop.blocks/box/box.js нужно оставить только секцию onSetMod, описывающую реакцию на установку модификаторов.

onSetMod : {

}

https://gist.github.com/4195865

В данном случае нужно реагировать на установку и снятие модификатора closed:

onSetMod : {

    'closed': {
        'yes': function() {
            // some functionality here
        },
        '': function() {
            // some functionality here
        }
    }

}

https://gist.github.com/4195879

Создание новых страниц


Страницы — это тоже блоки, на своём уровне переопределения. Поэтому для их создания тоже можно воспользоваться командой bem create:

bem create -l desktop.bundles -b contact

Флаг -T можно не указывать, потому что bem create благодаря настройкам уровня desktop.bundles знает, что создаваемые на этом уровне блоки должны быть представлены в технологии BEMJSON. Так, появляется файл desktop.bundles/contact/contact.bemjson.js с минимальным содержимым для страницы.

Новую страницу можно посмотреть по адресу http://localhost:8080/desktop.bundles/contact/contact.html
bem server соберёт её HTML, JS и CSS файлы в момент первого обращения.

Выкатка


Всё время, пока мы разрабатывали проект, работал bem server и пересобирал те части проекта, которые нуждаются в изменении при обновлении страниц.

Для выкатки в продакшен тоже нужна сборка проекта, но уже всего проекта целиком, вне зависимости от того, изменилось что-то или нет. Для этого можно воспользоваться командой bem make.
Рекомендуется запускать локальную для данного проекта версию пакета:

./node_modules/bem/bin/bem make


благодарность За подготовку разметки сайта большое спасибо tyv и gela-d.
Tags:
Hubs:
Total votes 80: ↑61 and ↓19 +42
Views 68K
Comments Comments 82