Форум

Методологія

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

Платформа

Спільнота

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

Создаем свой проект на БЭМ

Эта статья рассказывает о том, как быстро создать свой проект с использованием БЭМ-платформы. Для изучения материала, представленного в статье, необходимо знание JavaScript.

Мы шаг за шагом создадим страничку каталога товаров, пользуясь принципами БЭМ в CSS, возможностью писать декларативный JavaScript с использованием фреймворка i-bem.js и шаблонизатора BEMHTML.

Каталог товаров

Настройка окружения

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

Обратите внимание на актуальность версий инструментов и библиотек:

Для начала работы с любым БЭМ-проектом вам необходимо установить:

  • Node.js 4+;
  • Git Bash — для пользователей операционной системы Windows.

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

Самый оптимальный путь создания собственного проекта — использование шаблонного репозитория project-stub. Он содержит необходимый минимум конфигурационных файлов и папок и позволяет быстро развернуть проект.

Нам понадобится локальная копия project-stub. Её можно сделать любым удобным для вас способом. Мы будем использовать Git.

Важно Пользователям Windows необходимо выполнять все команды в Git Bash. Убедитесь, что Git Bash запущен от имени администратора.

git clone https://github.com/bem/project-stub.git --depth 1 test-project

Переходим в директорию нашего проекта:

cd test-project

Удаляем всю историю версионирования исходного репозитория:

rm -rf .git

Инициализируем собственный репозиторий в директории проекта:

git init

Устанавливаем зависимости:

Важно Не используйте права суперпользователя (root) при установке npm-зависимостей.

npm install

Собираем проект с помощью ENB:

npm run build

Конфигурация процесса сборки хранится в файле .enb/make.js. На её основе ENB подключает все технологии, которые составляют реализацию блоков: шаблоны, зависимости, CSS-правила и JavaScript-функциональность.

Для удобства разработки запускаем сервер:

npm start

В результате вы увидите следующее сообщение:

Server started at 0.0.0.0:8080

На вашем компьютере запустился ENB-сервер — инструмент для разработки, который при обновлении страницы в браузере будет автоматически пересобирать только ту часть проекта, которую затронули ваши изменения. Результат доступен по ссылке: http://localhost:8080/desktop.bundles/index/index.html.

Проблема?

Если порт 8080 уже используется другой программой, его можно переназначить с помощью аргумента -p.

npm start -- -p 8081

Кратко о структуре проекта

HTML-разметка web-страницы и применяемые к ней CSS-правила генерируются из её описания в BEMJSON-файле page-name.bemjson.js. В терминах БЭМ-методологии будем называть BEMJSON-описание страницы декларацией.

BEMJSON-декларация — это структура страницы, описанная в терминах блоков, элементов и модификаторов. Для создания HTML-представления web-страницы в работу включается шаблонизатор BEMHTML, который преобразует входные данные из BEMJSON-файла в HTML. На основе BEMJSON-файла, который описывает страницу в виде БЭМ-дерева, собираются зависимости, на основании которых собираются бандлы технологий.

Блоки — строительный материал для страниц. Их можно заимствовать из библиотек или создавать самостоятельно.

Блок может быть представлен с помощью таких технологий как css/styl, js, bemhtml.js, deps.js и т.д., которые в БЭМ-методологии называются файлами технологий реализации блока. Наборы реализаций блоков хранятся в одной директории. В БЭМ-терминах она называется уровнем переопределения.

Структура проекта предполагает, что все созданные и переопределенные блоки размещаются в директории desktop.blocks. А директория desktop.bundles содержит блоки страниц проекта и все блоки, указанные в их BEMJSON-декларациях.

Шаг за шагом

В этом разделе кратко описывается последовательность действий для создания страницы каталога товаров.

Для удобства определим, что страница будет состоять из шапки и тела — основной части.

  1. Разместим на странице шапку. В терминах БЭМ-методологии она будет представлена блоком head. Для этого задекларируем блок в BEMJSON-файле и создадим его в файловой структуре, напишем первые CSS-правила, обеспечивающие раскладку.

  2. Используем готовые блоки библиотеки для создания формы поиска и логотипа в шапке. Представим логотип блоком logo и сделаем картинку ссылкой на сайт bem.info. Переопределим существующие блоки библиотек с помощью технологий CSS и BEMHTML.

  3. В теле страницы разместим список товаров с помощью BEMHTML-шаблонов. Представим список блоком goods в BEMJSON-декларации. В BEMHTML-шаблоне зададим разметку элементам блока и откорректируем внешний вид CSS-правилами.

  4. Укажем зависимости блоков в файле deps.js, чтобы шаблоны, JavaScript-реализация и CSS-правила применились при сборке к нужным нам блокам.

  5. Подключим сторонние библиотеки в проект и расширим их функциональность с помощью доопределения JavaScript-функциональности.

  6. Рассмотрим варианты микса блоков и элементов.

  7. И, напоследок, создадим новую страницу и запустим полную сборку проекта.

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

Сейчас в проекте есть одна страница index.html, которую можно открыть в браузере: http://localhost:8080/desktop.bundles/index/index.html.

Изначально index.html содержит примеры блоков, которые наглядно демонстрируют разнообразие библиотеки bem-components, подключенной к project-stub.

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

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

Для начала разместим на странице шапку, добавив декларацию блока head в BEMJSON-файл страницы.

{ block: 'head' }
module.exports = {
    block: 'page',
    title: 'Title of the page',
    favicon: '/favicon.ico',
    head: [
        { elem: 'meta', attrs: { name: 'description', content: '' }},
        { elem: 'css', url: 'index.min.css' }
    ],
    scripts: [{ elem: 'js', url: 'index.min.js' }],
    content: [
        { block : 'head' }
    ]
};

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

    <!DOCTYPE html>
    <html class="ua_js_yes">
        <head>...</head>

        <body class="page">
            <div class="head"></div>

            <script src="index.min.js"></script>
        </body>
    </html>

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

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

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

Пример кода index.bemjson.js.

В HTML-представлении страницы появится необходимая разметка (вы сможете увидеть её, обновив страницу).

<!DOCTYPE html>
<html class="ua_js_yes">
    <head>...</head>

    <body class="page">
        <div class="head">
            <div class="layout">
                <div class="layout__left">left here</div>
                <div class="layout__right">right here</div>
            </div>
        </div>

        <script src="index.min.js"></script>
    </body>
</html>

Теперь для блока layout необходимо прописать CSS-правила. В БЭМ-терминах будем называть это реализацией блока в технологии CSS.

Важно В project-stub по умолчанию используется PostCSS.

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

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

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

где

  • -l directoryName — указывает на уровень переопределения;
  • -b blockName — определяет имя директории блока, для которого создается файл технологии. Если директории с таким именем ещё не существует, создает её;
  • -T technologyName — создает указанный файл технологии реализации блока.

Таким образом, команда создаст директорию для блока layout на уровне переопределения desktop.blocks и файл desktop.blocks/layout/layout.css для него, в котором уже есть селектор, совпадающий с именем блока.

Альтернативный, более лаконичный способ создать этот же файл:

bem create desktop.blocks/layout.css

Правило нужно дополнить соответственно внешнему виду блока. Сейчас можно просто скопировать пример.

Блоки можно создавать и вручную: создадим директорию desktop.blocks/layout и в ней разместим необходимые нам файлы технологий реализации блока.

Мы хотим, чтобы блок logo состоял из картинки и слогана. Для этого задекларируем его в блоке head файла desktop.bundles/index/index.bemjson.js.

Можете использовать картинку из нашего примера или указать свою.

{
    elem: 'right',
    content: {
        block: 'logo',
        content: [{
            block: 'image',
            url: '//varya.me/online-shop-dummy/desktop.blocks/b-logo/b-logo.png'
        },
        {
            elem: 'slogan',
            content: 'A new way of thinking'
        }]
    }
}

Пример кода index.bemjson.js.

Блок logo

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

Блоки поисковой формы input и button создавать самостоятельно не нужно. Они уже реализованы в библиотеке bem-components, которая подключается в project-stub по умолчанию. Достаточно просто задекларировать блоки на странице desktop.bundles/index/index.bemjson.js.

{
    elem: 'left',
    content: [
        {
            block: 'input',
            name: 'text',
            val: 'Find'
        },
        {
            block: 'button',
            mods: { type: 'submit' },
            content: 'Search'
        }
    ]
}

Пример кода index.bemjson.js.

Добавим обработку пользовательского запроса Яндексом:

{
    elem: 'left',
    content: {
        tag: 'form',
        attrs: { action: 'https://yandex.ru/yandsearch' },
        content: [
            {
                block: 'input',
                name: 'text',
                val: 'Find'
            },
            {
                block: 'button',
                mods: { type: 'submit' },
                content: 'Search'
            }
        ]
    }
}

Пример кода index.bemjson.js.

Форма поиска

Используя блок link из той же библиотеки, мы сделаем картинку и слоган ссылкой на сайт bem.info:

{
    elem: 'right',
    content: {
        block: 'logo',
        content: [
            {
                block: 'link',
                url: 'https://ru.bem.info',
                content: [
                    {
                        block: 'image',
                        url: 'http://varya.me/online-shop-dummy/desktop.blocks/b-logo/b-logo.png'
                    },
                    {
                        elem: 'slogan',
                        content: 'A new way of thinking'
                    }
                ]
            }
        ]
    }
}

Пример кода index.bemjson.js.

Модификация блоков библиотек

Модификация в CSS

Блоки input и button можно модифицировать, написав необходимые CSS-правила для каждого из них.

CSS мы поместим в блок input на уровне переопределения desktop.blocks:

bem create -l desktop.blocks -b input -T css

Пример кода input.css.

То же самое для блока button:

bem create -l desktop.blocks -b button -T css

Пример кода button.css.

Добавим необходимые CSS-правила для блока link.

bem create -l desktop.blocks -b link -T css

Пример кода link.css.

Форма поиска

Модификация BEMHTML

Чтобы отцентрировать весь материал на странице, нужно создать дополнительный HTML-элемент — контейнер. Для этого необязательно создавать специальный блок. Проще и правильнее модифицировать шаблон для блока page на уровне переопределения desktop.blocks, который генерирует выходной HTML для всей страницы.

В качестве шаблонизатора используем BEMHTML.

bem create -l desktop.blocks -b page -T bemhtml.js

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

В созданном файле desktop.blocks/page/page.bemhtml.js необходимо написать код, оборачивающий контент блока в дополнительный контейнер.

block('page')(
    content()(function() {
        return {
            elem: 'inner',
            content: applyNext()
        };
    })
);

Пример кода page.bemhtml.js.

<!DOCTYPE html>
<html class="ua_js_yes">
    <head>...</head>

    <body class="page">
        <div class="page__inner">
            <div class="head">
                <div class="layout">...</div>
            </div>

            <script src="index.min.js"></script>
        </div>
    </body>
</html>

Для новой разметки блока page создадим свои CSS-правила:

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

Контент для файла desktop.blocks/page/page.css можно скопировать из примера.

Чтобы шапка была заметна на странице, поместим её в рамку. Для этого создадим CSS-правила для блока head.

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

Контент для файла desktop.blocks/head/head.css можно скопировать из примера.

Блок head с рамкой

BEMHTML-шаблоны

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

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

Пример кода index.bemjson.js.

Чтобы эти данные превратились в нужную разметку, блок должен быть реализован в технологии BEMHTML. Для корректировки внешнего вида применим CSS-правила. Воспользуемся командой bem create, чтобы создать блок сразу в двух технологиях:

bem create -l desktop.blocks -b goods -T bemhtml.js -T css

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

block('goods')(
    tag()('ul'),

    // здесь опущена часть кода, см. полный вариант по ссылке ниже

    elem('item')(
        tag()('li')
    ),

    elem('title')(
       tag()('h3')
    ),

    elem('image')(
       tag()('img'),

        attrs()(function() {
            return { src: this.ctx.url };
        })
    ),

    elem('price')(
       tag()('span')
    )
);

Код пример goods.bemhtml.js.

<!DOCTYPE html>
<html class="ua_js_yes">
    <head>...</head>

    <body class="page">
        <div class="page__inner">
            <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="http://mdata.yandex.net/
                         i?path=b1004232748_img_id8368283111385023010.jpg"/>
                    <span class="goods__price">259</span>
                </li>
                <li class="goods__item">...</li>
                <li class="goods__item">...</li>
            </ul>

            <script src="index.min.js"></script>
        </div>
    </body>
</html>

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

Чтобы избежать вложенных селекторов при оформлении этой ссылки стилями, пометим её как элемент блока goods.

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

Пример кода goods.bemhtml.js.

<ul class="goods">
    <li class="goods__item">
        <h3 class="goods__title">Apple iPhone 4S 32Gb</h3>

        <img class="goods__image" src="http://mdata.yandex.net/
             i?path=b1004232748_img_id8368283111385023010.jpg"/>

        <span class="goods__price">
            <a class="link goods__link" href="/">259</a>
        </span>
    </li>
    //...
    <li class="goods__item">...</li>
    <li class="goods__item">...</li>
</ul>

Нужно визуально выделить на странице новые товары. Для этого добавим проверку модификатора new в шаблон: пример.

CSS-правила для блока можно скопировать отсюда.

Создавать блок отдельно в технологии CSS не нужно, потому что мы уже создали его командой bem create.

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

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

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

Делается это с помощью представления блока в технологии deps.js.

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

Так как блок link объявляется не в BEMJSON-декларации, а в шаблоне BEMHTML, необходимо добавить блок link в зависимости блока goods.

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

({
    shouldDeps: [
        'link'
    ]
})

Пример кода goods.deps.js.

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

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

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

Установим зависимую библиотеку. Сделаем это с помощью следующей команды:

npm install tadatuta/j --save-dev

Необходимо указать, что данная библиотека должна использоваться при сборке страниц. Это делается в файле .enb/make.js:

levels = [
    { path: 'node_modules/bem-core/common.blocks', check: false },
    { path: 'node_modules/bem-core/desktop.blocks', check: false },
    { path: 'node_modules/bem-components/common.blocks', check: false },
    { path: 'node_modules/bem-components/desktop.blocks', check: false },
    { path: 'node_modules/bem-components/design/common.blocks', check: false },
    { path: 'node_modules/bem-components/design/desktop.blocks', check: false },
    { path: 'node_modules/j/blocks', check: false },
    'common.blocks',
    'desktop.blocks'
];

Пример кода .enb/make.js.

При изменении конфигурации проекта необходимо перезапустить сервер. Текущий процесс придётся прервать (Ctrl + C) и снова ввести команду запуска сервера.

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

Теперь блок box можно использовать. Мы применим его к шапке страницы, чтобы добавить белый фон с тенью. Для этого смиксуем блок head с блоком box, используя метод mix в BEMJSON-декларации страницы.

Один из способов смешения — описать метод mix во входных данных (BEMJSON).

В данном случае нужно смешать блок head с блоком box:

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

Пример кода index.bemjson.js.

<!DOCTYPE html>
<html class="ua_js_yes">
    <head>...</head>

    <body class="page">
        <div class="page__inner">
            <div class="head box">
                <div class="layout">...</div>
            </div>

            <ul class="goods">...</ul>

            <script src="index.min.js"></script>
        </div>
    </body>
</html>

Микс блоков

Миксовать можно не только блоки, но и элементы с блоками. И не только в BEMJSON-декларации страницы, но также в шаблонах реализации конкретного блока.

Сделаем, чтобы каждый товар из списка имел такое же оформление, как и шапка страницы. Для этого в шаблоне блока goods смиксуем каждый элемент item с блоком box из только что подключенной библиотеки.

    // ...
    elem: 'item',
    elemMods: { new: item.new && 'yes' },
    mix: [{ block: 'box' }],
    // ...

Пример кода goods.bemhtml.js.

<!DOCTYPE html>
<html class="i-ua_js_yes">
    <head>...</head>

    <body class="page">
        <div class="page__inner">
            <div class="head box">...</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>
                //...
            </ul>

            <script src="index.min.js"></script>
        </div>
    </body>
</html>

Запишем блок box в зависимости блока goods.

({
    shouldDeps: [
        'link',
        'box'
    ]
})

Пример кода goods.deps.js.

Список товаров в блоке box

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

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

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

Для использования этой функциональности в шапке необходимо изменить описание блока head, указав, что блок box имеет JavaScript-реализацию.

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

Пример кода index.bemjson.js.

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

block: 'head',
mix: [{ block: 'box', js: true }],
content: [
    {
        block: 'layout',
        //...
    },
    {
        block: 'box',
        elem: 'switcher'
    }
]

Пример кода index.bemjson.js.

Теперь в блоке head есть стрелочка, умеющая сворачивать и разворачивать его.

Стрелочка

Модификация JavaScript

Расширим предлагаемую библиотекой JavaScript-функциональность блока box. Сделаем так, чтобы он сворачивался не только по вертикали, но и по горизонтали. При этом вносить изменения в чужую библиотеку мы не можем. Но благодаря тому, что JavaScript блока написан с использованием декларативного фреймворка i-bem.js, есть возможность изменить поведение блока.

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

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

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

modules.define('box', ['i-bem-dom'], function(provide, bemDom) {

  provide(bemDom.declBlock(this.name, {
      onSetMod : {
          'closed': {
              'yes': function() {
                  // some functionality here
              },

              '': function() {
                  // some functionality here
              }
          }
      }
  }));

});

Например, можно добавить анимацию, как показано в примере ниже.

Пример кода box.js.

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

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

Создадим страницу contact:

bem create -l desktop.bundles -b contact -T bemjson.js

Новую страницу можно посмотреть по адресу http://localhost:8080/desktop.bundles/contact/contact.html.

Сервер соберёт её HTML-представление, JS- и CSS-файлы в момент первого открытия в браузере.

Полная сборка проекта

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

Для сборки проекта целиком можно воспользоваться командой:

npm run build

Подведем итоги

Первый практический опыт использования БЭМ-методологии показал нам только вершину айсберга всех возможностей работы с БЭМ-проектом.

Итак, в ходе изучения статьи мы узнали, как быстро и легко начать работу с собственным проектом, развёрнутым на базе шаблонного репозитория project-stub.

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

Начали знакомство с БЭМ-инструментами, в частности, с ENB. Затронули шаблонизатор BEMHTML и лишь упомянули о возможности использования декларативного фреймворка i-bem.js.

Якщо ви помітили помилку, або хочете доповнити статтю, ви завжди можете або написати нам про це на Гітхабі, або поправити статтю з допомогою prose.io.