Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[RubyRussia 2020] Фронтенд без фронтенда

[RubyRussia 2020] Фронтенд без фронтенда

Всё в мире циклично, и веб-разработка не является исключением: десять лет назад мы наслаждались разработкой Rails приложений с HTML формами и jquery-ujs для интерактива — нашей продуктивности не было предела! Постепенно интерфейсы становились всё хитроумнее, «классический» подход начал уступать место фронтенд-фреймворкам, задвигая Ruby на роль поставщика API.
В последние годы ситуация начала меняться в обратную сторону, и в 2020-м практически каждый хотя бы краем уха слышал о ViewComponent, StimulusReflex или других представителях «новой волны».

В докладе я бы хотел сделать обзор современных «фронтенд-технологий» из мира Rails с примерами из личной (и не только) практики.

Vladimir Dementyev

November 13, 2020
Tweet

More Decks by Vladimir Dementyev

Other Decks in Programming

Transcript

  1. palkan_tula palkan jquery-ujs 14 # show.html.slim = link_to "Delete", post_path(post),

    remote: true # destroy.js.erb $("#<%= dom_id(post) %>").remove();
  2. HTML (Haml/Slim) Asset Pipeline CoffeeScript jquery Helpers jquery-ujs Turbolinks Sass

    Bootstrap Bundler (asset gems) vendor/assets npm / yarn ES6 Webpack PostCSS React SPA API
  3. palkan_tula palkan Эволюция фронтенда Приводит к разделению разработчиков на бекенд

    и фронтенд «группировки» Вытесняет Ruby/Rails на роль поставщика API Увеличивает стоимость разработки и поддержки* 18 * Примечание: мнение автора может не совпадать, а может и совпадать
  4. palkan_tula palkan Turbolinks SPA для бедных HTML приложений Перехватывает переходы

    по ссылкам, запрашивает страницы асинхронно (AJAX), заменяет HTML (только body) Использует собственный кэш для создания ощущения мгновенных переходов 25
  5. palkan_tula palkan Architecture Reactivity rails-ujs Stimulus 27 Client-side rendering Server-side

    rendering Interactivity JS framework JS sprinkles SPA Turbolinks
  6. palkan_tula palkan Пример: jQuery 30 function initBannerClose(){
 // Кажется, у

    нас CSS «утёк» $('.banner --close').click(function(e){ e.preventDefault(); const banner = $(this).parent(); banner.remove(); }); }); $(document).on('load', initBannerClose); // Не забываем про Turbolinks $(document).on('turbolinks:load', initBannerClose); // ...и jquery-ujs $(document).on('ajax:success', initBannerClose);
  7. palkan_tula palkan Пример: Stimulus 31 <div data-controller="banner"> <svg data-action="click ->banner#hide">

    </svg> <p>AnyWork ... </p> </div> import { Controller } from "stimulus"; export class BannerController extends Controller { hide() { this.element.remove(); } }
  8. palkan_tula palkan 35 = form_for @form do |f| // ...

    .field label.label Started at .control data-controller="datetimepicker" data-tz="-04:00" = f.text_field :started_at, required: true .field label.label Location .control data-controller="location-input" = f.text_field :location, placeholder: "Enter event location ..." .field label.switch = f.check_box :waitlist_enabled span.check span.control-label.label Waitlist enabled .field label.label Cover Image .control data-controller="upload" data-url=blob_path(f.model.cover) = f.file_field :cover Пример
  9. palkan_tula palkan 36 export class VueInputController extends Controller { connect()

    { var el = this.element; this.vue = new Vue( { el, data() { // Все data-атрибуты станут data.props Vue-компонента return { props: el.dataset }; }, // Вместо самого элемента будет контент компонента template: '<input class="my-input" v-bind="props" />' } ); } disconnect() { if (this.vue) { this.vue.$destroy(); } } } Пример
  10. palkan_tula palkan Architecture Reactivity rails-ujs Stimulus 40 Client-side rendering Server-side

    rendering Interactivity JS framework SPA Turbolinks JS sprinkles HTML-over-WebSocket
  11. palkan_tula palkan Phoenix LiveView HTML-компонент «присоединяется» к процессу на сервере

    (через сокет) Процесс реагирует на сообщения из браузера или других процессов, вычисляет изменившиеся части шаблона и отправляет их клиенту Клиент использует morphdom для изменения DOM 42
  12. palkan_tula palkan CableReady Библиотека для изменения DOM с сервера Использует

    для транспорта Action Cable broadcast Использует morphdom на клиенте 45
  13. palkan_tula palkan Пример 46 <!-- _item.html.erb --> <div id="<%= dom_id(item)

    %>"> ... <%= button_to item_path(item), method: :delete, remote: true do %> <svg> ... </svg> <% end %> </div>
  14. palkan_tula palkan Пример 47 # items_controller.rb def destroy item.destroy! stream

    = ListChannel.broadcasting_for(item.list) cable_ready[stream].remove(selector: dom_id(item)) head :no_content end
  15. palkan_tula palkan StimulusReflex Рефлексы (объекты) реагируют на действия пользователя и

    рендерят HTML CableReady отправляет HTML клиенту и обновляет DOM 49
  16. palkan_tula palkan Пример 52 <!-- _item.html.erb --> <div id="<%= dom_id(item)

    %>"> <label class="any-check mr-4"> <input type="checkbox" class="hidden" <%= item.completed? ? "checked" : "" %> data-reflex="change ->List#toggle_item_completion" data-item-id="<%= item.id %> > <svg> ... </svg> </label> <p><%= item.desc %> </p> <button data-reflex="click ->List#destroy_item" data-item-id="<%= item.id %>"> <svg> ... </svg> </button> </div>
  17. palkan_tula palkan Пример 53 class ListReflex < ApplicationReflex def toggle_item_completion

    item = find_item item.toggle!(:completed) html = render_partial("items/item", {item}) selector = dom_id(item) cable_ready[ ListChannel.broadcasting_for(item.list) ].outer_html( **{selector, html}) cable_ready.broadcast morph_flash :notice, "Item has been updated" end private def find_item Item.find element.dataset["item-id"] end end
  18. palkan_tula palkan Пример 54 class ListReflex < ApplicationReflex def toggle_item_completion

    item = find_item item.toggle!(:completed) html = render_partial("items/item", {item}) selector = dom_id(item) cable_ready[ ListChannel.broadcasting_for(item.list) ].outer_html( **{selector, html}) cable_ready.broadcast morph_flash :notice, "Item has been updated" end private def find_item Item.find element.dataset["item-id"] end end Изменяем DOM элемента всем подключенным пользователям (и себе) Показываем flash-сообщение текущему пользователю Объект-представление текущего элемента
  19. palkan_tula palkan Пример 55 class ApplicationReflex < StimulusReflex ::Reflex private

    def morph_flash(type, message) morph "#flash", render_partial( "shared/alerts", {flash: {type => message}} ) end end
  20. palkan_tula palkan Больше рефлексии futurism — асинхронная загрузка элементов страницы

    optimism — серверная валидация форм в реальном времени 56
  21. palkan_tula palkan SR: сильные стороны Стабильный проект (v3.4) Одна из

    лучших документаций в мире OSS Активное сообщество в сети (Discord >700 участников, многочисленные посты и видео) 58
  22. palkan_tula palkan SR: слабые стороны Action Cable Большой объём данных

    по сети (нет механизма диффов как в LiveView) Данные всегда идут через pub/sub (даже если нужно отправить клиенту- инициатору) 61 AnyCable
  23. palkan_tula palkan 62 Весь наш UI — это просто HTML.

    Как нам его организовать?
  24. palkan_tula palkan Architecture Reactivity rails-ujs Stimulus 63 Client-side rendering Server-side

    rendering Interactivity JS framework SPA Turbolinks JS sprinkles HTML-over-WebSocket
  25. palkan_tula palkan Architecture Reactivity rails-ujs Stimulus 66 Client-side rendering Server-side

    rendering Interactivity JS framework SPA Turbolinks JS sprinkles HTML-over-WebSocket View Components
  26. palkan_tula palkan View Component 68 "Ruby objects that output HTML"

    View Model (класс) + шаблон Изолированность, тестируемость, переиспользование Made by GitHub github.com/github/view_component
  27. palkan_tula palkan Пример 69 # app/components/button/component.rb class Button ::Component <

    ViewComponent ::Base attr_reader :label, :icon def initialize(label:, icon: nil) @label = label @icon = icon end alias icon? icon end
  28. palkan_tula palkan Пример 71 # some.html.erb <div class="container"> <%= render

    Button ::Component.new( label: "Like", icon: "❤") %> </div>
  29. palkan_tula palkan Пример 72 # app/components/like_button.rb class LikeButton < Button

    ::Component def initialize super(label: I18n.t("like"), icon: "❤") end end # some.html.erb <div class="container"> <%= render LikeButton.new %> </div>
  30. palkan_tula palkan Пример 73 # test/components/button_test.rb class Button ::ComponentTest <

    ActiveSupport ::TestCase include ViewComponent ::TestHelpers def test_render render_inline Button ::Component.new(label: "Test") assert_selector "button.btn", text: "Test" assert_no_selector "button.btn i" end def test_render_with_icon render_inline Button ::Component.new( label: "Test", icon: "✔") assert_selector "button.btn", text: "Test" assert_selector "button.btn i", text: "✔" end end
  31. palkan_tula palkan View Component Тесная интеграция с Rails (Rails way)

    Скорость рендеринга (до 10x быстрее partials) Функционал превью 75
  32. palkan_tula palkan View Component 76 app/ frontend/ components/ banner/ component.rb

    component.html.slim component.scss component.js Весь компонент в одной папке
  33. palkan_tula palkan 78 Client-side rendering Server-side rendering JS framework SPA

    Turbolinks JS sprinkles HTML-over-WebSocket View Components CSS-in-JS
  34. palkan_tula palkan Utility-first (никаких компонентов, только набор классов) Удобный механизм

    выделения компонентов Быстрое прототипирование (+playground) Встроенные оптимизации сборки 81
  35. palkan_tula palkan Что выбрать? Bulma — для админок и особенно,

    если есть Vue Shoelace — CRUD, админки Tailwind — для пользовательских интерфейсов 85
  36. palkan_tula palkan HTML-over-WebSocket (StimulusReflex, etc.) JS sprinkles (Stimulus) Fake SPA

    (Turbolinks) Компонентный подход (ViewComponent, komponent, etc.) Гибкий CSS-фреймворк (Tailwind, Shoelace, Bulma) 86 Full stack
  37. palkan_tula palkan Фронтенд без фронтенда Способен заменить JS фреймворки/ SPA

    для приложений с нехитровыдуманным UI Повышает продуктивность (но всё же имеет порог вхождения) Новая эра только начинается, всё лучшее — впереди! 88