Сегодня мы поделимся историей — расскажем о том, как избавились от одной очень назойливой боли. До недавнего времени эта боль преследовала нас постоянно: из месяца в месяц, из проекта в проект. Если и вам приходилось разрабатывать лендинги в составе ecom-проекта с отлаженными системами CI/CD c ревью кода и тестированием каждой итерации, возможно, вы тоже задумывались над этим. Какая боль, и, главное, как же мы с ней справились? Сейчас расскажем.
С чего все началось
У нас в AGIMA, процессы настроены так, чтобы не допускать багов на production и проводить тестирование на каждом этапе. При этом сложные технологические решения — обычное дело в нашей работе. И то, что они выверяются и тестируются на каждом шагу — это обычно хорошо и правильно. Но в какой-то момент у нас появилось ощущение, что при создании лендингов такая система — ловушка, из которой нужно срочно искать выход...
Оговорюсь, разработка ведется по waterfall-схеме: макет дизайна/верстка/backend-интеграция/тестирование/deploy, а изменения, внесенные в базу данных, требуют написания миграции для переноса по площадкам для тестирования и релиза. «Ну и что здесь такого?», скажете вы.
Однако... При создании каждого лендинга на нас обрушивался целый поток доработок, правок последней минуты и редактирования текстов. Очень часто правки нужно было сохранять в базе, а значит, фиксация изменений = написание миграций и ревью кода и перенос по стейдж-площадкам в режиме цейтнота это было похоже на кошмарный хоровод, в котором «кружилась» вся команда, т.к. любая правка — это ревью, деплой и тестирование.
Нужно было успевать делать такие лендинги вместе со всеми правками в ритме заказчика. Нам виделся лишь один путь к «спасению». Им мог бы стать конструктор, в котором заказчик создает лендинги силами контент-менеджеров в собственном ритме. А в идеале — еще и библиотека, в которой было бы сохранено большинство существующих блоков для повторного использования в новых задачах. Здесь я улыбаюсь, представляя лендинг, созданный по всем style-guides, который контент-менеджер собрал сам без помощи отдела разработки, а еще и самостоятельно внес все правки как текстовые, так и стилевые. И этот DIY — сразу на production с возможностью preview! Словом, мы увидели цель, осталось оценить препятствия.
А теперь время познакомить вас с темой статьи. Эта статья — о практике использования модуля конструктора сайтов на платформах 1С-Битрикс. Называется этот конструктор — Сайты24.
Дисклеймер: сравнение Сайты24 с существующими конструкторами веб-страниц, как и реклама отдельного решения не имеют ничего общего с целями данной статьи. Это просто рассказ про опыт конструктора и заметки по ходу.
Проект работает на платформе 1С-Битрикс «Управление сайтом». Бизнес-заказчику понравилась идея использовать уже встроенный функционал конструктора.
Все знают конструкторы сайтов Tilda, Wix и т.д., их обширные возможности и сильные стороны, но здесь пойдет речь не о конструкторах, вообще, а о включении такого инструмента в жизненный цикл приложения и схему его деплоя.
Чего мы ожидали от конструктора:
- Генерация валидного html-кода разметки страниц — без ограничений и критичных недостатков. Изменение структуры разметки, добавление новых тегов при редактировании визуального отображения блоков.
- Поддержка существующего шаблона сайта с общими блоками навигации.
- Простая интеграция и поддержка.
- Интерфейсы редактирования.
- Версионирование изменений.
- Адаптивность.
- Извечные проблемы: Качество, Скорость разработки и Надежность. А можно всё вместе?
Что из этого было «в наличии»:
- Интересная система шаблонов блоков для страниц на стилях bootstrap 4. Дает возможность генерировать валидный код, т.к. разметка фактически выстроена в шаблоне. Изменения проходят через применение набора классов/атрибутов для ключевых элементов разметки блока или через клонирование структурных элементов, без генерации избыточного кода. Привязка элементов блока описывается в PHP CONFIG файлах — манифестах, в которых элементу страницы можно назначить тип данных для поддержки визуальным редактором.
- Интеграция в уже задействованную платформу — очень весомый плюс. Берем существующий компонент. Просто создаем из него блок и даем возможность указать в манифесте только требуемые параметры при добавлении. Получаем возможность создавать шаблоны страниц сайта «из коробки».
- На рисунке блоки 1 и 2 могут быть отдельными «лендингами» из блоков меню (к примеру) в структуре и повторяться на группе связанных между собой страниц. Но в нашем случае это мешало, т.к. делало лендинги отдельными подпроектами, которые нужно было отдельно поддерживать. Мы нашли возможность встроить вывод лендингов на базе основного шаблона сайта, тем самым решив проблему со сквозными Header и Footer для всех страниц в общем случае.
Проблема: для лендингов в базовой схеме по документации есть отдельный шаблон /bitrix/templates/landing24. В нем идет подключение необходимых стилей для вывода лендингов в режиме демонстрации и в режиме редактора. Т.е. есть набор скриптов стилей, которые требуется совместить со стилями и скриптами работающего проекта.
Мы создали универсальный шаблон для всего сайта и логический блок для зависимостей, задействованных в работе модуля лендингов, который подключаем при необходимости.
use Bitrix\Main\Localization\Loc; use App\Helper\ABTestHelper; use App\Helper\DigitalDataManager; use App\Helper\Functions; use App\Helper\MetaDataHelper; use Bitrix\Main\Page\Asset;global $APPLICATION;$landingClass = (IS_ADMIN_PAGE_LANDING_VIEW || IN_LANDING_DIR) ? 'landing24-content' : ''; if (IS_ADMIN_PAGE_LANDING_VIEW && $APPLICATION->GetShowIncludeAreas()) { // Отключаем режим правки при переходе в конструктор unset($_SESSION['SESS_INCLUDE_AREAS']); } if (IS_ADMIN_PAGE_LANDING_VIEW && $_GET['landing_mode'] != 'edit') { require_once 'landing.header.php'; require_once 'landing.varjs.php'; return; } elseif (IS_ADMIN_PAGE_LANDING_VIEW || IN_LANDING_DIR) { // Подключаем кастомные ассеты, необходимые для работы блоков конструктора require_once 'landing.assets.php'; require_once 'landing.varjs.php'; if (\Bitrix\Main\Loader::includeModule('landing')) { CUtil::InitJSCore(['popup']); CUtil::InitJSCore(['landing_core_iqos']); } }
Были сомнения, что возможны конфликты, и наша идея не «взлетит», однако все заработало нормально.
Результат — возможность вести обновление общего шаблона сразу для всех частей системы, не тратя время на синхронизацию на каждой доработке. И это упростило нам жизнь. В идеологии создателей модуля заложен принцип разделения страниц из конструктора и страниц классического шаблона. В данном случае проект должен оставаться целостным. А в контексте нашего решения разница между этими типами страниц должна была присутствовать: нужна возможность добавления товаров в корзину со страницы-лендинга.
- Требуется быстро добавлять новые блоки.
Такая возможность заложена в архитектуре. Всё, на самом деле, очень просто и интересно. Нужно всего 2 файла: блок и манифест к нему. Что в них?
Блок — это блок html-разметки, в которую не нужно добавлять специальные элементы «для конструктора». Связь ключевых элементов (нод) с механикой конструктора идет через указание класса, по которому мы можем указывать, как настраивать тот или иной элемент. И это работает и для указания повторяемых элементов-списков.
Общие настройки для всех блоков
Проблема: все блоки в базовом понимании изолированы. В работе потребовалось, чтобы у всех блоков была общая часть. К примеру, возможность добавить дисклеймер, несколько кнопок «call to action» с различными стилями, кнопку добавления в корзину и так далее. Т.е. своеобразное наследование.
Эти общие вещи, конечно, можно прописывать для каждого нового блока, но так как блоков множество, поддержка обновлений такой общей части станет новой проблемой. К счастью, в итоге нашелся способ расширять файл манифеста для блоков с общей частью.
$snippets = \App\Helper\Landing24SnippetHelper::getDescription(['background', 'video']);
return array_merge_recursive($snippets, [ 'block' => [ 'name' => 'Блок с дисклеймером ', 'section' => ['iqos'], 'namespace' => 'bitrix' ], 'nodes' => [ '.main-block-tertiary__desc' => [ 'name' => 'Текст блока', 'type' => 'text', ], '.disclaimer' => [ 'name' => 'Текст дисклеймера', 'type' => 'text', ],
Фрагмент файла манифеста, расширенного типовыми нодами
/** * Class Landing24SnippetHelper * * Класс реализует сниппеты для хранения и быстрого получения части описания манифеста пользовательских * блоков для конструктора лендингов. * * Предоставляет 2 статических метода для вызова в description.php и block.php блока. * ** Landing24SnippetHelper::getDescription($type, $params) - для вызова в description.php * Landing24SnippetHelper::getBlock($type, $params) - для вызова в block.php ** * Настройка и реализация новых сниппетов производится через редактирования пресета сниппетов класса Landing24SnippetHelper::SNIPPETS * * Пример использования в description.php * ** $snippets = \App\Helper\Landing24SnippetHelper::getDescription(['btns', 'background', 'foo' => []]); // list of the snippets * $snippets = \App\Helper\Landing24SnippetHelper::getDescription('btns'); // for a single snippet * * return array_merge_recursive($snippets, array( * 'block' => array( * 'name' => 'Новый или существующий блок', * 'section' => array('iqos'), * ), * ... * )); ** * const SNIPPETS = [ 'btns' => [ 'component' => 'agima:landing24.btns.tpl', 'description' => 'btnsDescription', 'block' => self::FUNC_COMPONENT_INCLUDE_WARNING, 'props' => [] ], 'background' => [ 'component' => false, 'description' => 'backgroundDescription', 'block' => false, 'props' => [] ], ... /** * Инициализирует описание одного сниппета * * @param $type * * @param array $params */ protected function __construct($type, $params = []) { $manifestSettings = self::SNIPPETS[$type]; $this->typeExists = isset($manifestSettings); $this->type = $type; $this->params = $params; $this->componentName = $manifestSettings['component']; $this->descFuncName = $manifestSettings['description']; $this->blockFuncName = $manifestSettings['block']; $this->props = $manifestSettings['props']; }
Фрагмент класса-хэлпера с описанием сниппетов
/** * Описание манифеста для редактирования бэкграунда блока. * [background] сниппет. * * @return array */ protected function backgroundDescription() { return [ 'style' => array( 'block' => array( 'type' => array(['background-color']), ), ), 'attrs' => [ '.js-landing24-bg' => [ 'name' => 'Управление бэкграундом', 'attrs' => [ [ "type" => "text", "name" => "Цвет фона в формате hex", "placeholder" => "#XXXXXX", "value" => "", "attribute" => "data-background-color", "textOnly" => true ] ] ] ], 'assets' => [ 'js' => [ '/local/templates/.default/js/landing24/bg.js', ] ] ]; }
Метод описания изменений сниппета
Хелпер для расширения возможностей можно дополнять, включая в него компоненты системы с различными визуальными шаблонами. Гибко и просто.
- Интерфейсы редактирования.
Интерфейсы в целом удобные, но не всё идеально
Проблема: Недостаток типовых блоков для адаптива.
Тип нод image не дает возможности ввести комплект картинок для размеров различных устройств. Тип нод picture не предусмотрен. Это было бы неплохо. Думаем доработать, но необходимость в этом не столь острая.
- Интерфейсы редактирования.
Версионирование. Его нет в принципе, если не считать функционал отмены правок на шаг вперед/шаг назад в контексте редактирования. Тоже неплохо, но, как оказалось, недостаточно для нас.
Проблема: Конструктор Сайты 24 — простой инструмент для быстрого создания страниц из типовых блоков. В случае если таких блоков много, нужно потрудиться, чтобы внести настройки этих блоков для правильного наполнения отображения. Допустим, мы всё внесли на тестовой площадке, а что дальше? Как повторить это на production? Как зафиксировать и перенести наработки?
«А релизы, перенос лендингов между stages?» — еще один интересный вопрос. Всё — на собственное усмотрение.
Собирать лендинг недолго, но делать одну работу несколько раз очень напрягает. Что можно сделать? Согласно документации есть возможность экспортировать сайт с лендингами целиком, что, конечно, крайне неудобно.
dev.1c-bitrix.ru/api_d7/bitrix/landing/site/methods/fullexport.php
Но из этого можно извлечь некоторую пользу: получить всю структуру по отдельному сайту с описанием всех реквизитов, включая настройки SEO и прочее.
На основе этого был построен метод экспорта избранных страниц (лендингов).
public static function getLandingsExportData($siteId, $landingsIdList = [], $blocksList = []) { if (empty($siteId)) { return []; } $jsonFile = 'export.json'; $tmpDir = $_SERVER['DOCUMENT_ROOT'] . '/upload/landing_export/' . date('dmY_His'); $blocksDir = $tmpDir . '/blocks'; self::checkOrCreateDir($tmpDir); self::checkOrCreateDir($blocksDir); $export = \Bitrix\Landing\Site::fullExport( $siteId, ['edit_mode' => 'N'] ); if (empty($export)) { return []; } $export['description'] = 'Exported site'; $items = $export['items']; if (!empty($landingsIdList)) { foreach ($export['items'] as $landKey => $landData) { if (in_array($landData['old_id'], $landingsIdList)) { $landKeysList[] = $landKey; } } //Пример $landKey = 'a88yd0mbjk/black_friday' $export['items'] = self::array_slice_assoc($export['items'], $landKeysList); } $files = []; foreach ($export['items'] as &$land) { $blocks = self::getLandingBlocksList($land['old_id']); $landFiles = File::getFilesFromLanding($land['old_id']); $files = array_merge($files, $landFiles); self::exportLandingFileLinks($land); foreach ($land['items'] as $anchor => &$block) { echo $block['code'] . " "; echo $block['old_id']; $blockFiles = File::getFilesFromBlock($block['old_id']); $export['block_files'][$block['old_id']] = $blockFiles; $files = array_merge($files, $blockFiles); \Bitrix\Main\IO\File::putFileContents( $blocksDir . '/' . $anchor . '.html', self::exportBlockFileLinks( $blocks[$land['old_id'] . '_' . $block['old_id']]['CONTENT'], $blockFiles ) ); } } if (!empty($files)) { foreach ($files as $fid) { $file = File::getFileArray($fid); if (empty($file)) { continue; } $arPackFiles[] = $file['SRC']; self::checkOrCreateDir($tmpDir . "/upload/{$fid}"); IOFile::putFileContents( $tmpDir . "/upload/{$fid}/{$file['FILE_NAME']}", IOFile::getFileContents($_SERVER['DOCUMENT_ROOT'] . $file['SRC']) ); $export['files'][$fid] = $file['FILE_NAME']; } } \Bitrix\Main\IO\File::putFileContents( $tmpDir . '/' . $jsonFile, \CUtil::phpToJSObject($export) ); return $items; }
Идея: сбор содержимого блоков в отдельные html файлы, а метаданных по всем импортируемым страницам — в общий файл сборки: export.json.
В такой реализации есть возможность встраивать файлы экспорта в релизные миграции и производить деплой лендингов в автоматическом режиме.
Структура файла экспорта
Таким образом, при смягчении основных сложностей полет нормальный.
Качество результата обеспечивается стремлением исключить человеческий фактор, а также тестированием и фиксацией результата для деплоя на всех стадиях.
Конструктор стал возможностью перенести часть задач по созданию лендингов в «на плечи» контент-менеджеров создание лендинга занимает несколько часов одного сотрудника, а не всей команды.
Созданы десятки лендингов вообще без привлечения команды разработки. Интересен опыт аудитории в подобных вопросах — поделитесь своей болью (а главное — своим выбором «обезболивающего»)!