— Сначала вы его отрицаете, потом вы его ненавидите, а потом вы не можете без него жить.
из доклада Артема Курбатова «БЭМ: мастер-класс»
Методология БЭМ существует достаточно долго и принята на вооружение в Google, EPAM Systems, BBC, Альфа-Банке. При этом она все еще вызывает беспокойство у типичного разработчика и менеджера проектов среднего звена.
У некоторых смельчаков изучение БЭМ не ушло дальше ограничения возможностей CSS для получения более предсказуемых результатов. И хотя БЭМ давно вышел за пределы верстки, до сих пор на вопрос «Знаете ли вы БЭМ?» можно услышать: «Конечно, это про подчеркивания в классах».
Если ваше представление о БЭМ близко к этому, я отвечу вам словами работодателя при приеме на работу новоиспеченного выпускника: «Забудьте о том, что вы слышали о БЭМ ранее». Методология БЭМ настолько интересна, насколько большинству о ней ничего не известно. Чтобы понять всю прелесть БЭМ, необходимо иметь представление обо всех технологиях, библиотеках, фреймворках и инструментах, которые БЭМ предоставляет. Изучите их, оставайтесь инопланетянином, ребенком, который удивляется тому, с чем взрослые смирились.
Оглавление
- Используемые обозначения
- Используемые технологии
- Приложение Hello, World
- Приложение Social Services Search Robot
Что для меня БЭМ?
Наверняка вы слышали истории, как дерзкие вундеркинды, закрывшись в гараже, изобрели очередной стартап. Первые успехи позволили им привлечь инвестиции и выйти на рынок с потрясающим продуктом. Как это им удалось, как удалось выдержать пиковые нагрузки и не погрязнуть в вечном рефакторинге? Как надо, чтобы не как обычно?
БЭМ — это лайфхак для стартаперов. По сравнению со многими другими фреймворками и технологиями начало работы с БЭМ требует определенных усилий, но это окупается архитектурой, обеспечивающей устойчивую расширяемость. Во все технологии, фреймворки и библиотеки БЭМ изначально заложены принципы декларативного подхода. И именно это делает их такими непонятными на первый взгляд и любимыми в итоге.
Прочитав данную статью вы не постигните дзен БЭМ, но точно сможете ответить взрослым, что научились создавать полноценные динамические проекты по БЭМ, используя не только CSS. И если завтра ваше приложение постигнет участь в +10 К пользователей в сутки, будьте спокойны — у вас останется времени отметить это событие.
Сегодня мы расскажем о новом шаблонном репозитории для динамических проектов bem-express, который не только позволяет развернуть БЭМ-проект в один клик, но и делает автоматическую пересборку проекта и перезагрузку браузера. На его основе разработаем динамическое приложение и опишем процесс взаимодействия различных БЭМ-технологий. Мы сознательно не будем рассматривать вопросы верстки и клиентского JavaScript, чтобы избежать участи «подчеркивания в классах».
Что мы будем разрабатывать?
Поисковое приложение Social Services Search Robot (сокр. SSSR), которое по запросу покажет последние твиты и видео с YouTube.
Будем использовать:
- фреймворк i-bem.js;
- шаблонизатор bem-xjst;
- технологию для описания зависимостей DEPS;
- Express.js;
- YouTube Data API;
- Twitter Search API.
Для начала потребуется установить:
Важно! Пользователям операционной системы Windows необходимо дополнительно установить Git Bash.
Используемые обозначения
Чтобы статья получилась ярче, мы немного порисуем:
- — директория;
- — файл;
- — создать директорию;
- — создать файл;
- — отредактировать файл.
Используемые технологии
В БЭМ нет разделения технологий на главные и второстепенные. Есть набор, а выбор применения определяется индивидуально:
- BEMDECL — технология для описания деклараций в БЭМ.
- DEPS — технология для описания зависимостей в БЭМ.
- BEMTREE — шаблонизатор преобразующий данные в BEMJSON.
- BEMHTML — шаблонизатор преобразующий BEMJSON в HTML.
- i-bem.js — JavaScript-фреймворк для БЭМ.
Подробнее о BEMJSON-формате входных данных.
Давайте рассмотрим их подробнее.
BEMDECL
Определяет список БЭМ-сущностей для страницы.
Такой список в БЭМ называется декларацией. Задача декларации — определить, что и в каком порядке подключать в сборку.
Декларации описываются в файлах с расширением .bemdecl.js
.
Пример
exports.blocks = [
{ name: 'page' },
{ name: 'header' },
{ name: 'body' },
{ name: 'footer' }
];
Когда количество блоков переходит рубеж «легко запомнить», возникает проблема их перечисления в нужном порядке. Поэтому обычно декларируют какой-то определенный блок, который следует рассматривать как центральную «точку входа».
Все остальные БЭМ-сущности попадают в сборку по зависимостям.
DEPS
Определяет зависимости между БЭМ-сущностями, которые разнесены по файловой структуре проекта и не отражены в декларации.
Зависимости описываются в виде JavaScript-объекта в файлах с расширением .deps.js
.
Пример
/* page зависит от header */
({
block: 'page',
shouldDeps: [
{ block: 'header' }
]
})
Название технологии происходит от английского слова dependence и обозначает желание подключить в сборку какую-то БЭМ-сущность. Изучающие БЭМ порой забывают о декларативности технологий и недоумевают: почему для описанного в шаблоне блока, не собираются его стили и скрипты.
Помните: когда вы описываете шаблон (BEMHTML или BEMTREE) с каким-то блоком внутри (дочерний узел), вы просто добавляете новый HTML-элемент. Чтобы стили и скрипты этого блока попали в сборку, необходимо описать зависимость от него.
Например, для того чтобы добавить в сборку блоки header
, body
и footer
, необходимо описать зависимость от них:
/* page зависит от header, body, footer */
({
block: 'page',
shouldDeps: [
'header',
'body',
'footer'
]
})
Нижеследующая схема показывает логику сборки по зависимостям:
index(DECL) # Декларация блока page
|
└──> page(DEPS) # Зависимость блока page от header, body, footer
|
├──> header(DEPS)
| |
| └──> ...
|
├──> body(DEPS)
| |
| └──> ...
|
└──> footer(DEPS)
|
└──> ...
BEMTREE
Является частью шаблонизатора bem-xjst и преобразует данные в BEMJSON.
Шаблоны описываются в BEMJSON-формате в файлах с расширением .bemtree.js
.
Вход и выход шаблонизатора:
BEMHTML
Является частью шаблонизатора bem-xjst и преобразует BEMJSON-описание страницы в HTML.
Шаблоны описываются в файлах с расширением .bemhtml.js
.
Вход и выход шаблонизатора:
i-bem.js
Клиентский JavaScript-фреймворк для веб-разработки в рамках БЭМ-методологии.
JavaScript-код описывается в файлах с расширением .js
.
Позволяет:
- разрабатывать веб-интерфейс в терминах блоков, элементов, модификаторов;
- описывать логику работы блока в декларативном стиле — как набор состояний;
- легко интегрировать код JavaScript с BEMHTML-шаблонами и CSS;
- гибко переопределять поведение библиотечных блоков.
Приложение Hello, World
У программистов есть традиция: начинать программирование на новом языке или фреймворке с приложения Hello, World. Приложение обычно выводит слова «Hello, World» в выходной поток, демонстрируя тем самым, что оно запускается и выполняет операции ввода/вывода.
Давайте создадим его, а затем расширим до желаемого SSSR.
Нам потребуется локальная копия шаблонного репозитория bem-express. Ее можно сделать с помощью Git.
Примечание. Для пользователей OS X или Linux все команды выполняются в терминале. Пользователям Windows потребуется Git Bash. Убедитесь, что Git Bash запущен от имени администратора.
Шаблонный репозиторий
При решении задач по разработке динамических приложений в рамках БЭМ создан шаблонный репозиторий bem-express. Он содержит необходимый минимум конфигурационных файлов и решает целый класс задач, таких как сборка проекта, настройка линтеров, подключение библиотек и др.
В него по умолчанию подключены основные БЭМ-библиотеки:
Быстрый старт
Чтобы создать приложение Hello, World:
Склонируйте bem-express:
git clone https://github.com/bem/bem-express.git sssr-project
Примечание. В данном примере используется
bem-express
версии 2.00.
Перейдите в директорию проекта:
cd sssr-project
Удалите историю версионирования исходного репозитория:
rm -rf .git
Инициализируйте собственный Git-репозиторий:
git init
Установите зависимости:
npm install
Примечание. Не используйте права суперпользователя
root
при установке npm-зависимостей.
Соберите проект и запустите сервер:
npm run dev
Примечание. За сборку отвечает ENB.
При запуске приложения в терминале выводится сообщение о том, что сервер выполняется на порте 3000:
Server is listening on 3000
.
Примечание. Если порт
3000
используется другой программой, его можно переназначить. Например, на8000
:
Способ 1. Изменение значения при запуске приложения.
PORT=8000 npm run dev
Способ 2. Изменение значения по умолчанию в файлеserver/config.js
.
defaultPort: 8000,
На компьютере запустился:
- сервер — отвечает за обработку динамических данных;
- nodemon — следит за изменениями в файловой структуре и перезапускает сервер;
- chokidar — следит за изменениями в файлах директорий
*.blocks/
и перестраивает структуру проекта; - livereload — обновляет страницу в браузере.
Откройте браузер и введите адрес localhost:3000.
Должна открыться страница со следующим контентом:
Index page content footer content
Примечание. Если при запуске приложения в Windows, выводится уведомление от Брандмауэра:
- Отключите опцию Общественные сети (Public Network).
- Установите опцию Частные сети (Private Network).
- Разрешите доступ.
Откройте файл
server/index.js
и внесите следующие изменения (см. комментарии) в код начинающегося строкойapp.get('/', function(req, res)
:
/** * Функция обрабатывает все GET-запросы с главной страницы приложения * @function * @param {object} req - Запрос. * @param {object} res - Ответ. */ app.get('/', function(req, res) { var hello = 'Hello'; // Инициализируем переменную `hello` var world = 'World'; // Инициализируем переменную `world` render(req, res, { view: 'page-index', title: 'Main page', meta: { description: 'Page description', og: { url: 'https://site.com', siteName: 'Site name' } }, hello: hello, // Передаем переменную `hello` в `this.data.hello` world: world // Передаем переменную `world` в `this.data.world` }) });
Откройте файл
common.blocks/page-index/page-index.bemtree.js
и замените его содержимое на следующее:
block('page-index').content()(function() { // Получаем данные из глобального объекта `this` var data = this.data; // Возвращаем полученные данные: `data.hello: 'Hello'`, `data.world: 'World'` return data.hello + ', ' + data.world; });
После сохранения сервер автоматически перезапустится и контент страницы изменится на:
Hello, World footer content
Приложение Hello, World готово.
Не получилось?
Если при создании приложения возникли сложности, поищите решение на форуме. Если готового ответа не нашлось, задайте вопрос.
Приложение Social Services Search Robot
Собственно мы дошли до шага разработки приложения SSSR. Напомню, что по запросу приложение будет выводить последние твиты и видео с YouTube.
Сразу забежим в недалекое будущее — выглядеть приложение будет так:
Схема работы приложения
Шаг 1. Запрос
Пользователь отправляет запрос на сервер.
Шаг 2. Получение данных
Приложение обращается за данными к Twitter Search API и YouTube Data API в соответствии с полученным запросом.
Шаг 3. BEMTREE-шаблонизация
Приложение передает полученные данные BEMTREE-шаблонизатору, который преобразует данные в BEMJSON.
Шаг 4. BEMHTML-шаблонизация
Приложение передает BEMJSON BEMHTML-шаблонизатору, который преобразует BEMJSON в HTML.
Шаг 5. Отправка результата пользователю
Приложение возвращает результат (HTML-страницу) пользователю.
Используемые модули Node
Базовая реализация Node остается настолько простой, насколько это возможно. Вместо того, чтобы встраивать все возможные компоненты прямо в Node, разработчики предоставляют дополнительную функциональность в виде отдельных модулей (пакетов).
Система модулей Node построена по образцу системы CommonJS, механизма создания взаимодействующих модулей. Центральное место в системе занимает контракт, который должен выполняться разработчиками, чтобы их модули нормально взаимодействовали с другими.
Все пакеты установленные с помощью менеджера пакетов npm находятся в директории node_modules
.
Подключение модулей происходит при помощи команды require
. Если пакет установлен с использованием npm, указывать путь не нужно. Достаточно указать имя:
var express = require('express');
При подключении собственного локального модуля, необходимо указать к нему путь:
var someModule = require('./somefolder/somemodule');
Важной особенностью любого модуля является то, что он должен быть рассчитан на взаимодействие с Node. Для этого модуль нужно экспортировать с помощью module.exports
:
module.exports = {
// some module
};
Для работы приложения потребуются следующие модули:
- express — предоставляет большую часть функциональности, необходимой для построения веб-приложения;
- passport — предоставляет различные стратегии аутентификации для приложений Node.js;
- passport-youtube-v3 — предоставляет механизм аутентификации на Youtube посредством аккаунта Youtube и токенов OAuth 2.0;
- twitter — клиентская библиотека для работы с Twitter REST API;
- googleapis — клиентская библиотека для работы с Google REST API;
- moment — JavaScript библиотека для синтаксического анализа, валидации и форматирования дат.
Установить их можно одной командой:
npm install express passport passport-youtube-v3 twitter googleapis moment --save
Подготовка структуры проекта
Прежде чем начать писать код, немного изменим структуру взятого за основу приложения Hello, World.
Изменения для:
Изменения для статических файлов
Директория static
Создайте поддиректорию
images
.
- Перенесите фавиконку в поддиректорию
images
.
Директория common.blocks
Отредактируйте файл
root/root.bemtree.js
.
Измените:
favicon: '/favicon.ico',
На:
favicon: '/images/favicon.ico',
Директория server
Отредактируйте файл
index.js
.
Измените:
.use(favicon(path.join(staticFolder, 'favicon.ico')))
На:
.use(favicon(path.join(staticFolder, '/images/favicon.ico')))
Изменения для серверного кода
Директория server
Создайте поддиректории:
controllers
— контроллеры;helpers
— хелперы;middleware
— модули промежуточного звена.
Создайте пустые
JS
-файлы для будущих модулей:
app.js
— модуль монтирования промежуточных модулей (делает их доступными в приложении);auth.js
— модуль аутентификации на YouTube;routes.js
— модуль маршрутизации веб-запросов.
Добавьте следующий код в файл
app.js
.
Добавьте следующий код в файл
routes.js
.
Измените расширение файла
config
:
config.js
—>config.json
Отредактируйте файл
config.json
.
Измените:
module.exports = { staticFolder: 'static', defaultPort: 3000, cacheTTL: 30000, sessionSecret: 'REPLACE_ME_WITH_RANDOM_STRING' };
На:
{ "staticFolder": "static", "defaultPort": 3000, "cacheTTL": 30000, "sessionSecret": "REPLACE_ME_WITH_RANDOM_STRING" }
Измените весь текущий контент файла
index.js
на следующий.
Примечание. В
index.js
остается только функциональность, отвечающая за запуск приложения и прослушивание запросов на порте.
Директория controllers
Создайте пустой
JS
-файл:
index.js
— контроллер обработки запросов и рендеринга HTML.
- Добавьте следующий код в файл
index.js
.
Директория helpers
Создайте пустые
JS
-файлы:
index.js
— входная точка для хелперов;twitter.js
— модуль-хелпер для работы с Twitter Search API;youtube.js
— модуль-хелпер для работы с YouTube Data API.
Директория middleware
Создайте пустой
JS
-файл:
auth.js
— модуль проверки прохождения аутентификации на YouTube.
Получение OAuth-токенов
Сервисы Twitter и Google хранят различные данные пользователей — твиты, видео на Youtube, письма в Почте, фотографии и так далее. Чтобы обеспечить удобный доступ к этим данным из других приложений или сторонних сервисов, они используют открытый протокол авторизации OAuth 2.0.
Согласно протоколу, разработчик регистрирует приложение на OAuth-сервере и запрашивает доступ к определенным данным. Авторизованный пользователь разрешает или запрещает его.
Получение OAuth-токена для Twitter
Twitter предлагает приложениям возможность выдавать аутентифицированные запросы от имени самого приложения.
С чего начать?
- Изучите документацию.
- Зарегистрируйте приложение и получите ключи (Consumer Key, Consumer Secret).
- Установите Postman любым удобным для вас способом.
- Закодируйте строку
Consumer Key:Consumer Secret
методом Base64. - Получите OAuth-токен в обмен на код.
Используйте полученные токен и ключи в запросах к Twitter Search API.
Примечание. Postman поможет получить OAuth-токен с помощью POST-запроса в обмен на код, полученный методом Base64.
Как закодировать строку?
Чтобы закодировать строку методом Base64:
Сформируйте строку вида:
Consumer Key:Consumer Secret
.
Пример
xvz1evFS4wEEPTGEFPHBog:L8qq9PZyRg6ieKGEKhZolGC0vJWLw8iEJ88DRdyOg
Примечание. Получить ключи Consumer Key и Consumer Secret можно, перейдя на вкладку Keys and Access Tokens вашего приложения.
- Запустите терминал или Git Bash.
- Выполните команду
echo -n "xvz1evFS4wEEPTGEFPHBog:L8qq9PZyRg6ieKGEKhZolGC0vJWLw8iEJ88DRdyOg" | base64
. Скопируйте полученный код.
Пример
eHZ6MWV2RlM0d0VFUFRHRUZQSEdFS2hab2xHQzB2SldMdzhpRUo4OERSZHlPZw==
Примечание. Если возникли сложности, воспользуйтесь онлайн-ресурсом base64encode.org.
Как получить OAuth-токен в обмен на код?
Чтобы получить токен в обмен на код:
Запустите Postman.
Примечание. По умолчанию открывается вкладка, в которой необходимо сформировать POST-запрос к OAuth-серверу Twitter.
- Выберите тип запроса POST.
- Введите адрес сервера
https://api.twitter.com/oauth2/token
. - Перейдите на вкладку Headers.
Введите в поле Key заголовок
Authorization
со значением (поле Value)Basic <закодированная строка Consumer Key:Consumer Secret>
.
Пример
Authorization: Basic eHZ6MWV2RlM0d0VFUFRHRUZQSEdFS2hab2xHQzB2SldMdzhpRUo4OERSZHlPZw==
Примечание. Basic указывает на базовый метод авторизации.
Введите второй заголовок
Content-Type
со значениемapplication/x-www-form-urlencoded;charset=UTF-8
.
Пример
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
- Перейдите на вкладку Body.
- Выберите опцию
x-www-form-urlencoded
. - Введите в поле Key тело запроса
grant_type
со значениемclient_credentials
. Нажмите кнопку Send.
OAuth-сервер вернет токен в JSON-формате:
{ "token_type": "bearer", "access_token": "AAAAAAAAAAAAAAAAAAAAAA%2FAAAAAAAAAA%3DAAAAAAAAAAAAAAAAAA" }
Важно! Сохраните полученные токен и ключи (Consumer Key и Consumer Secret). Они потребуются для конфигурационного файла приложения.
Получение OAuth-токена для Google
Google предлагает приложениям возможность выдавать аутентифицированные запросы от имени самого приложения.
Примечание. За получение и обновление OAuth-токена с помощью POST-запроса в обмен на код авторизации отвечает модуль passport-youtube-v3.
С чего начать?
- Изучите документацию.
- Зарегистрируйте приложение и получите Client ID и Client Secret.
- Укажите callback URL (в нашем случае это
http://localhost:3000
) в учетной записи вашего приложения. - Используйте полученные Client ID и Client Secret в запросах к YouTube Data API.
Важно! Сохраните полученные ключи (Client ID и Client Secret). Они потребуются для конфигурационного файла приложения.
Конфигурация приложения
После того как все ключи и токены получены, их необходимо добавить в конфигурационный файл приложения:
Добавьте в файл
server/config.json
полеservices
.
"services": { "twitter": { "consumer_key": "", "consumer_secret": "", "bearer_token": "" }, "youtube": { "client_id": "", "client_secret": "", "redirect_url": "http://localhost:3000" } }
- Заполните одноименные поля полученными данными.
Скройте файл
server/config.json
от системы контроля версий Git, чтобы случайно не добавить личные ключи в репозиторий файлов.
# файл .gitignore server/config.json
Работа с Twitter Search API
Twitter Search API позволяет найти последние или популярные твиты, опубликованные на сайте Twitter.com за последние 7 дней.
Для успешного вызова API необходимо сделать следующие изменения:
Директория controllers
- Измените весь текущий контент файла
index.js
на следующий.
Директория helpers
Добавьте в файл
index.js
следующий контент:
module.exports = { twitter: require('./twitter') };
- Добавьте следующий код в файл
twitter.js
.
Работа с YouTube Data API
YouTube Data API позволяет найти видеоролики, опубликованные на сайте Youtube.com. По умолчанию в набор результата поиска включены следующие ресурсы: видео, каналы, списки воспроизведения.
Для успешного вызова API необходимо сделать следующие изменения:
Директория server
Добавьте следующий код в файл
auth.js
.
Отредактируйте файл
routes.js
.
Измените:
var router = require('express').Router(), controllers = require('./controllers'); router .get('/ping/', function(req, res) { res.send('ok'); }) .get('/', controllers.getContent); module.exports = router;
На:
var router = require('express').Router(), controllers = require('./controllers'), passportYouTube = require('./auth'), middleware = require('./middleware/auth'), isAuthenticated = middleware.isAuthenticated; router .get('/auth/youtube', passportYouTube.authenticate('youtube')) .get('/auth/youtube/callback', passportYouTube.authenticate('youtube', { failureRedirect: '/error', failureFlash: true }), (req, res) => { res.redirect('/'); }) .get('/', isAuthenticated, controllers.getContent); module.exports = router;
Директория controllers
- Измените весь текущий контент файла
index.js
на следующий.
Директория helpers
Добавьте в файл
index.js
следующий контент (см. комментарий):
module.exports = { twitter: require('./twitter'), youtube: require('./youtube') // Подключаем модуль `youtube.js` };
- Добавьте следующий код в файл
youtube.js
.
Директория middleware
Добавьте в файл
auth.js
следующий контент:
module.exports = { isAuthenticated: function(req, res, next) { if (req.isAuthenticated()) return next(); return res.redirect('/auth/youtube'); } };
Верстка
Мы сознательно не рассматривали вопросы верстки и клиентского JavaScript. Это привело бы к большему объему, а, значит, и к меньшей практической ценности данной статьи.
Процесс верстки сведен к следующим шагам:
- Удалите все блоки из директории
common.blocks
. - Склонируйте следующие блоки в директорию
common.blocks
. - Добавьте logo.svg в директорию
static/images
. - Перезапустите сервер:
npm run dev
.
Приложение Social Services Search Robot готово.
Не получилось?
Если при создании приложения возникли сложности, поищите решение на форуме. Если готового ответа не нашлось, задайте вопрос.