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

52cc8a838bf44a589d2572833b2dd1b9?s=128

Vladimir Dementyev

December 04, 2021
Tweet

More Decks by Vladimir Dementyev

Other Decks in Programming

Transcript

  1. HTML-OVER- WEBSOCKETS Vladimir Dementyev Evil Martians

  2. palkan_tula palkan github.com/palkan 2

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

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

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

  6. HTML (Haml/Slim) Helpers CoffeeScript jquery jquery-ujs Asset Pipeline Turbolinks Sass

    Bootstrap Bundler (asset gems) vendor/assets
  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
  8. palkan_tula palkan 8 Full-stack Ruby on Rails development in 202😷s

    — is that a thing?
  9. YES

  10. palkan_tula palkan Frontendless Rails RailsConf 2021 10

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

  12. palkan_tula palkan Phoenix LiveView 12

  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
  14. palkan_tula palkan morphdom 14

  15. palkan_tula palkan LiveView 15 https://my-live.app Browser events Partial HTML updates

    Internal events Erlang process DOM element
  16. palkan_tula palkan Partial HTML updates 16 <div class="item <%= item.completed?

    ? "checked" : "" %>" id="<%= dom_id(item) %>"> <label class="checkbox"> <input type="checkbox" class="hidden" <%= item.completed? ? "checked" : "" % >> </label> <p><%= item.desc %> </p> </div>
  17. palkan_tula palkan Partial HTML updates 17 <div class="item <%= item.completed?

    ? "checked" : "" %>" id="<%= dom_id(item) %>"> <label class="checkbox"> <input type="checkbox" class="hidden" <%= item.completed? ? "checked" : "" % >> </label> <p><%= item.desc %> </p> </div> Static vs. Dynamic 0 1 2 3 { "0": "checked", "2": "checked" }
  18. palkan_tula palkan Phoenix LiveView Component-driven architecture Erlang ecosystem (processes and

    message passing) Dedicated templating mechanism 18
  19. BACK TO RUBY

  20. palkan_tula palkan “A new way to craft modern, reactive web

    interfaces with Ruby on Rails.” 20
  21. palkan_tula palkan 21 Stimulus Reflex creator CableReady 🤔

  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
  23. palkan_tula palkan Example 23 <!-- _item.html.erb --> <div id="<%= dom_id(item)

    %>"> ... <%= button_to item_path(item), method: :delete, remote: true do %> <svg> ... </svg> <% end %> </div>
  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
  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()
  26. palkan_tula palkan CableReady 26 cableready.stimulusreflex.com

  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
  28. palkan_tula palkan Stimulus 28 stimulusjs.org

  29. palkan_tula palkan Example 29 Hide-able banners

  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);
  31. palkan_tula palkan Example: Stimulus 31 import { Controller } from

    "stimulus"; export class BannerController extends Controller { hide() { this.element.remove(); } }
  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
  33. None
  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
  35. palkan_tula palkan Example 35

  36. palkan_tula palkan Example 36 <!-- _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>
  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
  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
  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
  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
  41. palkan_tula palkan StimulusReflex Stable & Mature (v4) Works with AnyCable

    out-of-the-box 41
  42. palkan_tula palkan StimulusReflex Comprehensive documentation Active Discord community (>1k members)

    42 discord.gg/stimulus-reflex
  43. palkan_tula palkan NEW MAGIC hotwire.dev 43

  44. palkan_tula palkan Hotwire Turbo Stimulus Strada 44

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

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

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

  48. palkan_tula palkan Turbo Streams Minimalistic CableReady (only 5 actions) Transport-agnostic

    Zero JavaScript (uses custom HTML elements to trigger updates) 48
  49. palkan_tula palkan DOM update Turbo Streams 49 https://my-hot.app <turbo-stream-source> GET/POST/...

    HTML Action Cable broadcast from anywhere Rails controller Action Cable subscribe <turbo-stream>
  50. palkan_tula palkan Turbo Streams 50 <!-- app/views/workspaces/show.html.erb --> <div> <%=

    turbo_stream_from workspace %> <div id="<%= dom_id(workspace, :chat) %>" class="chat"> <h1><%= workspace.name %> </h1> <hr class="mt-1"> <div class="messages" id="<%= dom_id(workspace, :chat_messages) %>"> </div> </div>
  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
  52. palkan_tula palkan More HTML-over-WS Motion Live 52

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

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

  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
  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
  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
  58. THANKS github.com/palkan twitter.com/palkan_tula evilmartians.com twitter.com/evilmartians