Единый UI-кит и синхронизация дизайна в Учи.ру. Часть 1

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

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

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

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

    И для разработчика, и для дизайнера

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

    Разработчики применяют UI-кит при создании сервисов. Теперь не нужно писать компоненты заново — достаточно обратиться к общей библиотеке.

    Выбираем основу библиотеки

    На сегодняшний день мы развиваем наш UI-кит на базе кросс-платформенного компилятора Stencil, используем частичную автоматизацию обновления компонентов и подтягиваем нужные параметры дизайна через токены из Figma. Это совершенно осознанное решение, так как у нас был ряд требований к фреймворку, на котором мы собирались формировать единую базу.

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

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

    Библиотек и фреймворков для создания веб-компонентов много. Например, ресурс WebComponents.dev, кроме предоставления исчерпывающего списка вариантов, обновляет их и информацию об их характеристиках. Мы составили свой топ-3: связку lit-element + lit-html и фреймворки Stencil, Hybrids — выделили наиболее значимые критерии и сравнили по ним. На момент выбора — это было в начале июня 2020 года — получили такую картину:

    Критерии

    Фреймворк/библиотека

    lit-element + lit-html

    Stencil

    Hybrids

    JavaScript ES3+

    +

    + (при использовании полифиллов)

    +

    JavaScript ES6+

    +

    +

    -

    TypeScript

    +

    +

    +

    JSX

    +

    +

    +

    Выгрузка для React

    -

    + (при использовании плагина)

    -

    Выгрузка для Vue

    -

    + (при использовании плагина)

    -

    Поддержка работы без фреймворка

    +

    +

    +

    Поддержка IE11

    +

    + (при использовании полифиллов)

    -

    Поддержка современных браузеров

    +

    +

    +

    Поддержка плагинов / возможности расширения

    -

    +

    -

    CSR

    +

    +

    +

    SSR

    + (при использовании доп. библиотеки)

    +

    -

    Подключение ассетов и CSS как отдельных файлов

    -

    +

    -

    Хорошая документация

    +

    +

    +

    Развитое комьюнити

    +

    +

    +

    Что за выгрузка в React/Vue?

    Возможно критерии «‎Выгрузка для React» и «‎Выгрузка для Vue» вам покажутся странными. Но мы искали инструмент, который сможет дать возможность работать с веб-компонентами как с обычными React и Vue компонентами.

    На первый взгляд, lit-element + lit-html и Stencil показались почти равноценными. Первые не поддерживали плагины или не давали возможности расширения, соответственно не решали наш вопрос с выгрузкой, не поддерживали подключения ассетов и CSS как отдельных файлов, а со вторым на помощь пришли плагины. Для большей ясности мы ввели коэффициенты для каждого критерия и остановились на Stencil.

    Если у вас похожий стек, можете посмотреть нашу таблицу.

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

    Когда мы выбирали инструменты, оценивали, насколько изменится размер бандла сайта и на React, и на Vue. Сначала все было не очень хорошо, потому что демосайт на React весил без библиотеки 2,5 Мбайт, а с библиотекой — 5 Мбайт. Бандл оказался таким большим потому, что по умолчанию Stencil предоставляет библиотеку с полифилами с поддержкой старых браузеров. Чтобы уменьшить размер бандла, мы использовали решение для выгрузки компонентов в виде отдельных файлов, с которыми очень хорошо работают фреймворки. Также мы отдельно вынесли шрифты и подключаем их по URL — так мы добились, чтобы с библиотекой на Stencil сайт весил 3 Мбайт. Это хорошо, хотя уже сейчас понятно, что можно еще поработать над размером дополнительных компонентов. 

    От Figma до готового компонента

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

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

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

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

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

    Для презентации UI-кита мы используем Storybook. Бандлы выкладываются сначала на внутренний ресурс. Дизайнер или менеджер может зайти, «пощупать» какой-то компонент, изучить особенности работы, почитать описания настроек. Например, можно сделать кнопку активной или неактивной и посмотреть, как это выглядит, еще до публикации бандла. Во второй вкладке таблицы вы найдете сравнение интерфейсов для UI-кита.

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


    Расскажите в комментариях, используете ли вы в работе UI-киты. Может быть, у вас уже есть лайфхаки по дальнейшей оптимизации процессов дизайна и переходу на микрофронтенды? Приходилось ли вам более глубоко автоматизировать обновление компонентов в библиотеках? Насколько увеличивается размер бандла из-за использования общего UI-кита в вашем случае и удалось ли вам его оптимизировать? Интересно было бы узнать о вашем опыте.

    Учи.ру
    Крупнейший EdTech в школьном образовании

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

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

      При чем тут нейронки?
        0
        Да, наполнение UI-кита можно автоматизировать. Например, с помощью нейронных сетей можно распознавать изображения в Figma и генерировать их верстку. Но останется вопрос с интерактивностью, логику компонент все-равно придется писать.

        Сейчас для наполнения UI-кита нужно минимум два сотрудника: дизайнер, программист. Первый рисует, а второй реализует. Если нейронки позволят полностью закрыть время хотя бы одного сотрудника, то, скорее всего, мы будем смотреть в этом направлении. Но, предварительно будем смотреть на целесообразность, ведь UI-кит конечен, он не будет постоянно расти.
          0
          Нейронки в данном случае ничем не помогут.
            +1
            После прочтения статей типа Turning Design Mockups Into Code With Deep Learning я уже не до конца уверен, что это невозможно.
            В частности, если у вас был опыт этой/смежной области было бы интересно узнать ваше мнение, как бы вы решали подобную задачу и какие технологии выбрали. Заранее спасибо.
              +1
              Тут все зависит от способа привязки, если вы используете API и верстаете, все UI kit компоненты в одном месте тогда не вижу ничего другого как заставить дизайнера называть одинаково блоки, что-бы за них можно было зацепиться, но в любом случае при генерации новых компонентов нужно сначала что-бы первый раз их сверстал человек, а уже потом обновление стилей можно сделать скриптом.

              Это если хочется что-бы в будущем эти компоненты использовали другие люди.
                0
                Спасибо за развернутый комментарий, идея хороша, будем думать.
        +1
        А как именно у вас происходит «отправляется автоматическое уведомление мейнтейнерам, и они обновляют соответствующие элементы библиотеки»?
        Вы пользуетесь у Figma API, или стили в компонентах обновляются вручную?
          +1
          Верно, вы попали в точку. Мы используем Figma API для получения информации с доски, которую преобразуем в нужный формат и подключаем в библиотеку. Далее создаем Pull Request в репозитории с изменениями и подключаем людей к проверке/внесению дополнительных изменений. На стороне Figma мы также версионируем доску, чтобы избежать случайных изменений.

          Думаю реализация будет интересна, поэтому в следующей части я рассмотрю флоу и используемые инструменты подробнее.
          +1

          Привет!


          Подскажите, насколько удобно использовать веб компоненты Stencil в React?


          Например, я могу вложить один компонент Stencil в другой внутри React компонента?
          Или вложить React компонент внутрь Stencil компонента (все внутри React компонента)?

            0

            Я как-то исследовал вопрос создания универсального UI-kit, и не смог подружить даже React с Preact, особенно при серверном рендеринге — слишком мало возможностей использования компонента остаётся, ограничения вложенности, плюс потеря context, и так далее

              +1
              Привет!

              Подскажите, насколько удобно использовать веб компоненты Stencil в React

              Использование веб-компонентов в React похоже на оборачивание React компонента специальным HTML тэгом. Который уже будет отрендерен браузером.

              Stencil расширяет поведение за счет плагинов, предоставляет возможность сделать для веб-компонентов обертку, чтобы работать с каждым как с React компонентом. Это очень удобно, например, если в вашем стеке TypeScript. При подключении компонента из обертки визуально отличить обычный от веб-компонента нельзя :)

              Например, я могу вложить один компонент Stencil в другой внутри React компонента?
              Или вложить React компонент внутрь Stencil компонента (все внутри React компонента)?

              Да, конечно. Например, у нас есть компонент для типографии и для сетки. Если компонент типографии подключается 1 раз в самом вехнем узле, то компонент сетки может быть вложен, например, если нужно сформировать вложенную сетку. А внутри этих компонентов могут быть как обычные React компоненты, так и из UI-кита, например, кнопка.

              Я как-то исследовал вопрос создания универсального UI-kit, и не смог подружить даже React с Preact, особенно при серверном рендеринге — слишком мало возможностей использования компонента остаётся, ограничения вложенности, плюс потеря context, и так далее

              SSR для нас важная фича, но не критичная, так как мы в первую очередь ориентированы на CSR. В целом, у Stencil'а поддержка есть, но нужно будет настраивать.

              По CSR могу сказать, что ограничений вложенности не обнаружили и контекст передается без проблем. Посмотрите на первый сайт в новой парадигме. Все HTML тэги, которые начинаются с dino- это веб-компоненты.
              Конечно есть и то, что сейчас может и не заработать. Например, можно сделать расширение (через extend) компонента из UI-кита в проекте на React, но не факт что будет работать. Мы не делаем extend'ы компонент из UI-кита, чтобы гарантировать, что во всех подключаемых проектах будет внешний вид и поведение таким, как в дизайне. Но, если это важно, этот вопрос можно решить, все зависит от того как сделаете обертку.

              Основное — веб-компоненты позволяют не привязываться к конкретному фреймворку. Что особенно актуально, когда команды используют разные фреймворки. А у нас именно так :) И этот плюс очень сильно перевешивает все минусы.

              На данный момент Stencil, кроме HTML, поддерживает фреймворки: Angular, Vue, React, Ember, есть еще плагин для Svelte (на момент публикации еще не писали о нем в документации на сайте).
                0

                Спасибо за развернутый ответ, похоже стоит продолжить исследовать тему!)

            0
            Контекст у меня терялся, т.к. для React компонентов, которые внутри Preact, приходилось делать отдельный hydrate

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

            На сервере похоже эта проблема останется, т.к. надо отдельно рендерить компоненты через апи stencil/hydrate, и не обойтись без dangerouslySetInnerHtml
              0
              Промазал, это к предыдущему треду(
                +1
                Веб-компоненты с контекстом напрямую не связаны. Нужен какой-то HOC, либо просто компонент, который уже из контекста передаст данные в веб-компонент через атрибуты. Веб-компоненты это же по сути обычный HTML, только подправленный JS. Поэтому в теории должно сработать.

                Мне будет интересно почитать результаты использования Stencil в SSR в вашем примере на практике :)
                  0
                  Приведу пример, когда у `BarComponent` не будет доступа к контексту `Provider` на сервере:

                  const App = () => {
                    return (
                      <Provider>
                        <FooComponent>
                          <stencil-web-component>
                            <BarComponent />
                            <button ... />
                          </stencil-web-component>
                        </FooComponent>
                      </Provider>
                    )
                  }
                  


                  Тут нужно будет отрендерить в строку `stencil-web-component` силами Stencil, затем передать его в `dangerouslySetInnerHTML` родительского тега.
                  На самом деле, тут даже не понятно, как `BarComponent` отрендерить внутри `stencil-web-component`, ведь аналога `dangerouslySetInnerHTML` у Stencil нет)

                  Но пока писал, понял что можно делать немного по другому — делать `ReactDOMServer.renderToString` на все приложение, без модификации веб компонентов, а потом парсить полученную строку, и реплейсить каждый веб компонент через `renderToString` Stencil.

                  Нашел пример с next js, кажется все еще проще — в `stencil.renderToString` просто можно передать строку, которую отрендерил реакт. Прям надо попробовать)
                    0

                    Да, это очень похоже на решение. Надеюсь, результат будет успешным :)

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

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