Делимся нашим опытом создания различных решений для микрофронтендов за последние четыре года.

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

Как мы можем сами эффективно развить эту способность и что нужно учитывать, чтобы получить отличный результат?

В этой статье я хочу поделиться нашим опытом создания различных решений для микрофронтендов за последние четыре года. Многие из наших опытов и извлеченных уроков были переданы через наш проект с открытым исходным кодом Piral, который вот-вот будет выпущен в версии v1.

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

Bit.dev - это место для публикации, документирования и систематизации JS-компонентов (React, Angular, Vue и т. Д.). Bit дает вам свободу непрерывно публиковать новые компоненты пользовательского интерфейса (из любой кодовой базы) в вашу собственную общую коллекцию компонентов или систему дизайна (в Bit.dev).

Статьи по Теме

Прежде чем погрузиться в эту статью, убедитесь, что вы знаете (или хотя бы понимаете) содержание двух предыдущих статей:

  1. Совместное использование зависимостей в Micro Frontend
  2. Связь между микрочастицами

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

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

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

Прочный фундамент

Что бы мы ни хотели делать с кросс-фреймворковыми компонентами, у нас должна быть сильная архитектура с четко определенными точками соприкосновения. Например, очевидный вопрос в одностраничном приложении может быть таким: как работает маршрутизация?

Очевидно, что каждая библиотека или фреймворк, предлагающий определение компонентов, имеет ответ на эти вопросы. В то время как некоторые варианты более ограничены (обычно они называются «framework»), другие дают больше свободы (и поэтому обычно выбирают термин «библиотека для создания пользовательских интерфейсов»).

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

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

Еще один момент, который необходимо обсудить и определить заранее, - это степень изоляции. Хотя может иметь смысл прямой доступ к компонентам из других фреймворков (и, следовательно, знать о них), может оказаться желательным наличие агрегирования компонентов, как описано в предыдущей статье.

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

Однако в конечном итоге он также решает проблему MxN.

Решение проблемы MxN

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

Предположим, у нас есть M языков программирования и N типов машин. Сколько компиляторов вам нужно написать? Что ж, очевидно, ответ - MxN. Это было несложно, правда?

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

Например, если взять 4 языка и 3 архитектуры машины, мы получим 12 ребер (MxN).

Решение предыдущей проблемы было простым: ввести промежуточный язык (или вообще: промежуточное представление). Таким образом, все языки программирования M компилируются в один и тот же промежуточный язык, который затем компилируется в целевую архитектуру. Вместо масштабирования MxN теперь у нас есть M + N. Добавить новую выходную архитектуру так же просто, как добавить компиляцию из промежуточного языка в новую архитектуру.

Давайте посмотрим, как изменится диаграмма нашего примера при добавлении промежуточного представления (IR). Теперь у нас всего 7 ребер (M + N).

То же самое было сделано и для поддержки IDE. Вместо поддержки языков программирования M для IDE N теперь у нас есть единый стандарт языковой поддержки (называемый протоколом языкового сервера - сокращенным LSP).

Это секрет, почему команда TypeScript (и другие тоже) может поддерживать VS Code, Sublime, Atom и многие другие редакторы. Они просто обновляют свою реализацию LSP, а остальное следует. Поддержать новую IDE так же просто, как написать плагин LSP для соответствующей IDE - больше ничего не требуется.

В чем теперь помогают нам эти сказки с кросс-фреймворковыми компонентами? Что ж, если у нас есть M фреймворки, то включение обмена компонентами между фреймворками между N из них снова будет MxN. Решить эту проблему теперь можно, опираясь на опыт приведенных выше решений. Нам нужно найти подходящее «промежуточное представление».

Следующая диаграмма показывает это для трех фреймворков. Промежуточное представление позволяет конвертировать из - и в - разные фреймворки. Всего здесь 6 ребер (2N).

Если мы даже возьмем одну из фреймворков в качестве IR, мы получим 4 ребра (2N - 2), что позволит сэкономить два преобразователя, но также повысит производительность в «счастливом случае». , т. е. когда данная структура является наиболее часто используемой для компонентов.

В Пирале мы выбрали React в качестве промежуточного решения. Для этого были веские причины:

  • React поддерживается всеми основными браузерами, даже IE11 и старше.
  • React имеет очень четко определенную компонентную модель, которая легковесна.
  • React дает нам четкий жизненный цикл компонентов
  • Контекст React позволяет легко передавать контекстную информацию
  • Ленивая загрузка и обработка ошибок тривиальны для поддержки
  • Поскольку React был основным выбором для нашего дерева рендеринга, мы хотели оставаться рядом с ним.

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

Простая оболочка

Давайте продолжим то, что мы уже обсуждали. Нам определенно нужен четко определенный жизненный цикл компонентов. Полный жизненный цикл можно указать через ComponentLifecycle интерфейс, как показано ниже.

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

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

Кроме того, мы можем вводить значения с переносом контекста, такие как контекст маршрутизатора (содержащий, среди прочего, history, location и другие).

Что такое createPortal и destroyPortal? Это глобальные действия, которые позволяют регистрировать или уничтожать запись портала. Портал использует ReactPortal дочерний элемент внизу для проецирования элемента из дерева рендеринга React в другое место в дереве DOM. Следующая диаграмма иллюстрирует это.

Это довольно мощно. Он настолько мощный, что работает и в теневом DOM. Таким образом, промежуточное представление можно использовать (то есть проецировать) где угодно, например, в узле, который визуализируется другой структурой, такой как Vue.

Обработка ошибок оставлена ​​за границей ошибки, упомянутой выше. Компонент довольно невзрачный. Поэтому давайте займемся PortalRenderer и ComponentContainer.

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

Теперь ComponentContainer - это то место, где играет музыка. Для расширенного доступа к полному жизненному циклу React мы используем класс Component.

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

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

  1. componentDidMount отвечает за монтирование - с использованием захваченного узла DOM хоста
  2. componentDidUpdate выполняет повторное монтирование (если узел DOM изменился) или пытается выполнить упрощенную операцию обновления
  3. componentWillUnmount отвечает за отсоединение

Почему мы назначаем этот странный атрибут data-portal-id хост-узлу? Это нужно для того, чтобы позже найти хост-узел при использовании ReactPortal.

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

Монтирование этого компонента React в дереве Vue работает через DOM (хорошо!), Но, как обсуждалось, будет отображаться через портал. Таким образом, мы остаемся синхронизированными с обычным деревом рендеринга React и получаем все преимущества.

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

Код может быть таким простым, как:

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

Пример

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

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

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

Имейте в виду, что svelte-extension - это (в этом примере) способ доступа к конвертеру, переходящий от промежуточного представления (то есть React) к Svelte.

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

Как мы здесь определяем преобразователи? Сложной частью, безусловно, является подключение к настраиваемому элементу, которое мы решаем с помощью события (называемого render-html), которое запускается после подключения веб-компонента.

В остальном Svelte делает здесь очень простой вид. Создание нового экземпляра компонента Svelte фактически присоединяет его к заданному target.

Заключение

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

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

В чем, по вашему мнению, сияют кросс-фреймворковые компоненты?

Учить больше





Быстрая интерфейсная интеграция с помощью битовых компонентов
Используйте« легкую
интеграцию на основе компонентов, чтобы помочь вашей интерфейсной команде работать вместе быстрее и эффективнее. blog.bitsrc.io »