Фронтенд на рельсах (почти) без JS

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

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

То, что есть из коробки: rails-ujs, turbolinks

Rails UJS

Давным давно, когда я только пытался сверстать свою первую HTML страничку, у Rails уже был крутой инструмент jquery-ujs (unobtrusive javascript), который теперь называется rails-ujs. Он отлично работает с рельсовым бэкендом, когда вам нужно добавить парочку AJAX запросов малой ценой.

Можете попробовать сделать что-то вроде этого:

app/controllers/money_controller.rb

class MoneyController < ApplicationController
  def show
    @money = GetAllMoney.call
  end

  def destroy
    SpendAllMoney.call
  end
end

views/money/show.html.erb

Your money

<%= @money %> $ <%= link_to 'Spend all money', money_path, method: 'delete', remote: true, data: { confirm: 'Do you want to spend all money?' }, class: 'spend-money-button' %>

views/money/destroy.js

document.querySelector('#money-amount').innerHTML = 0

Новый сайт со ставками на спорт. Лучшие коэффициенты xDНовый сайт со ставками на спорт. Лучшие коэффициенты xD

Итак, вы сделали AJAX запрос, используя всего несколько HTML атрибутов и один JS файл с одной строчкой кода. Круто, правда?

Turbolinks

Еще один старожил в мире Rails — Turbolinks. Эта библиотека не находится в стадии активной разработки, но о ее преемнике мы поговорим немного позже. В двух словах, Turbolinks приносит вам SPA опыт почти без клиентского кода. Если подробно, то эта библиотека:

  • загружает содержимое новых страниц с помощью JS и заменяет его на странице без перезагрузки браузера;  

  • она кэширует страницы, чтобы повторные посещения казались мгновенными;

  • позволяет сохранять элементы на странице неизменными во время навигации.

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

app/helpers/application_helper.rb

module ApplicationHelper
  def notifications_count
    sleep 3 # emulate some calculations

    10
  end

  def articles
    Article.last(5)
  end
end

app/views/layouts/application.html.erb




  Turbolinks
  
  <%= csrf_meta_tags %>
  <%= csp_meta_tag %>

  <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
  <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>



<%= yield %>

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

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

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

Таким образом, мы можем просто не выполнять код, если страница запрашивалась Turbolinks и и запретить Turbolinks обновлять часть страницы. Вот как это выглядит:

app/helpers/application_helper.rb

module ApplicationHelper
  def notifications_count
+   return nil if request.headers['Turbolinks-Referrer'].present?
+
    sleep 3 # emulate some calculations

    10
  end

  def articles
    Article.last(5)
  end
end

app/views/layouts/application.html.erb

-
+
<%= notifications_count %>

Чего для нас не достаточно

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

Новые инструменты от команды Rails

В начале 2021 года DHH объявил о появлении альтернативного подхода Hotwire, нового способа Rails для построения пользовательских интерфейсов. Несмотря на то, что Hotwire является собирательным названием для семейства библиотек, эта семья довольно мала. По состоянию на октябрь 2021 года было всего две библиотеки: Turbo и Stimulus.

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

Turbo

Если вы думали, что Turbolinks потеряли свою часть «links», потому что это теперь больше чем навигация, вы на 100% правы. Библиотека Turbo разделена на несколько частей, где каждая служит единой цели — доставить в ваше приложение HTML, отрисованный на сервере, с разницей в том, когда и как это делается:

  • Turbo Drive — тот старый добрый Turbolinks, с которым мы знакомы. 

  • Turbo Frames — «отдельные» фреймы, которые могут быть загружены асинхронно и обновлены, когда сервер возвращает фрейм с тем же id. 

  • Turbo Streams — другой тип фреймов, который обновляется в результате HTTP запроса или с помощью сервера через Websocket. 

  • Turbo Native — обёртка вашего «турбированного» веб-приложения, которая интегрирует его в мобильное приложение.

Итак, теперь обо всем по порядку.

Turbo Drive

Как упоминалось ранее, Turbo Drive просто заменяет Turbolinks и берет на себя навигацию между страницами. Поскольку почти ничего не изменилось, миграция довольно проста.

Вам нужно просто добавить пакет npm

yarn add @hotwired/turbo

Заменить Turbolinks на Turbo в вашем javascript коде

app/javascript/packs/application.js

  import Rails from "@rails/ujs"
- import Turbolinks from "turbolinks"
+ import * as Turbo from "@hotwired/turbo"
 
  Rails.start()
- Turbolinks.start()

Зменить data-turbolinks... атрибуты с data-turbo…

app/views/layouts/application.html.erb


-    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
-    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
+    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbo-track': 'reload' %>
+    <%= javascript_pack_tag 'application', 'data-turbo-track': 'reload' %>

Важный момент, на который нужно обратить внимание —  заполнение формы. Turbo drive берет на себя и это. Прежде всего, он ожидает, что redirect после отправки формы будет со статусом 303, чтобы позволить Fetch API автоматически следовать за редиректом. Это правильный статус НТТР для неиндемпотентных (умное слово для описания HTTP методов помимо GET и HEAD

© Habrahabr.ru