Как мы еще не перешли с Vue.js на Hotwire

b985c0a6a87ca0c289cfbb5070e50522.png

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

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

  • Admin Area, в которой пользователи могут загружать свой видеоконтент и настраивать веб-сайт.

  • Storefront, представляющий собой сайт, которым пользуются клиенты контент креаторов.

  • Нативные приложения, доступные как на мобильных, так и на ТВ-платформах.

ecebce0456748314634629462a85be5a.jpeg

В вебе наше приложение держится на достаточно популярном стеке — Rails и Vue.js. Кроме того, мы используем специальную библиотеку Inertia.js, которая позволяет писать Vue-компоненты в стиле Rails таким образом, что каждый компонент представляет собой view шаблон страницы, подвязанный к своему экшну в контроллере. При этом переходы между страницами осуществляются без перезагрузки страницы в браузере, как это делает библиотека Turbolinks.

Про Inertia.js у меня есть отдельный доклад.

Uscreen разрабатывался так несколько лет, пока у нас не накопился ряд проблем на фронтенде.

Что было не так с фронтендом:

  • Скопилось достаточно много легаси. Ряд неупорядоченных решений привел к тому, что нам стало сложно развивать фронтенд. В итоге мы застряли на версии Vue 2 и не смогли проапгрейдится до версии Vue 3.

  • Дублирование бизнес-логики и валидаций входных данных.

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

  • Собственный model layer на фронтенде, который еще сильнее усложнил фронтенд.

  • Ну и основной недостаток, который нас не устраивал, — наш фронтенд не подходил к Rails Way! Мы любим то, что предлагает Rails сообщество вместе с DHH и ценим практики компании Basecamp, поэтому стараемся не сильно отклоняться от стандартного Rails Way подхода. 

В итоге мы решили переехать.

Hotwire

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

Turbo

В Turbo мы разбиваем HTML страницы на независимые контексты — фреймы (turbo frames). Турбо-фреймы — части HTML документа, которые в дальнейшем будут асинхронно видоизменяться.

bd9e9dd4d0215682807fe42cbe1e49df.jpeg

При отправке формы или при переходе по ссылкам в турбо-фреймах мы инициируем запрос на сервер со специальным хедером:

Content-type: text/vnd.turbo-stream.html

Распарсив такой запрос, сервер вернет на клиент специальный HTML-подобный документ с описанием команд (turbo streams). В турбо стриме может описываться то, как следует поступить с тем или иным турбо фреймом. Например, мы можем заменить один турбо фрейм абсолютно новым HTML контентом, удалить его из DOM-дерева или добавить в его конец новый HTML-фрагмент.

4bd5aa50a247b3ef915fabafdf59617a.jpeg

Stimulus

Библиотека Stimulus появилась как попытка упорядочить различные подходы к написанию JavaScript в компании Basecamp и вынести их в специальные классы-контроллеры.

Контроллеры — JS-вкрапления с небольшой динамикой для страниц.

e0ee67773b4b8ebd1d8a6bd898c9607b.jpeg

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

80da6099fc6b20eabbe2adeb8a0894d4.jpeg

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

3cfd3b5cf8af25f8223917163991d092.jpeg

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

  • Работа с Hotwire это 90% Ruby + erb, что упрощает разработку для Rails-инженеров.

  • Несложная концепция библиотек Turbo и Stimulus. Нам больше не нужно погружаться в такие понятия как виртуальный DOM, обработку вложенных vue событий, осуществление API запросов и т. д. Мы отправляем на клиент уже готовые куски HTML.

  • Проект достаточно активно развивается сообществом. В интернете уже опубликовано достаточно много докладов и статей, а также появляются первые best practices.

  • Для разработки зачастую требуется буквально один разработчик.

Все это приближает нас к концепции David Heinemeier Hansson, которую он описал в конце 2021 года — The One Person Framework, согласно которой Rails 7 в связке с Hotwire возвращает Rails былое величие, и для разработки современных веб-приложений нужен один разработчик.

682e0bc5caf6e777fa8cc88be623a8c0.jpeg

Если Rails-разработчики будут все делать сами, что же тогда делать фронтендерам? Фронтендеры могут:

  • Создавать web component-ы.

  • Работать над сложными интерфейсами, сильно завязанными на js (в нашем случае это Page Builder, Video Player, Live Chat).

  • Оборачивать легаси Vue-компоненты в Stimulus.

  • Консультировать и помогать Rails-инженерам.

Web components

Web Components — технология, позволяющая инкапсулировать в HTML-теги некую верстку, стили и JavaScript. При этом их теги выглядят как обычные HTML-теги.

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

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

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

Резюмируем наш план переезда:

  1. Постранично переписываем наши .vue-шаблоны на .erb.

  2. Динамические части HTML-страниц выносим в турбо-фреймы и используем Turbo для коммуникации с сервером.

  3. Для легкой динамики на странице используем Stimulus.

  4. Используем webcomponents для повторяющихся элементов UI.

  5. Сложные Vue-компоненты, с которых не удается быстро переехать, оборачиваем в Stimulus-контроллеры.

  6. Повторяем процесс до тех пор, пока не закончим.

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

Переход с Vue на Turbo на примере лайка поста

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

Пример поста на Vue.jsПример поста на Vue.js

По сути, это дерево компонентов, среди которого нас интересует компонент . Во vue-коде это кнопка, к которой привязан некий обработчик. При вызове обработчика вызывается событие like, которое в дальнейшем будет обрабатываться в родителе.

fb215b16f298536397b3f95a1a5f43b5.jpeg

Родитель вызовет метод like на модели поста и инициирует запрос на сервер. Сервер обработает запрос, вернет в компонент некоторый JSON, компонент изменит свое состояние, т. е. выполнится стандартный флоу, знакомый для всех кто работал с Vue.js.

f9023aca58d5110580ceb5e47e3139e6.jpeg

Теперь разберемся, как то же самое будет работать через Hotwire.

c1966a8529fefb596257d7f849692515.jpeg

Мы разделяем страницу поста на старые добрые erb partial-ы. Тут нас интересует _post_controls.html.erb. В нем указан турбо фрейм, в котором лежит форма с кнопкой.

ac1064e8e6024b0d54ca74c7f9674ba8.jpeg

Здесь важным является параметр data: { turbo: true }. Благодаря этому параметру Turbo понимает, что на данный запрос вернется турбо-стрим, который будет необходимо асинхронно обработать и изменить контент в соответствующем турбо-фрейме.

В контроллере мы явно вернем турбо стрим. Для этого мы вызовем на объекте turbo_stream метод update в который передадим название турбо-фрейма и то, каким partial-ом его следует обновить. В итоге на клиент вернется документ с указанным турбо стримом.

c1ee1b798870254736bdd2cc13f7fd0c.jpeg

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

23c6d1dd0cbd38cf42c9d6bb4467a330.jpeg

Пример на Stimulus

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

3a20b520077cc75af2fb2b1ef6a3f4ed.jpeg

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

  • имя контроллера — autogrow-textarea,

  • target, который связывает контроллер с текущим HTML-элементом (в данном случае — textarea),

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

dbf7a0ebab38209579ac2cb430476b18.jpeg

Контроллер выглядит достаточно просто и содержит всего два метода:

  • стандартный метод connect в Stimulus, который вызывается в момент подсоединения контроллера к HTML-странице. В данном случае мы указываем первоначальные стилистические атрибуты для нашего элемента.

  • метод resize, который будет указывать на textarea величину, соответствующую зоне скролла.

d8d1ae66eca9aa5d06faeefe0607a8fb.jpeg

Обертка Vue в Stimulus

В нашем проекте есть достаточно сложный Live Chat, который пока не удается переписать в веб-компонент. Поэтому мы оборачиваем его в Stimulus. Упрощенный вид контроллера выглядит следующим образом:

b4c4baad1a9513c4ccbfebb1fbd44449.jpeg

Во-первых, в методе connect мы загружаем необходимые нам JavaScript-модули и передаем их в метод render. В методе render мы создаем новый Vue-компонент и «подвязываем» его к некоему root-элементу. Этот root-элемент хранится в переменной currentTarget.

ba65e9a49465f0e6ee946d6ba5aea68d.jpeg

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

df39eaee2fae5350d3a1c3bfe4927c8f.jpeg

Подведем промежуточные итоги нашего переезда.

  • Наблюдать за динамикой происходящего на фронтенде для Rails-инженеров стало проще, потому что больше не нужно понимать, как устроен виртуальный DOM, обрабатывать хитрые события, разбираться в сложной структуре веб-компонентов и API запросов. Мы возвращаемся к старой доброй HTML спеке: отправляем запросы через формы или ссылки и оперируем готовыми кусками HTML.

  • Web Components — технология, которая упрощает жизнь Rails-разработчикам и ускоряет разработку фронтенда. По сути, в нашем распоряжении готовые строительные блоки, которые можно напрямую указывать в наших erb-partial-ах.

  • Зачастую фичу (и фронтенд, и бэкенд) может разработать один человек благодаря Hotwire.

  • Несмотря на простоту Turbo + Stimulus, они требуют нового образа мышления и понимания того, как следует проектировать фичи, как делить страницу на турбо-фреймы и в какой момент их загружать (лениво или сразу). Кроме того, необходимо понимать, каким образом выносить в Stimulus контроллеры то или иное поведение.

  • За простотой использования Web Components также скрывается сложность самой технологии, которая требует от фронтенд-разработчиков времени на погружение и проектирование подходящего API. Разработчикам нужно уметь проектировать хороший интерфейс таких веб-компонентов, чтобы их можно было переиспользовать в рамках всего проекта.

  • Rails-инженерам приходится сильнее углубляться в HTML и CSS, что требует определенных ресурсов.

© Habrahabr.ru