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

[RubyConf China 2021] HTML-over-WebSockets: From LiveView to Hotwire

[RubyConf China 2021] HTML-over-WebSockets: From LiveView to Hotwire

Vladimir Dementyev

December 04, 2021
Tweet

More Decks by Vladimir Dementyev

Other Decks in Programming

Transcript

  1. HTML-OVER-
    WEBSOCKETS
    Vladimir Dementyev
    Evil Martians

    View Slide

  2. palkan_tula
    palkan
    github.com/palkan
    2

    View Slide

  3. palkan_tula
    palkan
    Web development today
    3
    This is me 😎

    View Slide

  4. palkan_tula
    palkan
    Back in 2010s
    4
    Full-stack developer
    It's me again 🙂

    View Slide

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

    View Slide

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

    View Slide

  7. HTML (Haml/Slim)
    Asset Pipeline
    CoffeeScript
    jquery
    Helpers
    jquery-ujs
    Turbolinks
    Sass
    Bootstrap
    Bundler (asset gems)
    vendor/assets
    ES6
    Webpack
    PostCSS React
    SPA
    API
    npm / yarn

    View Slide

  8. palkan_tula
    palkan
    8
    Full-stack Ruby on Rails development in 202😷s
    — is that a thing?

    View Slide

  9. YES

    View Slide

  10. palkan_tula
    palkan
    Frontendless Rails
    RailsConf 2021
    10

    View Slide

  11. palkan_tula
    palkan
    HTML-over-
    WebSockets:
    The overview
    11

    View Slide

  12. palkan_tula
    palkan
    Phoenix LiveView
    12

    View Slide

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

    View Slide

  14. palkan_tula
    palkan
    morphdom
    14

    View Slide

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

    View Slide

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

    >

    <%= item.desc %>

    View Slide

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

    >

    <%= item.desc %>

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

    View Slide

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

    View Slide

  19. BACK TO RUBY

    View Slide

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

    View Slide

  21. palkan_tula
    palkan
    21
    Stimulus Reflex creator
    CableReady 🤔

    View Slide

  22. 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
    22

    View Slide

  23. palkan_tula
    palkan
    Example
    23


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

    View Slide

  24. palkan_tula
    palkan
    Example
    24
    # 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

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

    View Slide

  26. palkan_tula
    palkan
    CableReady
    26
    cableready.stimulusreflex.com

    View Slide

  27. 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
    27

    View Slide

  28. palkan_tula
    palkan
    Stimulus
    28
    stimulusjs.org

    View Slide

  29. palkan_tula
    palkan
    Example
    29
    Hide-able banners

    View Slide

  30. palkan_tula
    palkan
    Example: jQuery
    30
    🍜
    function initBannerClose(){

    // Oops, leaking CSS
    $('.banner --close').click(function(e){
    e.preventDefault();
    const banner = $(this).parent();
    banner.remove();
    });
    });
    $(document).on('load', initBannerClose);
    // And don't forget about Turbolinks
    $(document).on('turbolinks:load', initBannerClose);
    // ...or jquery-ujs
    $(document).on('ajax:success', initBannerClose);

    View Slide

  31. palkan_tula
    palkan
    Example: Stimulus
    31
    import { Controller } from "stimulus";
    export class BannerController extends Controller {
    hide() {
    this.element.remove();
    }
    }

    View Slide

  32. palkan_tula
    palkan
    Stimulus
    Stimuli are activated/deactivated
    automatically (MutationObserver API)
    Just drop an element with `data-controller`
    onto a page (like Custom Elements)
    32

    View Slide

  33. View Slide

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

    View Slide

  35. palkan_tula
    palkan
    Example
    35

    View Slide

  36. palkan_tula
    palkan
    Example
    36



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

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


    View Slide

  37. palkan_tula
    palkan
    Example
    37
    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

  38. palkan_tula
    palkan
    Example
    38
    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 Slide

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

    View Slide

  40. 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
    40

    View Slide

  41. palkan_tula
    palkan
    StimulusReflex
    Stable & Mature (v4)
    Works with AnyCable out-of-the-box
    41

    View Slide

  42. palkan_tula
    palkan
    StimulusReflex
    Comprehensive documentation
    Active Discord community (>1k members)
    42
    discord.gg/stimulus-reflex

    View Slide

  43. palkan_tula
    palkan
    NEW MAGIC
    hotwire.dev
    43

    View Slide

  44. palkan_tula
    palkan
    Hotwire
    Turbo
    Stimulus
    Strada
    44

    View Slide

  45. palkan_tula
    palkan
    Turbo
    Drive (ex-Turbolinks)
    Frames
    Streams
    45

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  49. palkan_tula
    palkan
    DOM
    update
    Turbo Streams
    49
    https://my-hot.app

    GET/POST/...
    HTML
    Action Cable broadcast
    from anywhere
    Rails controller
    Action Cable subscribe

    View Slide

  50. palkan_tula
    palkan
    Turbo Streams
    50


    <%= turbo_stream_from workspace %>

    <%= workspace.name %>




    View Slide

  51. palkan_tula
    palkan
    Turbo Streams
    51
    # 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 Slide

  52. palkan_tula
    palkan
    More HTML-over-WS
    Motion
    Live
    52

    View Slide

  53. palkan_tula
    palkan
    Motion
    53
    github.com/unabridged/motion

    View Slide

  54. palkan_tula
    palkan
    Live
    54
    github.com/socketry/live

    View Slide

  55. palkan_tula
    palkan
    Live
    55
    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 Slide

  56. palkan_tula
    palkan
    HTML-over-WebSockets
    Gives full-stack development a second chance
    There are plenty of implementations already
    StimulusReflex and CableReady are rock solid!
    Hotwire is gaining popularity (and stability)
    56

    View Slide

  57. palkan_tula
    palkan
    HTML-over-WebSockets
    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
    57

    View Slide

  58. THANKS
    github.com/palkan
    twitter.com/palkan_tula
    evilmartians.com
    twitter.com/evilmartians

    View Slide