Hotwire! Onde vivem? De que se alimentam?

Hotwire Esse assunto foi bastante falado desde o lançamento do Rails 7, mas até então eu nunca tinha parado para entender o que ele é, e como ele funciona.Caia naquelas dúvidas, meio na descrença, sabe? Conteúdo dinâmico com poucas linhas de código sem reload, usando Rails? Será mesmo. Não vai…

Hotwire

Esse assunto foi bastante falado desde o lançamento do Rails 7, mas até então eu nunca tinha parado para entender o que ele é, e como ele funciona.
Caia naquelas dúvidas, meio na descrença, sabe? Conteúdo dinâmico com poucas linhas de código sem reload, usando Rails? Será mesmo. Não vai ser lento? Funciona bem? Como ele funciona? Mas nunca passava disso.
Até que me deparei com a necessidade de conteúdos dinâmicos em um projeto de estudos onde acabei usando Hotwire e decidi compartilhar um pouco do que aprendi!
O objetivo deste post é explicar um pouco do que é Hotwire, levar um primeiro contato, trazer uma ideia do que podemos fazer com ele, um ou outro exemplo simples usando Rails e conforme o interesse do pessoal trazer ou não mais sobre esse assunto.

O que podemos fazer com Hotwire?

O Hotwire nos permite adicionar interatividade ao nosso app, de um jeito completamente novo, algumas linhas de código no controller, outras no erb e já conseguimos alterar o conteúdo dinamicamente, enviando pedaços HTML para a View via essa “conexão viva” (hotwire). Sabe aquela Single Page Application (SPA) que tu querias fazer? Boom, você não vai precisar daqueles frameworks SPA, vai poder manter isso em Rails! Também significa que você pode manter sua toda lógica de renderização centralizada em um único servidor e ainda ter o carregamento da página rápido e interativo! Formulários dinâmicos, rolagem infinita, anexar conteúdos, tudo isso sem reload da página.

Mas afinal o que é Hotwire e como funciona?

Hotwire é uma nova abordagem na construção de aplicações web e mobile por meio do envio de HTML sobre a conexão, usando novas e outra não tão novas técnicas. Não é uma única biblioteca, mas sim um conjunto de ferramentas que incluem o Turbo, Stimulus e o Strada (que ainda está em desenvolvimento), além de usar conexões Websockets.

O Hotwire é agnóstico de uma linguagem e pode funcionar tão bem em outras linguagens e aplicações quanto no Rails e apesar de associarmos um ao outro existem materiais abordando como usar o Stimulus e o Turbo em apps não Rails por exemplo.

O que faz cada coisa no Hotwire?

Como falei anteriormente, no Hotwire temos um conjunto de técnicas que juntas nos permitem ter a interatividade, atualização da página sem reload! Vamos explorar um pouco o que cada membro desse conjunto faz!

Turbo

Bom, o turbo e é onde a mágica acontece! Ele fornece várias técnicas para lidar com os dados HTML transmitidos pela conexão e mostrar isso na aplicação sem ter de recarregar a página inteira! Ele é composto por:

  • Turbo Drive:  Com seu uso os links não vão disparar um reload completo da página, o turbo drive intercepta alguns eventos JavaScript no seu aplicativo, carrega HTML assincronamente e substitui partes que estão diferentes do HTML que está dentro do <body>.
  • Turbo Frames: decompõe a página em diversos pedaços (frames) que podem ser atualizadas de forma independente, assincronamente, como se fossem contêiner autônomos.
  • Turbo Streams: Conjunto de utilidade para trazer dados em tempo real de uma maneira fácil para o seu aplicativo. Por exemplo, digamos que você está construindo um feed de notícias como o do Twitter. Você deseja trazer novos tweets para o feed de um usuário assim que forem postados, sem recarregar a página. O Turbo Streams permite fazer isso sem escrever uma única linha de código JavaScript. Ele é um novo meio de enviar HTML para o browser e atualizar partes específicas do site.
  • Turbo Native: Permite construir um invólucro nativo ao redor do seu aplicativo web. A navegação e interação passará a sensação de ser nativa, sem que você precise redesenhar todas as telas de forma nativa. Você continuará entregando o restante do aplicativo através da web. Dessa forma, você pode se concentrar nas partes realmente interativas do seu aplicativo e fazer com que elas funcionem corretamente.

Stimulus

Com o objetivo de escrever o mínimo possível de JavaScript, este é descrito como “um framework JavaScript com ambições modestas”, ao contrário de frameworks mais pesados que visam criar um aplicativo completo, o Stimulus, em muitos aspectos, lembra o jQuery. Suas principais responsabilidades estão em anexar comportamentos aos elementos e responder a eventos básicos. No entanto, ele traz toque moderno à mesa, permitindo que você anexe classes que implementam comportamentos a objetos no DOM. Ele não renderiza nenhum HTML, em vez disso ele adiciona alguma responsividade em cima do HTML existente.

Strada

Infelizmente, ainda não se tem muitas informações sobre o Strada, parece que o Strada possibilitará que um aplicativo web se comunique com um aplicativo nativo, permitindo possíveis interações e ações, por meio da utilização de “atributos de ponte” em HTML.

Bora pro primeiro contato!

Ambiente de desenvolvimento e versões usadas nos exemplos.

ruby -v
#ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x86_64-linux]

rails -v
#rails 7.0.6

bundle -v
#Bundler version 2.4.15

Criando projeto base

Vamos começar criando um novo projeto Rails e depois rodar o bundle.

rails new hotwire_first_steps

bundle install

E então vamos criar os Controllers

class HomeController < ApplicationController
end
class CustomersController < ApplicationController
end

Agora vamos criar as Views

<h1>Bem-vindo à página de Clientes</h1>
<div>
  <%= link_to "ir para home page", root_path %>
</div>

<h1>Bem-vindo à Home Page</h1>
<div>
  <%= link_to "ir para página de Clientes", customers_path %>
</div>

As rotas

Rails.application.routes.draw do
  get :customers, to: "customers#index"
  root to: "home#index"
end

E finalmente, vamos rodar o servidor Rails 😀

rails s

Abra o localhost:3000 no browser e você vai ver algo assim:

pronto projeto base funcionando! 🥳🥳🥳

Agora vamos dar uma olhada em alguns exemplos…

Turbo Drive

Primeor abra a aba “Network” do DevTools do seu browser.

Agora navegue da home para a página customers clicando no link.

Você notou algo?

A navegação foi realizada via XHR (XMLHttpRequest). O CSS não é recarregado a cada navegação, o JavaScript também não é recarregado. Na verdade, as únicas coisas recarregadas são as diferenças no DOM dentro da Tag HTML, como o body por exemplo.
A performance é significativamente maior, cada clique responde imediatamente, mesmo para aplicativos em produção, não é somente no localhost, onde as melhorias nem são tão notáveis.
Porém isso pode trazer comportamento estranhos e que precisam ser tratados, como piscadas na DOM, JS com comportamentos incorretos já que não é recarregado, menor acessibilidade entre outros.


O Turbo Drive é extremamente poderoso, mas requer bastante paciência para dominá-lo e saber como fazer para que cada coisa funcione corretamente em conjunto com ele.

Turbo Frame

Vamos alterar o Home controller para:

class HomeController < ApplicationController
  # rota GET /turbo_frame_form
  def turbo_frame_form; end

  # @rota POST /turbo_frame_submit
  def turbo_frame_submit
    extracted_word = params[:any][:qualquer_palavra]
    render :turbo_frame_form, status: :ok,
                              locals: { qualquer_palavra: extracted_word, comment: 'turbo_frame_submit ok' }
  end
end

Criamos uma nova View, repare que temos uma tag com um atributo chamado ‘umframe’

<section>

    <%= turbo_frame_tag 'umframe' do %>

      <div>
          <h2>Frame substituto, será novamente substituído quando você apertar em enviar</h2>
          <%= form_with scope: :any, url: turbo_frame_submit_path, local: true do |form| %>
              <%= form.label :qualquer_palavra, 'Digite uma palavra', 'class' => 'my-0  d-inline' %>
              <%= form.text_field :qualquer_palavra, 'required' => 'true',
              'value' => "#{local_assigns[:qualquer_palavra] || ''}",  'aria-describedby' => 'qualquer_palavra' %>
              <%= form.submit 'enviar', 'id' => 'submit-word' %>
          <% end %>
      </div>
      <div>
        <h2>Dados da view</h2>
        <pre style="font-size: .7rem;"><%= JSON.pretty_generate(local_assigns) %></pre>
      </div>

    <% end %>

</section>

Mudamos a view index da Home, veja , aqui também temos o atribuito ‘umframe’,

<h1>Bem-vindo à Home Page</h1>
<div>
  <%= link_to "ir para página de Clientes", customers_path %>
</div>
<%= turbo_frame_tag 'umframe' do %>
  <div>
    <h2>Frame inicial, este frame será alterado quando clicar no submit</h2>
    <%= form_with scope: :any, url: turbo_frame_submit_path, local: true do |form| %>
      <%= form.label :qualquer_palavra, 'Digite uma palavra', 'class' => 'my-0  d-inline' %>
      <%= form.text_field :qualquer_palavra, type: 'input', 'required' => 'true',
      'value' => "#{local_assigns[:qualquer_palavra] || ''}",  'aria-describedby' => 'qualquer_palavra' %>
      <%= form.submit 'enviar', 'id' => 'submit-word' %>
    <% end %>
  </div>
<% end %>

e adicionamos as duas rotas que usaremos no exemplo

Rails.application.routes.draw do
  get :customers, to: 'customers#index'
  root to: 'home#index'
  get '/home/turbo_frame_form' => 'home#turbo_frame_form', as: 'turbo_frame_form'
  post '/home/turbo_frame_submit' => 'home#turbo_frame_submit', as: 'turbo_frame_submit'
end

Reinicie o Rails Server e abrar o Browser em localhost:3000

Inicialmente você verá isso:

Digite uma palavra qualquer e clique em enviar

O Turbo Frame atualizou apenas o Frame desejado identificado como umframe na turbo_frame_tag, mantendo intocadas a primeira parte que contém o título e o link!

Existem outras maneiras de definir o target, incluindo via controler, mas não iremos abordar isso nesse post.

Usos tradicionais do Turbo Frame incluem edição inline, conteúdo em abas, busca, classificação e filtragem de dados.

Turbo Streams

Na Teoria, ele pode entregar alterações de página sobre WebSocket, SSE ou em resposta a envio de formulários, usando somente HTML e o conjunto de Ações Estilo um CRUD.

Trazendo para nosso mundo, podemos atualizar um blocos de HTML quando respondemos um POST/PUT/PATCH/DELETE action ( GET NÃO FUNCIONA ). E Transmitir uma alteração para todos os usuários, sem a necessidade de refresh/reload do navegador.

vamos fazer um exemplo simples

Alteremos as rotas

Rails.application.routes.draw do
  get :customers, to: 'customers#index'
  root to: 'home#index'
  get '/home/turbo_frame_form' => 'home#turbo_frame_form', as: 'turbo_frame_form'
  post '/home/turbo_frame_submit' => 'home#turbo_frame_submit', as: 'turbo_frame_submit'
  post '/customers/post_something' => 'customers#post_something', as: 'post_something'
end

Vamos criar a View, reapre que temos a action e um target, o target aponta para onde no html, o conteúdo vai apontar.

<turbo-stream action="append" target="alguma_coisa">
  <template>
    <div id="alguma_coisa_1">Isso altera o conteúdo existente!</div>
  </template>
</turbo-stream>

Modifiquemos então a view index de Customers, criando um turbo frame, o tag com id ‘alguma_coisa’ será o target para o Turbo streams

<h1>Bem-vindo à página de Clientes</h1>

<div><%= link_to "ir para home page", root_path %></div>

<div style="margin-top: 3rem;">
  <%= form_with scope: :any, url: post_something_path do |form| %>
      <%= form.submit 'Enviar algo via POST' %>
  <% end %>
  <turbo-frame id="alguma_coisa">
    <div>Uma informação por enquanto vazia</div>
  </turbo-frame>
</div>

E por último mas não menos importante, alteraremos o Customers Controller, adicionando a ação que será disparada via botão, e reponderá com o post_something.turbo_stream.erb

class CustomersController < ApplicationController

  def post_something
    respond_to do |format|
      format.turbo_stream {  }
    end
  end
end

Reinicie o Rails Server e agora deve ter algo assim na tela

Clique em Enviar algo via POST, para simularmos isso!

Uma linha foi adicionada! ✌️✌️

Com o Turbo Streams conseguimos anexar uma mensagem depois de um submit, sem nenhum reload, de uma forma simples e fácil. Não esqueça, o target da tag turbo-stream deve bater com o id de onde deseja modificar/adicionar conteúdo, nesse caso estamos usando um turbo frame com a id alguma_coisa, mas funcionaria se fosse em uma lista, div desde que fosse a mesma id do target. Assim como no Turbo Frame, existem outras maneiras de definir o target, mas teriamos que nos aprofundar mais, por isso não cobriremos isso neste post.

Também podemos fazer que o Turbo Streams substitua um conteúdo em vez de anexar, vamos testar isso??

Altere o action na view post_something.turbo_stream.erb, de append para replace.

<turbo-stream action="replace" target="alguma_coisa">
  <template>
    <div id="alguma_coisa_1">Isso substitui o conteúdo existente!</div>
  </template>
</turbo-stream>

Reiniciar o Rails Server e…

Agora vamos clicar novamente Enviar algo via POST

Desta vez o conteúdo foi substituído!! 😎

Stimulus

É a ferramenta do Hotwire para lidar com JS, a razão para ter esse recurso é que, por mais que o objetivo seja evitar JS, o Turbo não consegue cobrir todos os cenários possíveis. Existirão casos que o JS ainda será necessário.

Vamos alterar a View customers index

<h1>Bem-vindo à página de Clientes</h1>

<div><%= link_to "ir para home page", root_path %></div>

<div style="margin-top: 3rem;">
  <%= form_with scope: :any, url: post_something_path do |form| %>
      <%= form.submit 'Enviar algo via POST' %>
  <% end %>
  <turbo-frame id="alguma_coisa">
    <div>Uma informação por enquanto vazia</div>
  </turbo-frame>
</div>
<div style="margin-top: 2rem;">
  <h2>Stimulus</h2>
  <div data-controller="hello">
    <input data-hello-target="name" type="text" placeholder="Digite seu nome aqui">
    <button data-action="click->hello#greet">
      Cumprimentar
    </button>
    <span data-hello-target="output">
    </span>
  </div>
</div>

Altere o arquivo hello_controller.js para o conteúdo abaixo, ele fica em app/Javascript/controllers/

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = [ "name", "output" ]

  greet() {
    this.outputTarget.textContent =
      `Olá, ${this.nameTarget.value}!`
  }
}

Reinicie seu Rails Server, depois vá ao localhost:3000/customers

Digite um nome, e clique em cumprimentar

O Stimulus vai disparar a função greet, nos targets definidos, e fará a alteração do conteúdo.

Esse exemplo foi retirado diretamente da documentação. É o suficiente para entender o objetivo do Stimulus, adicionar interatividade no lado do front-end! E da para fazer bastante coisas, quem sabe nos aprofundamos mais em um próximo post.

Chegamos ao final do Post

Então pessoal, com esse post aprendemos uns conceitos básicos do Hotwire, seus objetivos, vimos algumas coisas legais e teríamos muitas outras para aprofundar, testar, porém, este post já ficou longo demais.

O Hotwire realmente tem muitos recursos legais, nos possibilita ter mais interatividade sem usar Javascript, de fato isso é possível! A dificuldade que achei é justamente essa, por ter tantos recursos, temos que aprender a contornar alguns comportamentos indesejados. Fora essas dores de cabeça, achei o funcionamento bem legal.

E sim, é possível fazer páginas SPA, ter rolagem infinita, e várias outras coisas legais só com o Hotwire.

Até a próxima pessoal,

->Github projeto com exemplos<-

Links interessantes:

https://turbo.hotwired.dev/

https://stimulus.hotwired.dev/

https://www.hotrails.dev/

https://blog.appsignal.com/2022/07/06/get-started-with-hotwire-in-your-ruby-on-rails-app.html

0 Comentário