Как переиспользовать код с бандлами Symfony 5? Часть 3. Интеграция бандла с хостом: шаблоны, стили, JS

    Поговорим о том, как прекратить копипастить между проектами и вынести код в переиспользуемый подключаемый бандл Symfony 5. Серия статей, обобщающих мой опыт работы с бандлами, проведет на практике от создания минимального бандла и рефакторинга демо-приложения, до тестов и релизного цикла бандла.


    В предыдущей статье мы вынесли в бандл основной код и шаблоны, настроили роутинг и подключение сервисов в Dependency Injection контейнер. В этой статье будем встраивать бандл в приложение-хост:


    • Интеграция шаблонов: 2 пути
    • Интеграция шаблонов: независимый модуль
    • Подключение стилей бандла в сборку
    • Интеграция шаблонов: встраивание в шаблоны хоста
    • Переопределение стилей и JS


    Если вы не последовательно выполняете туториал, то скачайте приложение из репозитория:
    https://github.com/bravik/symfony-bundles-tutorial/
    и переключитесь на ветку 2-basic-refactoring.


    Инструкции по установке и запуску проекта в файле README.md.
    Финальную версию кода для этой статьи вы найдете в векте 3-integration.


    Интеграция шаблонов


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


    Наше приложение работает и используует шаблоны бандла. Однако шаблоны редактора мероприятий в бандле зависят от базового шабона base.html.twig, который принадлежит приложению-хосту:


    {% extends 'base.html.twig' %}

    Базовый шаблон определяет костяк HTML-кода страницы и размечает основные блоки, которые в свою очередь уже переопределяются шаблонами каждой из страниц. С одной стороны, редактору мероприятий нужен такой базовый шаблон, но с другой стороны у каждого приложения-хоста может быть свой базовый шаблон с отличным набором блоков. Как можно сделать шаблоны редактора независимыми?


    Есть 2 пути: сделать полностью независимый модуль со своим собственным базовым шаблоном и оформлением, либо попытаться органично встроиться в имеющийся шаблон админки приложения.

    Какой путь выбрать зависит от вашей задачи и степени, в которой вы готовы пожертвовать переиспользуемостью бандла.


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


    Если мы не готовы жертвовать reusability, то редактор должен быть полностью независимым и иметь свой базовый шаблон. Для этого мы сделаем внутри бандла свой base.html.twig, а точнее скопируем из приложения хоста.


    Удалим из скопированного шаблона все лишнее:


    • include menu.html.twig — в бандле нет меню
    • {{ encore_entry_link_tags('host-styles') }} и {{ encore_entry_script_tags('host-app') }} — это entry-файл стилей и скриптов хоста, собираемых webpack encore

    После этого в шаблонах templates/editor/* бандла поменяем:


    {% extends 'base.html.twig' %}
     на
    {% extends '@Calendar/base.html.twig' %}

    Теперь редактор мероприятий не зависит от шаблонов хоста. Проверим работает ли приложение.


    Отлично, проект запускается и работает. Переходим в Редактор и видим… голый HTML список событий и голую HTML-форму события.


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


    Для оформления бандла мы используем SCSS, а для их сборки — Symfony Encore (обертку Symfony, упрощающую работу с Webpack).


    В шаблон ассеты подключаются с помощью entry-файлов. Entry-файлы — это итоговые файлы, в которые вебпак соберет ассеты и которые непосредственно подключаются в шаблон. Они регистрируются в webpack.config.js.


    Entry-файлы подключаются в шаблон twig-функциями encore_entry_link_tags(entry-file) и encore_entry_script_tags(entry-file), предоставленных Encore.


    Добавим бандлу собственные entry-файлы для стилей и скриптов. Для этого скопируем папку assets из хоста в корень бандла:


    cp ./assets ./bundles/CalendarBundle/assets -R

    Переименуем entry-файлы:


    mv host-app.js calendar-editor-app.js
    mv host-styles.scss calendar-editor-styles.scss

    Удалим лишний файл assets/scss/events/_main.scss и его импорт в calendar-editor-styles.scss.


    Заметим так же, что у нас из хоста скопировался файл стилей scss/events/calendar.scss, — это стили виджета календаря. Они тоже должны быть внутри бандла, поэтому мы оставляем их здесь и удаляем из папки assets хоста. В файле hosts-styles.scss нужно поправить импорт стилей виджета на следующий:


    @import "../../vendor/bravik/calendar-bundle/assets/scss/widget/calendar";

    На страницах редактора виджет календаря не используется, поэтому убираем его импорт из calendar-editor-styles.scss.


    Чтобы WebpackEncore добавил наши entry-файлы в сборку добавим их в webpack.config.js:


    Encore
    // ...
    .addEntry(
        'calendar-editor-app',
         './vendor/bravik/calendar-bundle/assets/js/calendar-editor-app.js'
    )
    .addStyleEntry(
        'calendar-editor-styles',
         './vendor/bravik/calendar-bundle/assets/scss/calendar-editor-styles.scss'
    )

    Обратите внимание, что мы указываем пути к файлам не в ./bundles/CalendarBundle, а в папке vendors. Готовый бандл будет подключатся с помощью composer и загружаться именно в эту папку. На время разработки мы в первой статье установили в vendors симлинк(ярлык) на нашу локальную папку.


    Проверим сборку ассетов.
    Перезапустите Encore-сервер: убейте старый процесс и снова выполните команду npm start.
    Если сборка ассетов пройдет без ошибок — вы все сделали правильно.
    Если что-то не получилось, вы всегда можете переключиться на git-ветку 3-integration с финальным результатом этой статьи.


    Осталось добавить entry-файлы бандла в шаблоны.


    В шаблоне @Calendar/base.html.twig в блок stylesheets добавьте:


    {{ encore_entry_link_tags('calendar-editor-styles') }}

    и в блок javascripts:


    {{ encore_entry_script_tags('calendar-editor-app') }}


    Посмотрим результат: ассеты должны быть подключены и страница оформлена.


    Для многих приложений — это хороший подход: у вас полностью независимое отдельное приложение внутри хоста, со своим интерфейсом, стилями, скриптами, в котором вы делаете всю нужную работу и возвращаетесь обратно.

    Интеграция шаблонов: встраиваемый редактор


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


    Но если добиваться такой степени изолированности и разделения,
    то нужен ли нам вообще бандл?

    Может быть стоит вынести всю эту логику в отдельное полностью независимое микроприложение и взаимодействовать с ним через API? А в бандле оставить только виджет и сервис, извлекающий данные из API? Возможно это хороший вариант для вашей ситуации, но мы хотим более тесной интеграции.


    Мы хотим встроить редактор событий в приложение так, чтобы пользователь не замечал переходов: не открывались новые вкладки, не менялся интерфейс. Для этого нам нужно встроить шаблоны бандла в базовый шаблон хоста, а так же подключить его ассеты.


    В Symfony предусмотрен механизм для переопределения частей бандла. Например, мы можем переопределить любой его шаблон.

    В папке хоста templates создадим папку bundles/CalendarBundle. Имя и структура папок важны.


    Когда вы указываете путь шаблона бандла Symfony по умолчанию вначале ищет не в папке templates бандла, а прежде в templates/bundles/<имя бандла> хоста. И только если не найдет запрошенного файла в этом месте, попробует найти его в templates внутри бандла.


    Таким образом, придерживаясь в templates/bundles/CalendarBundle хоста структуры папок и именования шаблонов идентичного бандлу, то мы можем переопределить любой его шаблон.


    Давайте переопределим в хосте базовый шаблон бандла base.html.twig. Для этого скопируем его из хоста:


    cp ./bundles/CalendarBundle/templates/base.html.twig ./templates/bundles/CalendarBundle/base.html.twig

    Внутри переопределенного шаблона добавим меню из базового шаблона хоста. Вставьте после <body>:


    {% include 'menu.html.twig' %}

    Попробуем обновить страницу редактора, — и у нас появилось меню из хоста!


    Продвинутое переопределение шаблонов


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


    В примитивном случае там будет немного повторяющегося кода. Но если у нас большое приложение, то базовый шаблон может быть гораздо сложнее. Мы не хотим поддерживать в двух местах один шаблон, поэтому попробуем свести дублирование к минимуму. Для этого унаследуем преопределенный шаблон бандла от базового шаблона приложения.


    Добавим в начале переопределенного шаблона бандла
    templates/bundles/CalendarBundle/base.html.twig:


    {% extends 'base.html.twig' %}

    Здесь base.html.twig — это базовый шаблон хоста.


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


    {% extends 'base.html.twig' %}
    
    {% block title %}Редактор событий{% endblock %}
    
    {% block stylesheets %}
        {{ parent() }}
        {{ encore_entry_link_tags('calendar-editor-styles') }}
    {% endblock %}
    
    {% block javascripts %}
        {{ parent() }}
        {{ encore_entry_script_tags('calendar-editor-app') }}
    {% endblock %}

    Мы добавили {{ parent() }} внутри блоков, чтобы не потерять ничего из родительского шаблона:


    В этом примере у нас совпадают названия блоков бандла и хоста, но в другом приложении могут не совпадать. И вообще нас смущает такое неявное переопределение блоков хоста блоками бандла.


    Все неявное в коде — это зло и ваши потенциальные проблемы в будущем.

    Чтобы дать больше контроля над блоками, мы можем добавить блокам бандла префиксы. Тогда базовый шаблон бандла и его переопределенный вариант станет выглядеть вот так:


    {% extends 'base.html.twig' %}
    
    {% block title %}
        {%- block calendar_title %}Редактор событий{% endblock -%}
    {% endblock %}
    
    {% block stylesheets %}
        {{ parent() }}
        {{ encore_entry_link_tags('calendar-editor-styles') }}
        {% block calendar_styles %}{% endblock %}
    {% endblock %}
    
    {% block body %}
        {%- block calendar_body %}{% endblock -%}
    {% endblock %}
    
    {% block javascripts %}
        {{ parent() }}
        {{ encore_entry_script_tags('calendar-editor-app') }}
        {% block calendar_scripts %}{% endblock %}
    {% endblock %}
    

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


    Переопределение стилей


    Переопределение стилей сводится к переопределению entry-файлов бандла.


    Таким образом независимо от того, встраиваете ли вы или используете независимо от хоста, везде, где подключается этот entry-файл, вы подключите свой переопределенный.


    Например, мы можем создать свой собственный overriden-calendar-styles.scss:


    // Импортируем оригинальные стили бандла
    @import "../../vendor/bravik/calendar/assets/scss/calendar-styles";
    
    // Добавляем свои модификации
    @import "calendar/customizations";

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


    Если нужно что-то кардинально изменить, можно вместо импорта скопировать содержимое entry-файла и переделать по-своему. Однако в этом случае, при обновлениях бандла придется вручную следить за обновлениями переопределенной версии.


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


    Таким же образом можно переопределить entry-файлы JavaScript.


    Сейчас у нас нет JS-логики. Но entry-файл может выглядеть так:


    import CalendarApp from "../calendar/CalendarApp";
    
    global.bravikCalendar = new CalendarApp();
    global.bravikCalendar.init();

    Entry-файл специально делается минималистичным. Он не должен меняться и рассчитан на то, что его могут скопировать и переопределить.

    Вся логика, которая может поменяться прячется внутри CalendarApp.
    Это дает возможность переопределить файл в хосте, и добавить нужную логику. Например так:


    import CalendarApp from "../../vendor/bravik/calendar/assets/js/calendar/CalendarApp";
    import myCustomLogic from "./MyCutomLogic";
    
    global.bravikCalendar = new CalendarApp();
    global.bravikCalendar.registerSomeCustomLogic(myCustomLogic);
    // And more ...
    global.bravikCalendar.init();

    Резюме


    Мы рассмотрели два способа интеграции шаблонов и логики бандла в приложение-хост: как независимое под-приложение с собственным базовым шаблоном, стилями и JS или как набор шаблонов, встраиваемых в шаблоны хоста. Первый способ прост и позволяет избежать конфликтов с хостом, а второй позволяет сделать более незаметным «шов» на стыке представления бандла и хоста.


    Финальную версию кода для этой статьи вы найдете в ветке 3-integration.


    В следующей статьеразберемся как предоставить пользователям бандла интерфейс, точки для расширения его функциональности уникальной для проекта логикой.


    Другие статьи серии:


    Часть 1. Минимальный бандл
    Часть 2. Выносим код и шаблоны в бандл
    Часть 3. Интеграция бандла с хостом: шаблоны, стили, JS
    Часть 4. Интерфейс для расширения бандла
    Часть 5. Параметры и конфигурация
    Часть 6. Тестирование, микроприложение внутри бандла
    Часть 7. Релизный цикл, установка и обновление

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 0

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

    Самое читаемое