Pro Yearly is on sale from $80 to $50! »

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

52cc8a838bf44a589d2572833b2dd1b9?s=47 Vlad Dem
November 13, 2020

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

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

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

52cc8a838bf44a589d2572833b2dd1b9?s=128

Vlad Dem

November 13, 2020
Tweet

Transcript

  1. Фронтенд без фронтенда Владимир Дементьев

  2. palkan_tula palkan github.com/palkan 2

  3. palkan_tula palkan Разработка сегодня 3 Это я

  4. palkan_tula palkan Ты помнишь, как всё было десять лет назад

    4 Full-stack developer Это тоже я
  5. palkan_tula palkan 5 Full-stack разработка на Ruby (on Rails) в

    202
  6. palkan_tula palkan Full-stack ruby? Saint P Ruby Community @ Telegram

    6
  7. palkan_tula palkan 7 Одного программиста достаточно, чтобы вкрутить лампочку Full-stack

    — это ...
  8. palkan_tula palkan 8 Разработка в рамках единой экосистемы Full-stack —

    это ...
  9. Немного истории А вот в наше время...

  10. palkan_tula palkan Full-stack Rails 10 HTML-over-the-Wire

  11. HTML (Haml/Slim) Helpers

  12. palkan_tula palkan Helpers 12 https://github.com/redmine/redmine

  13. HTML (Haml/Slim) CoffeeScript jquery Helpers jquery-ujs

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

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

    Bootstrap Bundler (asset gems) vendor/assets
  16. palkan_tula palkan Перемены на фронтé 16

  17. 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
  18. palkan_tula palkan Эволюция фронтенда Приводит к разделению разработчиков на бекенд

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

    из зоны комфорта Ruby и Rails
  20. palkan_tula palkan 20 Живая «классика»

  21. palkan_tula palkan 21 Неоклассицизм

  22. palkan_tula palkan hey.com 22

  23. palkan_tula palkan 23 Client-side rendering Server-side rendering

  24. palkan_tula palkan 24 Client-side rendering Server-side rendering SPA Turbolinks

  25. palkan_tula palkan Turbolinks SPA для бедных HTML приложений Перехватывает переходы

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

    Interactivity JS framework SPA Turbolinks JS sprinkles
  27. palkan_tula palkan Architecture Reactivity rails-ujs Stimulus 27 Client-side rendering Server-side

    rendering Interactivity JS framework JS sprinkles SPA Turbolinks
  28. palkan_tula palkan Stimulus 28 stimulusjs.org

  29. palkan_tula palkan Пример 29 Скрываемые нотификации

  30. 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);
  31. 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(); } }
  32. palkan_tula palkan Stimulus: сильные стороны Автоматическая активация стимулов (MutationObserver API)

    Turbolinks работает без лишних усилий 32
  33. palkan_tula palkan Stimulus Позволяет делать из статичного HTML элемента компонент

    ...но писать его придётся ручками ...или нет 33
  34. palkan_tula palkan 34 Пример Интерактивная форма (Stimulus + Vue)

  35. 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 Пример
  36. 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(); } } } Пример
  37. palkan_tula palkan Stimulus + Vue 37 github.com/gretchenfitze/stimulus-turbolinks

  38. palkan_tula palkan Больше примеров stimulusconnect.com betterstimulus.com github.com/stimulus-use/stimulus-use 38

  39. Ты же говорил, что мы не будем писать на JavaScript!

  40. 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
  41. palkan_tula palkan Phoenix LiveView 41

  42. palkan_tula palkan Phoenix LiveView HTML-компонент «присоединяется» к процессу на сервере

    (через сокет) Процесс реагирует на сообщения из браузера или других процессов, вычисляет изменившиеся части шаблона и отправляет их клиенту Клиент использует morphdom для изменения DOM 42
  43. palkan_tula palkan “A new way to craft modern, reactive web

    interfaces with Ruby on Rails.” 43
  44. palkan_tula palkan 44 автор Stimulus Reflex CableReady

  45. palkan_tula palkan CableReady Библиотека для изменения DOM с сервера Использует

    для транспорта Action Cable broadcast Использует morphdom на клиенте 45
  46. 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>
  47. 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
  48. palkan_tula palkan CableReady 48

  49. palkan_tula palkan StimulusReflex Рефлексы (объекты) реагируют на действия пользователя и

    рендерят HTML CableReady отправляет HTML клиенту и обновляет DOM 49
  50. None
  51. palkan_tula palkan Пример 51

  52. 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>
  53. 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
  54. 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-сообщение текущему пользователю Объект-представление текущего элемента
  55. 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
  56. palkan_tula palkan Больше рефлексии futurism — асинхронная загрузка элементов страницы

    optimism — серверная валидация форм в реальном времени 56
  57. palkan_tula palkan И немного альтернативы 57 github.com/unabridged/motion

  58. palkan_tula palkan SR: сильные стороны Стабильный проект (v3.4) Одна из

    лучших документаций в мире OSS Активное сообщество в сети (Discord >700 участников, многочисленные посты и видео) 58
  59. palkan_tula palkan Reactive Rails! medium.com/@obie/react-is-dead-long-live-reactive-rails-long-live-stimulusreflex-and-viewcomponent-cd061e2b0fe2 59

  60. None
  61. palkan_tula palkan SR: слабые стороны Action Cable Большой объём данных

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

    Как нам его организовать?
  63. 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
  64. palkan_tula palkan evilmartians.com/blog evilmartians.com/chronicles/evil-front-part-1 64

  65. palkan_tula palkan 65 Компонентный подход

  66. 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
  67. palkan_tula palkan View Components 67 partials decorators helpers facades presenters

    builders view components
  68. palkan_tula palkan View Component 68 "Ruby objects that output HTML"

    View Model (класс) + шаблон Изолированность, тестируемость, переиспользование Made by GitHub github.com/github/view_component
  69. 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
  70. palkan_tula palkan Пример 70 # app/components/button/component.html.erb <button class="btn"> <% if

    icon? %> <i><%= icon %> </i> <% end %> <% == label %> </button>
  71. palkan_tula palkan Пример 71 # some.html.erb <div class="container"> <%= render

    Button ::Component.new( label: "Like", icon: "❤") %> </div>
  72. 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>
  73. 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
  74. Слыхал, Ник, GitHub переизобрели Cells Долго они

  75. palkan_tula palkan View Component Тесная интеграция с Rails (Rails way)

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

    component.html.slim component.scss component.js Весь компонент в одной папке
  77. palkan_tula palkan Другие компоненты Cells hanami-view dry-view komponent elemental_components 77

  78. palkan_tula palkan 78 Client-side rendering Server-side rendering JS framework SPA

    Turbolinks JS sprinkles HTML-over-WebSocket View Components CSS-in-JS
  79. palkan_tula palkan CSS 2020 Bulma (+Buefy) Tailwind Shoelace 79

  80. palkan_tula palkan Mobile-first CSS-only Модульность и кастомизация (Sass- переменные) Часто

    используется в связке с Vue компонентами (Buefy) 80
  81. palkan_tula palkan Utility-first (никаких компонентов, только набор классов) Удобный механизм

    выделения компонентов Быстрое прототипирование (+playground) Встроенные оптимизации сборки 81
  82. palkan_tula palkan 82

  83. palkan_tula palkan Web Components Продуманная кастомизация Accessibility 83

  84. palkan_tula palkan 84

  85. palkan_tula palkan Что выбрать? Bulma — для админок и особенно,

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

    (Turbolinks) Компонентный подход (ViewComponent, komponent, etc.) Гибкий CSS-фреймворк (Tailwind, Shoelace, Bulma) 86 Full stack
  87. Итоги Есть ли фронтенд без фронтенда?

  88. palkan_tula palkan Фронтенд без фронтенда Способен заменить JS фреймворки/ SPA

    для приложений с нехитровыдуманным UI Повышает продуктивность (но всё же имеет порог вхождения) Новая эра только начинается, всё лучшее — впереди! 88
  89. Cпасибо! @palkan @palkan_tula evilmartians.com @evilmartians