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

[RubyBanitsa] HTML-over-WebSockets

[RubyBanitsa] HTML-over-WebSockets

https://rubybanitsa.com/events/79

https://www.youtube.com/watch?v=DfL22z8FjQc&t=12s

The HTML-over-WebSockets approach is conquering the Ruby and Rails universe. New talks, blog posts, frameworks and libraries are popping out like mushrooms in the rain. Want to join this new wave and don't know where to start? My talk is for you!

I'd like to introduce the HTML-over-WebSockets approach, discuss the available architectures (from and outside the Ruby world), and help you figure out whether this new magic fits your needs.

Vladimir Dementyev

June 04, 2021
Tweet

More Decks by Vladimir Dementyev

Other Decks in Programming

Transcript

  1. HTML-over-
    WebSockets
    Vladimir Dementyev
    Evil Martians

    View full-size slide

  2. palkan_tula
    palkan
    github.com/palkan
    2

    View full-size slide

  3. palkan_tula
    palkan
    Web applications
    3

    View full-size slide

  4. palkan_tula
    palkan
    Web applications
    4
    Data Representation

    View full-size slide

  5. palkan_tula
    palkan
    Breakfast 🥐
    5
    Data Representation
    ?

    View full-size slide

  6. palkan_tula
    palkan
    Breakfast 🥬
    6
    Data Representation
    ?

    View full-size slide

  7. Canteen-style
    Ready-made
    Predefined set of dishes
    Wait in line
    Cheap & simple

    View full-size slide

  8. Classic Rails
    Ready-made (HotW)
    Predefined set of responses
    Wait in line (queueing)
    Cheap & simple

    View full-size slide

  9. DIY
    Raw food to cook
    Special equipment is required
    Expensive though tasty

    View full-size slide

  10. SPA
    Raw data to render
    Frontenders are required
    Expensive though "cool" 😎

    View full-size slide

  11. A la carte
    Waiters serve clients
    concurrently
    Order once, get fully-cooked
    food in batches
    Refills without requests
    The chef is a boss

    View full-size slide

  12. HTML-over-WS
    Server serves clients
    concurrently
    Subscribe once, get HTML
    asynchronously
    Live updates w/o requests
    The server is a boss

    View full-size slide

  13. palkan_tula
    palkan
    13
    Why does everyone try to build yet another
    Korean BBQ?
    Is HTML-over-WebSockets possible in 2021?
    The question

    View full-size slide

  14. palkan_tula
    palkan
    Frontendless Rails
    RailsConf 2021
    15

    View full-size slide

  15. palkan_tula
    palkan
    Frontendless Rails (live)
    16

    View full-size slide

  16. palkan_tula
    palkan
    HTML-over-
    WebSockets:
    The overview
    17

    View full-size slide

  17. palkan_tula
    palkan
    Phoenix LiveView
    18

    View full-size slide

  18. palkan_tula
    palkan
    Phoenix LiveView
    HTML elements «connects» to an Erlang process via
    WebSocket
    Process reacts on user interaction and internal server
    events, updates the state, re-renders the affected
    template parts and sends to the client
    Client uses morphdom to perform a fast DOM patching
    19

    View full-size slide

  19. palkan_tula
    palkan
    morphdom
    20

    View full-size slide

  20. palkan_tula
    palkan
    LiveView
    21
    https://my-live.app

    View full-size slide

  21. palkan_tula
    palkan
    LiveView
    21
    https://my-live.app
    DOM element

    View full-size slide

  22. palkan_tula
    palkan
    LiveView
    21
    https://my-live.app
    Erlang process
    DOM element

    View full-size slide

  23. palkan_tula
    palkan
    LiveView
    21
    https://my-live.app
    Browser
    events
    Erlang process
    DOM element

    View full-size slide

  24. palkan_tula
    palkan
    LiveView
    21
    https://my-live.app
    Browser
    events
    Partial HTML
    updates
    Erlang process
    DOM element

    View full-size slide

  25. palkan_tula
    palkan
    LiveView
    21
    https://my-live.app
    Browser
    events
    Partial HTML
    updates
    Internal
    events
    Erlang process
    DOM element

    View full-size slide

  26. palkan_tula
    palkan
    LiveView
    21
    https://my-live.app
    Browser
    events
    Partial HTML
    updates
    Internal
    events
    Erlang process
    DOM element

    View full-size slide

  27. palkan_tula
    palkan
    Partial HTML updates
    22
    " id="<%= dom_id(item) %>">

    >

    <%= item.desc %>

    View full-size slide

  28. palkan_tula
    palkan
    Partial HTML updates
    23
    " id="<%= dom_id(item) %>">

    >

    <%= item.desc %>

    Static vs. Dynamic

    View full-size slide

  29. palkan_tula
    palkan
    Partial HTML updates
    23
    " id="<%= dom_id(item) %>">

    >

    <%= item.desc %>

    Static vs. Dynamic
    0 1
    2
    3

    View full-size slide

  30. palkan_tula
    palkan
    Partial HTML updates
    23
    " id="<%= dom_id(item) %>">

    >

    <%= item.desc %>

    Static vs. Dynamic
    0 1
    2
    3
    {
    "0": "checked",
    "2": "checked"
    }

    View full-size slide

  31. palkan_tula
    palkan
    Phoenix LiveView
    Component-driven architecture
    Erlang ecosystem (processes and message
    passing)
    Dedicated templating mechanism
    24

    View full-size slide

  32. palkan_tula
    palkan
    Back to Ruby
    25

    View full-size slide

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

    View full-size slide

  34. palkan_tula
    palkan
    27
    Stimulus Reflex creator

    View full-size slide

  35. palkan_tula
    palkan
    27
    Stimulus Reflex creator
    CableReady 🤔

    View full-size slide

  36. palkan_tula
    palkan
    CableReady
    A library to broadcast DOM modification
    commands from server to browsers
    Uses Action Cable as a transport
    Uses morphdom to update HTML
    28

    View full-size slide

  37. palkan_tula
    palkan
    Example
    29


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

    View full-size slide

  38. palkan_tula
    palkan
    Example
    30
    # 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 full-size slide

  39. palkan_tula
    palkan
    # 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
    Example
    31
    $(" ##{dom_id(item)}").remove()

    View full-size slide

  40. palkan_tula
    palkan
    CableReady
    32
    cableready.stimulusreflex.com

    View full-size slide

  41. palkan_tula
    palkan
    CableReady v5.0
    Custom operations
    stream_from helper (like in Hotwire, see further)
    33

    View full-size slide

  42. palkan_tula
    palkan
    StimulusReflex
    Reflexes react on user actions and render HTML
    responses
    CableReady is use to send HTML to clients and to
    update DOM
    34

    View full-size slide

  43. palkan_tula
    palkan
    StimulusReflex
    36
    https://my-reflex.app

    View full-size slide

  44. palkan_tula
    palkan
    StimulusReflex
    36
    https://my-reflex.app
    DOM element

    View full-size slide

  45. palkan_tula
    palkan
    StimulusReflex
    36
    https://my-reflex.app
    Browser events ➝
    reflex class & action
    Reflex class
    DOM element

    View full-size slide

  46. palkan_tula
    palkan
    StimulusReflex
    36
    https://my-reflex.app
    Browser events ➝
    reflex class & action
    CableReady
    operation
    Reflex class
    DOM element
    Document

    View full-size slide

  47. palkan_tula
    palkan
    StimulusReflex
    36
    https://my-reflex.app
    Browser events ➝
    reflex class & action
    CableReady
    operation
    Action Cable broadcast
    from anywhere
    Reflex class
    DOM element
    Document

    View full-size slide

  48. palkan_tula
    palkan
    StimulusReflex
    36
    https://my-reflex.app
    Browser events ➝
    reflex class & action
    CableReady
    operation
    Action Cable broadcast
    from anywhere
    Reflex class
    DOM element
    Document

    View full-size slide

  49. palkan_tula
    palkan
    Example
    37

    View full-size slide

  50. palkan_tula
    palkan
    Example
    37

    View full-size slide

  51. palkan_tula
    palkan
    Example
    38



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

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


    View full-size slide

  52. palkan_tula
    palkan
    Example
    39
    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 full-size slide

  53. palkan_tula
    palkan
    Example
    40
    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
    Broadcast DOM updates to all
    connected clients
    Show flash-notification to the current
    user
    Object representing the current
    element data attributes

    View full-size slide

  54. palkan_tula
    palkan
    Example
    41
    class ApplicationReflex < StimulusReflex ::Reflex
    private
    def morph_flash(type, message)
    morph "#flash", render_partial(
    "shared/alerts",
    {flash: {type => message}}
    )
    end
    end

    View full-size slide

  55. palkan_tula
    palkan
    StimulusReflex
    Introduces a new abstraction layer (reflexes)
    Unscoped DOM updates (you can even update
    the whole page)
    DOM manipulations could be customized to
    infinity
    42

    View full-size slide

  56. palkan_tula
    palkan
    StimulusReflex
    Stable & Mature (v3.4)
    Comprehensive documentation
    Active Discord community (>1k members)
    Works with AnyCable out-of-the-box 😉
    43

    View full-size slide

  57. palkan_tula
    palkan
    StimulusReflex v4.0
    Transport-agnostic (cables, SSE, message_bus,
    AJAX)
    Reliable data flow
    44

    View full-size slide

  58. palkan_tula
    palkan
    NEW MAGIC
    hotwire.dev
    45

    View full-size slide

  59. palkan_tula
    palkan
    NEW MAGIC
    hotwire.dev
    45

    View full-size slide

  60. palkan_tula
    palkan
    evilmartians.com/blog
    evilmartians.com/chronicles/hotwire-reactive-rails-with-no-javascript
    46

    View full-size slide

  61. palkan_tula
    palkan
    Hotwire Demystified
    @jamie_gaskings at RailsConf 2021
    47

    View full-size slide

  62. palkan_tula
    palkan
    Turbo Drive
    Poor man's SPA
    Intercepts navigation / forms submission, performs
    AJAX requests, replaces HTML body contents
    Keeps track of visited pages (cache) to provide
    smooth experience
    48
    ex-Turbolinks

    View full-size slide

  63. palkan_tula
    palkan
    Turbo Frames
    Turbolinks for page fragments (frames)
    Lazy loading of page parts (plays well with HTTP
    cache)
    49

    View full-size slide

  64. palkan_tula
    palkan
    Example
    50
    # app/controllers/items_controller.rb
    class ItemsController < ApplicationController
    def update
    item.update!(item_params)
    render partial: "item", locals: {item}
    end
    def destroy
    item.destroy!
    render partial: "item", locals: {item}
    end
    end

    View full-size slide

  65. palkan_tula
    palkan
    Example
    51


    <% unless item.destroyed? %>
    ">
    <%= form_for item do |f| %>

    <%= f.check_box :completed,
    class: item.completed? ? "hidden checked" : "hidden",
    onchange: "this.form.requestSubmit();" %>

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

    <% end %>

    <% end %>

    View full-size slide

  66. palkan_tula
    palkan
    Turbo Streams
    Minimalistic CableReady (only 5 actions)
    Transport-agnostic
    Zero JavaScript (uses custom HTML elements to
    trigger updates)
    52

    View full-size slide

  67. palkan_tula
    palkan
    Turbo Streams
    53
    https://my-hot.app

    View full-size slide

  68. palkan_tula
    palkan
    Turbo Streams
    53
    https://my-hot.app

    View full-size slide

  69. palkan_tula
    palkan
    Turbo Streams
    53
    https://my-hot.app
    Action Cable subscribe

    View full-size slide

  70. palkan_tula
    palkan
    Turbo Streams
    53
    https://my-hot.app
    GET/POST/...
    Rails controller
    Action Cable subscribe

    View full-size slide

  71. palkan_tula
    palkan
    Turbo Streams
    53
    https://my-hot.app
    GET/POST/...
    HTML
    Rails controller
    Action Cable subscribe

    View full-size slide

  72. palkan_tula
    palkan
    Turbo Streams
    53
    https://my-hot.app
    GET/POST/...
    HTML
    Action Cable broadcast
    from anywhere
    Rails controller
    Action Cable subscribe

    View full-size slide

  73. palkan_tula
    palkan
    DOM
    update
    Turbo Streams
    53
    https://my-hot.app
    GET/POST/...
    HTML
    Action Cable broadcast
    from anywhere
    Rails controller
    Action Cable subscribe

    View full-size slide

  74. palkan_tula
    palkan
    Turbo Streams
    54


    <%= turbo_stream_from workspace %>

    <%= workspace.name %>




    View full-size slide

  75. palkan_tula
    palkan
    Turbo Streams
    55
    # app/controllers/chat/messages_controller.rb
    class MessagesController < ApplicationController
    def create
    Turbo ::StreamsChannel.broadcast_append_to(
    workspace,
    target: ActionView ::RecordIdentifier.dom_id(workspace, :chat_messages),
    partial: "chats/message",
    locals: {message: params[:message], name: current_user.name}
    )
    head :ok
    end
    end

    View full-size slide

  76. palkan_tula
    palkan
    More HTML-over-WS
    Motion
    Live
    56

    View full-size slide

  77. palkan_tula
    palkan
    Motion
    57
    github.com/unabridged/motion

    View full-size slide

  78. palkan_tula
    palkan
    Live
    58
    github.com/socketry/live

    View full-size slide

  79. palkan_tula
    palkan
    Live
    59
    class ClickCounter < Live ::View
    def initialize(id, **data)
    super
    @data[:count] ||= 0
    end
    def handle(event, details)
    @data[:count] = Integer(@data[:count]) + 1
    replace!
    end
    def render(builder)
    builder.tag :button, onclick: forward do
    builder.text("Add an image. ( #{@data[:count]} images so far).")
    end
    builder.tag :div do
    Integer(@data[:count]).times do
    builder.tag :img, src: "https: //picsum.photos/200/300"
    end
    end
    end
    end

    View full-size slide

  80. palkan_tula
    palkan
    Live
    60
    class ClickCounter < Live ::View
    def initialize(id, **data)
    super
    @data[:count] ||= 0
    end
    def handle(event, details)
    @data[:count] = Integer(@data[:count]) + 1
    replace!
    end
    def render(builder)
    builder.tag :button, onclick: forward do
    builder.text("Add an image. ( #{@data[:count]} images so far).")
    end
    builder.tag :div do
    Integer(@data[:count]).times do
    builder.tag :img, src: "https: //picsum.photos/200/300"
    end
    end
    end
    end

    View full-size slide

  81. palkan_tula
    palkan
    Motion / Live
    Conceptually closer to LiveView (stateful
    components)
    Does keeping view components state scale in
    Ruby? 🤔
    61

    View full-size slide

  82. palkan_tula
    palkan
    HTML-over-WebSockets
    There are plenty of implementations already
    StimulusReflex and CableReady are rock solid!
    Hotwire is gaining popularity (and stability)
    Motion and Live are promising
    62

    View full-size slide

  83. palkan_tula
    palkan
    HTML-over-WebSockets
    Gives full-stack development a second chance
    Worth considering if:
    You don't want to hire the whole new team
    Your app is based on user-server interactions
    You want to vitalize a classic Rails app
    63

    View full-size slide

  84. THANKS!
    @palkan
    @palkan_tula
    evilmartians.com
    @evilmartians
    https://discord.gg/stimulus-reflex

    View full-size slide