Микрофронтенды На Hh.ru

Современный интерфейс — это больше, чем просто формы и стили.

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

Для разработки и поддержки такого левиафана требуется множество разработчиков — писать еще больше кода.

Команда становится больше, кодовая база растёт — работать с монолитом становится всё сложнее.

Казалось бы, выхода нет – сиди и мучайся.

Но мы смогли выйти из этой сложной ситуации.

Меня зовут Влад Коротун, я ведущий front-end разработчик в одной из продуктовых команд. hh.ru .

В этой статье я расскажу вам о нашем пути от большого монолита к так называемым «микрофронтендам».



Микрофронтенды на hh.ru



Основные проблемы

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

    Трудно определить, кто несет ответственность за тот или иной код.

  • В монолите сложно проводить рефакторинг и внедрять новые технологии.

  • Сборка монолита с каждым годом занимает все больше времени.

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

  • При выпуске наблюдается спешка, часто возникают конфликты.

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

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

В нашем фронтенде более 500 страниц, миллионы строк кода, поэтому все это мы знаем не понаслышке.

И мы нашли выход из этой ситуации — вынести части проекта в отдельные микросервисы.



Попытка написать

Не так давно перед нами стояла задача добавить веб-интерфейс чата между соискателями и работодателями.

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

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

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

Связь с основным сайтом осуществлялась через сообщения postMessage. Благодаря тому, что чат получил собственную независимую кодовую базу, он стал нашим первым сервисом на React 17.

Прокси-страницы

Работать с iframe не всегда удобно.

В случае с чатом, который является скорее веб-приложением, чем макетом страницы, iframe не вызвал никаких проблем.

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

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

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

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

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

А поскольку его стили, помимо React, используются еще и в коде Legacy-xsl, их имена не хэшируются.

Из-за несоответствия версии основного сайта и встроенной страницы могут возникать конфликты.

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



Компоненты прокси

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

Поэтому следующим витком эволюции наших микрофронтендов стали прокси-компоненты.

Прокси-компоненты, как и прокси-страницы, используют нашу библиотеку компонентов.

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

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

Это позволило нам без дополнительных доработок размещать компоненты в блоках, которые можно отображать с черным фоном и белым текстом или наоборот — с белым фоном и черным текстом.

Цвет текста в любом случае будет унаследован от родителя.



А что насчет ССР?

Чтобы ускорить отображение страниц для пользователя и улучшить индексацию сайта, мы используем Server-Side-Rendering. Первая версия интеграции прокси-компонентов загружала код непосредственно в браузер пользователя.

Соответственно, первый рендер, пришедший с сервера, вообще не включал в себя прокси-компоненты.

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

И вот что мы сделали: Мы перенесли инициализацию прокси-компонентов из браузера в наш BackendForFrontend на Python. Если нужно было отрендерить прокси-компонент, обработчик ходил в сервис и помещал результат в специальный узел.

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

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



Недостатки

Единственный недостаток работы с ShadowRoot таким способом — первые пару секунд пользователи видят фрагменты страницы без стилей.

Почему это происходит? Стили прокси-компонента могут конфликтовать с основным документом, поэтому мы не помещаем их в DOM при первом рендеринге.

Стили появляются только тогда, когда весь код перенесен в ShadowRoot.

Что мы собираемся с этим делать?

Мы планируем перенести нашу библиотеку компонентов в модули CSS. Поскольку его код поставляется «несобранным» и собирается самим сервисом, имена классов получат уникальные префиксы, и в сборку будут включены только те стили, которые в нем используются.

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

Полученные результаты

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

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

Управлять зависимостями становится еще сложнее — их приходится обновлять отдельно в каждом сервисе.

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

Это может привести к различиям в стилях повторно используемых компонентов.

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

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

Однако у нас есть больше преимуществ:

  • Сервис собирается гораздо быстрее монолита — 1-2 секунды против 15. Быстрее видишь результат — быстрее принимаешь решения.

  • Никакой спешки при выпуске, когда из-за конфликта задач весь поезд останавливается до выяснения ситуации.

  • Автотесты быстрые.

    На основном проекте это чуть больше часа, а в сервисе мы запускаем только те тесты, которые затрагивают фрагменты со встроенными прокси-компонентами или страницы.

Это все, что у меня есть.

Задавайте вопросы в комментариях, рассказывайте, как вы режете свой монолит. Счастливого мая! Теги: #Разработка сайтов #микросервисы #react #iframe #shadowdom

Вместе с данным постом часто просматривают: