Micro Frontend — расширение концепции микросервисов на фронтенд-разработку.

внешний интерфейс JavaScript API браузер

Переведено с https://micro-frontends.org/

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

Что такое микрофронтенд?

микро интерфейсПервоначально этот термин появился в 2016 году из журнала ThoughtWorks Technology Radar [ https://www.thoughtworks.com/radar/techniques/micro-frontends], что расширяет концепцию микросервисов до области внешнего интерфейса. Текущая тенденция заключается в создании многофункционального и мощного интерфейсного приложения, одностраничного приложения (SPA), которое само по себе обычно строится на архитектуре микросервисов. Интерфейсный слой обычно разрабатывается отдельной командой, и со временем он может стать больше и сложнее в обслуживании. Это легендарный фронтенд-гигант (Frontend Монолит) [https://www.youtube.com/watch?v=pU1gXA0rfwc ].

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

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

Интегрированный передний конец

вертикальная организация

Что такое современное фронтенд-приложение

Я использовал фразу «создать современное интерфейсное приложение» во введении, но давайте дадим некоторый контекст этому термину.

С более широкой точки зрения, Арал Балкан написал соответствующий блог об этой концепции, которую он называет континуумом документ-приложение. Он предлагает концепцию скользящей шкалы, где в крайнем левом углу шкалы находится веб-сайт, состоящий из статических документов, связанных друг с другом ссылками, а в крайнем правом — чисто управляемое поведением приложение с небольшим содержанием, например веб-сайт. онлайн фоторедактор.

Если вы расположите свой проект в левой части этой области, интеграция на уровне веб-сервера будет более подходящей. В этой модели сервер собирает содержимое каждого компонента на странице и объединяет его строку HTML обратно пользователю. Контент обновляется путем перезагрузки с сервера или частично заменяется ajax. Густав Нильссон Котте написал обширную статью на эту тему.

Когда пользовательский интерфейс должен обеспечить немедленную обратную связь, даже при ненадежном соединении, веб-сайта, созданного исключительно сервером, недостаточно. Чтобы достичь Optimistic UI или Skeleton ScreensПри такой методике нужно обновить UI на самом устройстве. PWA, предложенный Google, точно описывает этот сбалансированный подход (прогрессивное улучшение), обеспечивая при этом производительность, подобную приложению. Этот тип приложений находится где-то в середине континуума документ-приложение по шкале выше. Здесь чисто серверного решения уже недостаточно, приходится выносить основную логику в браузер, чему и будет посвящена данная статья.

Основная идея микрофронтендов

    Технология не имеет значения

Каждая команда должна иметь возможность выбирать и обновлять свой технологический стек без необходимости взаимодействия с другими командами.Custom ElementsЭто очень хороший способ скрыть детали реализации, предоставляя при этом унифицированный интерфейс для внешнего мира.

Изолировать командный код

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

Установите префиксы для каждой команды

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

Нативные функции браузера имеют приоритет над пользовательскими API.

усыновитьСобытия браузера для передачи данныхвместо создания глобальной системы издатель-подписчик. Если вам нужно создать межкомандный API, убедитесь, что он максимально прост.

Создавайте адаптивные веб-сайты

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


DOM — это API

Пользовательские элементы Пользовательские элементыАспект функциональной совместимости спецификации, ориентированной на веб-компоненты, является важным элементом функциональной интеграции в браузере. Каждая команда создает свои компоненты, используя веб-технологию по своему выбору, и инкапсулирует их впользовательский элемент 中(比如   ).这个特定元素的 DOM 声明(标签名、属性和事件)对于其他团队来说体现为一个协定或者叫公共 API。这样做的好处是其他人可以使用这个组件及其功能而不需要知道实现细节,他们只需要能够和 DOM 交互即可。

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

Эта статья разделена на две части. Сначала мы представим композицию страницы (Page Composition) — как объединить компоненты из разных команд в одну страницу. Затем мы приведем несколько примеров, показывающих конверсии страниц на стороне клиента (Page Transition) реализация.

комбинация страниц

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

базовый прототип

Следующая страница продукта для магазина моделей тракторов послужит основой для последующих примеров.

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

Все страницы HTML проходят черезЧистый JavaScriptи строки шаблона ES6 генерируются на стороне клиента,без всяких зависимостей. В коде используется простое разделение состояния/разметки, и вся HTML-страница будет перерисовываться всякий раз, когда происходит изменение — никаких классных сравнений DOM, пока нет.Универсальный рендеринг. Конечно, нетРазделение команды- Весь код находится в одном файле js/css.

Интеграция с клиентом

В приведенном ниже примере страница разделена на разные компоненты и фрагменты, каждый из которых принадлежит трем разным командам.Торговая группа(синий) отвечает за все, что связано с процессом выставления счетов, т.е.кнопка купитьа такжемини-тележка.Рекомендуемая группа(зеленый) Отвечает за раздел рекомендаций продукта на странице. Сама страница естьГруппа товаров(красный) отвечает.

Группа товаровРешите, какая характерная точка используется и где эта функция размещается на макете страницы. Страница содержит информацию, которая может быть предоставлена ​​самой группой продуктов, например название продукта, изображения и параметры, которые можно взять, но также может включать фрагменты (пользовательские элементы), предоставленные другими командами.

Как создать пользовательский элемент

давайте положимкнопка купитьНапример. Группа продуктов просто<blue-buysku="t_porsche"></blue-buy>Эту кнопку можно использовать, добавив ее в нужное место на странице. Чтобы эта кнопка работала, группе транзакций также необходимо зарегистрировать элемент на странице.blue-buy.

class BlueBuy extends HTMLElement {
    constructor() {
        super();
        this.innerHTML = `<button type="button">buy for 66,00 €</button>`;
    }
    disconnectedCallback() { ... }
 }
window.customElements.define('blue-buy', BlueBuy);

Теперь всякий раз, когда браузер встречает новыйblue-buy 标签时,都会调用这个构造器。 в, thisявляется ссылкой на корневой узел DOM этого пользовательского элемента. Доступны все стандартные свойства и методы элемента DOM, такие какinnerHTML или getAttribute().

Как определено в стандартной документации, единственным требованием при именовании пользовательских элементов является то, что имя должноСтавь тире -Для обеспечения совместимости с будущими новыми тегами HTML. В следующем примере используйте[team_color]-[feature]Соглашения об именах. Командные пространства имен предотвращают коллизии, и такой подход делает точку ответственности более ясной: просто взгляните на DOM.

Связь между родительскими и дочерними элементами / модификация DOM

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

container.innerHTML;
// => <blue-buy sku="t_porsche">...</blue-buy>
container.innerHTML = '<blue-buy sku="t_fendt"></blue-buy>';

старый элементdisconnectedCallbackМетод будет вызываться синхронно для выполнения некоторых операций очистки, таких как удаление прослушивателей событий. Затем вновь созданныйt_fendtэлементальconstructorбудет называться.

Другой более производительный вариант — просто обновить существующий элемент.skuАтрибуты.

document.querySelector('blue-buy').setAttribute('sku', 't_fendt');

Если группа продуктов использует механизм шаблонов, который поддерживает сравнение DOM, например React, его алгоритм сделает это автоматически.

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

const prices = {
    t_porsche: '66,00 €',
    t_fendt: '54,00 €',
    t_eicher: '58,00 €',
 };
class BlueBuy extends HTMLElement {
    static get observedAttributes() {
        return ['sku'];
    }
    constructor() {
        super();
        this.render();
    }
    render() {
        const sku = this.getAttribute('sku');
        const price = prices[sku];
        this.innerHTML = `<button type="button">buy for ${price}</button>`;
        }
    attributeChangedCallback(attr, oldValue, newValue) {
        this.render();
    }
    disconnectedCallback() {...}
 }
window.customElements.define('blue-buy', BlueBuy);

Чтобы избежать дублирования, представитьrender()Методы иconstructor а также attributeChangedCallbackназывается в. Этот метод собирает необходимые данные и заполняет новую метку.innerHTMLАтрибуты. Именно здесь находится код инициализации при принятии решения об использовании более зрелого механизма шаблонов или фреймворка для пользовательских элементов.

Поддержка браузера

В приведенном выше примере используется версия 1 спецификации Custom Element, которая в настоящее время поддерживается в Chrome, Safari и Opera. Но document-register-element — это легковесный и тщательно протестированный полифилл, позволяющий этой функции работать во всех браузерах. Под капотом он использует широко поддерживаемый API-интерфейс Mutation Observer, поэтому за прослушиванием дерева DOM нет агрессивного взлома.

Совместимость с фреймворком

Поскольку пользовательские элементы Custom Element являются веб-стандартом, поддерживаются все основные фреймворки JavaScript, такие как Angular, React, Preact, Vue или Hyperapp. Но если говорить подробно, некоторые фреймворки все же будут иметь проблемы с реализацией. Вы можете получить доступ к Custom Elements Everywhere. В этом наборе тестов совместимости Роб Додсон выделен без решенных проблем.

Общение между детьми и родителями или братьями и сестрами / события DOM

Однако передачи свойств сверху вниз недостаточно для всех взаимодействий. В нашем примере, когда пользователь нажимает кнопку «Купить», мини-корзина должна обновляться.

Оба приведенных выше фрагмента поддерживаются группой транзакций (синяя), поэтому для достижения эффекта связи с мини-корзиной и кнопкой они могут создать встроенный JavaScript API для связи. Но это требует, чтобы экземпляры компонентов понимали друг друга, а также нарушает принцип изоляции.

Более чистый подход заключается в использовании механизма издатель-подписчик: один компонент может публиковать информацию, а другие компоненты подписываются на указанную тему. К счастью, в браузеры встроена эта функция, а именноclick,select,mouseoverи другой механизм работы событий браузера. В дополнение к этим локальным событиям существует вероятность того, что черезnewCustomEvent(...)для создания событий более высокого уровня. События всегда привязаны к тому, что они создают или назначают На узлах DOM большинство локальных событий также поддерживают функцию всплытия, которая позволяет прослушивать все события определенного узла поддерева в DOM. Если вы хотите прослушивать все события на странице, достаточно прикрепить прослушиватель событий к элементу окна. В этом примере следующееblue:basket:changedСоздание события примерно выглядит так:

class BlueBuy extends HTMLElement {
  [...]
  connectedCallback() {
    [...]
    this.render();
    this.firstChild.addEventListener('click', this.addToCart);
  }
  addToCart() {
    // maybe talk to an api
    this.dispatchEvent(new CustomEvent('blue:basket:changed', {
      bubbles: true,
    }));
  }
  render() {
    this.innerHTML = `<button type="button">buy</button>`;
  }
  disconnectedCallback() {
    this.firstChild.removeEventListener('click', this.addToCart);
  }
 }

Мини-тележки теперь доступны по адресуwindowОбъект подписан на это событие и будет уведомлен, когда ему потребуется обновить данные.

class BlueBasket extends HTMLElement {
  connectedCallback() {
    [...]
    window.addEventListener('blue:basket:changed', this.refresh);
  }
  refresh() {
    // fetch new data and render it
  }
  disconnectedCallback() {
    window.removeEventListener('blue:basket:changed', this.refresh);
  }
 }

При такой реализации фрагмент мини-корзины добавляет прослушиватель для элементов DOM, которые не входят в его область действия (окно). Для большинства приложений этот подход не вызывает проблем, но если вас не устраивает этот подход, вы также можете позволить самой странице (группе продуктов) прослушивать это событие и, вызвав функцию DOM-элементаrefresh()метод уведомления мини-тележки.

// page.js
const $ = document.getElementsByTagName;
$('blue-buy')[0].addEventListener('blue:basket:changed', function() {
  $('blue-basket')[0].refresh();
 });

Императивные вызовы методов DOM на самом деле довольно редки, но, например, в API элемента видео. Этот императивный подход (изменение свойства) по-прежнему следует рекомендовать, если это возможно.

Рендеринг сервера/универсальный рендеринг

Хорошей практикой является использование пользовательских элементов для интеграции компонентов в браузер. Однако при фактическом создании сайта, доступного в Интернете, вероятно, первоначальная производительность загрузки является ключевым моментом.До того, как все JS-фреймворки будут загружены и выполнены, пользователь увидит только белый экран. Кроме того, стоит подумать о том, что произойдет с веб-сайтом, если JavaScript выйдет из строя или будет заблокирован. Джереми Кит объясняет важность этого вопроса в своей электронной книге/подкасте Resilient Web Design. Таким образом, возможность отображать основной контент на стороне сервера является ключевым моментом. К сожалению Спецификация веб-компонентов вообще не обсуждает рендеринг на стороне сервера. В JavaScript нет, как и в пользовательских элементах :(

Пользовательские элементы + включения на стороне сервера (Включает) = ❤️

Предыдущий пример был переработан, чтобы представить рендеринг на стороне сервера. У каждой команды есть свой экспресс-сервер, пользовательские элементыrender()Доступ к методам также осуществляется через URL.

$ curl http://127.0.0.1:3000/blue-buy?sku=t_porsche
<button type="button">buy for 66,00 €</button>

Имя тега пользовательского элемента используется в качестве имени пути, а имя атрибута становится параметром запроса. Это дает вам возможность серверного рендеринга контента для каждого компонента. сотрудничать снова<blue-buy>Пользовательский элемент, очень близкийУниверсальные веб-компонентыМатериал выходит:

<blue-buy sku="t_porsche">
    <!--#include virtual="/blue-buy?sku=t_porsche" -->
 </blue-buy>

#includeАннотации являются частью включения на стороне сервера, функции, поддерживаемой большинством веб-серверов. Да, это тот же метод, который мы использовали для встраивания текущей даты на наш веб-сайт давным-давно. Есть несколько других дополнительных технологий, таких как ESI, nodesi, compoxure и tailor, но для нашего проекта SSI оказался простым и довольно стабильным решением.

Прежде чем веб-сервер отправит полную страницу в браузер#includeКомментарии заменены на/blue-buy?sku=t_porscheВозвращаемое значение. Конфигурация в Nginx следующая:

upstream team_blue {
  server team_blue:3001;
 }
upstream team_green {
  server team_green:3002;
 }
upstream team_red {
  server team_red:3003;
 }
server {
  listen 3000;
  ssi on;
  location /blue {
    proxy_pass  http://team_blue;
  }
  location /green {
    proxy_pass  http://team_green;
  }
  location /red {
    proxy_pass  http://team_red;
  }
  location / {
    proxy_pass  http://team_red;
  }
 }

инструкцияssi:on;Используется для включения функции SSI,upstream а также locationБлок используется для обеспечения того, чтобы URL-адрес каждой команды был правильно назначен соответствующему сервису, например./blueURL-адреса, начинающиеся с, будут перенаправлены в соответствующую службу приложения (team_blue:3001). Кроме того, /Маршруты сопоставляются с группами товаров (красные), отвечающими за домашнюю страницу и страницы товаров.

Следующие анимационные шоу вJavaScript отключенИспользование Tractor Shop в вашем браузере.

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

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

Вы можете запустить этот код локально. Просто установите Docker Compose[https://docs.docker.com/compose/install/].

git clone https://github.com/neuland/micro-frontends.git
cd micro-frontends/2-composition-universal
docker-compose up --build

Docker запустит Nginx на порту 3000 и создаст образы node.js для каждой команды. когда вы открываете его в своем браузереhttp://127.0.0.1:3000/Вы должны увидеть красный трактор. пройти через docker-composeПриведенный комбинированный журнал позволяет легко увидеть, что происходит в сети. Плохо то, что на данный момент нет контроля над цветом выходного сообщения, поэтому приходится смириться с тем, что синие группы транзакций могут подсвечиваться зеленым :)

srcФайлы будут сопоставлены с отдельными контейнерами, а приложение узла перезапустится, когда вы внесете изменения в код. Исправлять nginx.confнужно перезапуститьdocker-composeвступить в силу. Тогда вы можете просто возиться и давать обратную связь.

Сбор данных и статус загрузки

продолжение следует...

Следуйте GitHub Repo [https://github.com/neuland/micro-frontends] Чтобы получить уведомление

Справочные ресурсы

  • [ https://speakerdeck.com/naltatis/micro-frontends-building-a-modern-webapp-with-multiple-teams ]

  • [ https://medium.com/@tomsoderlund/micro-frontends-a-microservice-approach-to-front-end-web-development-f325ebdadc16 ] 

  • [ https://custom-elements-everywhere.com/ ]

  •  [ https://www.manufactum.com/ ]

——————————————————

Нажмите и удерживайте QR-код, следуйте Dazhuanzhuan FE