$30 off During Our Annual Pro Sale. View Details »

[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. Фронтенд
    без
    фронтенда
    Владимир Дементьев

    View Slide

  2. palkan_tula
    palkan
    github.com/palkan
    2

    View Slide

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

    View Slide

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

    View Slide

  5. palkan_tula
    palkan
    5
    Full-stack разработка на
    Ruby (on Rails) в 202

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  11. HTML (Haml/Slim)
    Helpers

    View Slide

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

    View Slide

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

    View Slide

  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();

    View Slide

  15. HTML (Haml/Slim)
    Asset Pipeline
    CoffeeScript
    jquery
    Helpers
    jquery-ujs
    Turbolinks
    Sass
    Bootstrap
    Bundler (asset gems)
    vendor/assets

    View Slide

  16. palkan_tula
    palkan
    Перемены
    на фронтé
    16

    View Slide

  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

    View Slide

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

    View Slide

  19. palkan_tula
    palkan
    19
    Возможна ли разработка
    современных веб-приложений,
    не выходя из зоны комфорта
    Ruby и Rails

    View Slide

  20. palkan_tula
    palkan
    20
    Живая «классика»

    View Slide

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

    View Slide

  22. palkan_tula
    palkan
    hey.com
    22

    View Slide

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

    View Slide

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

    View Slide

  25. palkan_tula
    palkan
    Turbolinks
    SPA для бедных HTML приложений
    Перехватывает переходы по ссылкам,
    запрашивает страницы асинхронно
    (AJAX), заменяет HTML (только body)
    Использует собственный кэш для
    создания ощущения мгновенных
    переходов
    25

    View Slide

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

    View Slide

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

    View Slide

  28. palkan_tula
    palkan
    Stimulus
    28
    stimulusjs.org

    View Slide

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

    View Slide

  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);

    View Slide

  31. palkan_tula
    palkan
    Пример: Stimulus
    31


    AnyWork ...

    import { Controller } from "stimulus";
    export class BannerController extends Controller {
    hide() {
    this.element.remove();
    }
    }

    View Slide

  32. palkan_tula
    palkan
    Stimulus: сильные стороны
    Автоматическая активация
    стимулов (MutationObserver API)
    Turbolinks работает без лишних
    усилий
    32

    View Slide

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

    View Slide

  34. palkan_tula
    palkan
    34
    Пример
    Интерактивная форма (Stimulus + Vue)

    View Slide

  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
    Пример

    View Slide

  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: ''
    }
    );
    }
    disconnect() {
    if (this.vue) {
    this.vue.$destroy();
    }
    }
    }
    Пример

    View Slide

  37. palkan_tula
    palkan
    Stimulus + Vue
    37
    github.com/gretchenfitze/stimulus-turbolinks

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  41. palkan_tula
    palkan
    Phoenix LiveView
    41

    View Slide

  42. palkan_tula
    palkan
    Phoenix LiveView
    HTML-компонент «присоединяется» к процессу
    на сервере (через сокет)
    Процесс реагирует на сообщения из браузера
    или других процессов, вычисляет изменившиеся
    части шаблона и отправляет их клиенту
    Клиент использует morphdom для изменения
    DOM
    42

    View Slide

  43. palkan_tula
    palkan
    “A new way to craft modern,
    reactive web interfaces with
    Ruby on Rails.”
    43

    View Slide

  44. palkan_tula
    palkan
    44
    автор Stimulus Reflex
    CableReady

    View Slide

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

    View Slide

  46. palkan_tula
    palkan
    Пример
    46


    ...
    <%= button_to item_path(item),
    method: :delete,
    remote: true do %>
    ...
    <% end %>

    View Slide

  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

    View Slide

  48. palkan_tula
    palkan
    CableReady
    48

    View Slide

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

    View Slide

  50. View Slide

  51. palkan_tula
    palkan
    Пример
    51

    View Slide

  52. palkan_tula
    palkan
    Пример
    52



    <%= item.completed? ? "checked" : "" %>
    data-reflex="change ->List#toggle_item_completion"
    data-item-id="<%= item.id %>
    >
    ...

    <%= item.desc %>
    data-item-id="<%= item.id %>">
    ...


    View Slide

  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

    View Slide

  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-сообщение
    текущему пользователю
    Объект-представление текущего
    элемента

    View Slide

  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

    View Slide

  56. palkan_tula
    palkan
    Больше рефлексии
    futurism — асинхронная загрузка
    элементов страницы
    optimism — серверная валидация
    форм в реальном времени
    56

    View Slide

  57. palkan_tula
    palkan
    И немного альтернативы
    57
    github.com/unabridged/motion

    View Slide

  58. palkan_tula
    palkan
    SR: сильные стороны
    Стабильный проект (v3.4)
    Одна из лучших документаций в
    мире OSS
    Активное сообщество в сети
    (Discord >700 участников,
    многочисленные посты и видео)
    58

    View Slide

  59. palkan_tula
    palkan
    Reactive Rails!
    medium.com/@obie/react-is-dead-long-live-reactive-rails-long-live-stimulusreflex-and-viewcomponent-cd061e2b0fe2
    59

    View Slide

  60. View Slide

  61. palkan_tula
    palkan
    SR: слабые стороны
    Action Cable
    Большой объём данных по сети (нет
    механизма диффов как в LiveView)
    Данные всегда идут через pub/sub
    (даже если нужно отправить клиенту-
    инициатору)
    61
    AnyCable

    View Slide

  62. palkan_tula
    palkan
    62
    Весь наш UI — это просто HTML.
    Как нам его организовать?

    View Slide

  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

    View Slide

  64. palkan_tula
    palkan
    evilmartians.com/blog
    evilmartians.com/chronicles/evil-front-part-1
    64

    View Slide

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

    View Slide

  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

    View Slide

  67. palkan_tula
    palkan
    View Components
    67
    partials
    decorators
    helpers
    facades
    presenters
    builders
    view components

    View Slide

  68. palkan_tula
    palkan
    View Component
    68
    "Ruby objects that output HTML"
    View Model (класс) + шаблон
    Изолированность, тестируемость,
    переиспользование
    Made by GitHub
    github.com/github/view_component

    View Slide

  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

    View Slide

  70. palkan_tula
    palkan
    Пример
    70
    # app/components/button/component.html.erb

    <% if icon? %>
    <%= icon %>
    <% end %>
    <% == label %>

    View Slide

  71. palkan_tula
    palkan
    Пример
    71
    # some.html.erb

    <%= render Button ::Component.new(
    label: "Like", icon: "❤") %>

    View Slide

  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

    <%= render LikeButton.new %>

    View Slide

  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

    View Slide

  74. Слыхал, Ник, GitHub
    переизобрели Cells
    Долго они

    View Slide

  75. palkan_tula
    palkan
    View Component
    Тесная интеграция с Rails
    (Rails way)
    Скорость рендеринга (до 10x
    быстрее partials)
    Функционал превью
    75

    View Slide

  76. palkan_tula
    palkan
    View Component
    76
    app/
    frontend/
    components/
    banner/
    component.rb
    component.html.slim
    component.scss
    component.js
    Весь компонент в одной папке

    View Slide

  77. palkan_tula
    palkan
    Другие компоненты
    Cells
    hanami-view
    dry-view
    komponent
    elemental_components
    77

    View Slide

  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

    View Slide

  79. palkan_tula
    palkan
    CSS 2020
    Bulma (+Buefy)
    Tailwind
    Shoelace
    79

    View Slide

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

    View Slide

  81. palkan_tula
    palkan
    Utility-first (никаких компонентов,
    только набор классов)
    Удобный механизм выделения
    компонентов
    Быстрое прототипирование
    (+playground)
    Встроенные оптимизации сборки
    81

    View Slide

  82. palkan_tula
    palkan
    82

    View Slide

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

    View Slide

  84. palkan_tula
    palkan
    84

    View Slide

  85. palkan_tula
    palkan
    Что выбрать?
    Bulma — для админок и особенно,
    если есть Vue
    Shoelace — CRUD, админки
    Tailwind — для пользовательских
    интерфейсов
    85

    View Slide

  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

    View Slide

  87. Итоги
    Есть ли фронтенд без
    фронтенда?

    View Slide

  88. palkan_tula
    palkan
    Фронтенд без фронтенда
    Способен заменить JS фреймворки/
    SPA для приложений с
    нехитровыдуманным UI
    Повышает продуктивность (но всё же
    имеет порог вхождения)
    Новая эра только начинается, всё
    лучшее — впереди!
    88

    View Slide

  89. Cпасибо!
    @palkan
    @palkan_tula
    evilmartians.com
    @evilmartians

    View Slide