Nếu bạn tìm cách tăng tốc page changes và form submissions, chia complex pages thành components mà không viết JavaScript code, bài này giúp đưa Rails lên level tiếp theo với Hotwire. Bài viết dạy cách dùng tool cho server-side rendering.
Hotwire là gì?
Hotwire cung cấp cách build modern web applications không viết JavaScript bằng cách gửi HTML thay vì JSON over the wire, làm pages load nhanh hơn. Nó maintain rendering trên server-side và cho phép development experience đơn giản, productive hơn, như Rails luôn incorporate vào technologies, mà không hy sinh speed hoặc responsiveness của traditional single-page application (SPA).
Core của Hotwire là Turbo gem. Là tập hợp complementary techniques tăng tốc navigation pages và submission forms, chia complex pages thành components và transmit partial updates của pages qua WebSocket (gồm ActionCable, channels, streaming data).
Tại sao nên dùng?
Nếu bạn struggle với JavaScript và muốn tận hưởng better user experience qua cảm giác navigate giữa pages nhanh hơn, Hotwire là alternative cho bạn.
Hotwire hoạt động như thế nào? Hotwire dùng server-side rendering (SSR) giải quyết một số problems associated với SPAs nhưng vẫn giữ main advantages. SSR đảo ngược rendering process, mang phần rendering effort của SPA lên server, giống traditional loading. SSR cung cấp users efficient loading hơn vì một phần rendering done trên server. Ngoài performance improvement possibility, giúp deal với SEO problems như indexing.
Dễ dùng?
Rất đơn giản; chỉ cần default package Rails project (Ruby, RoR, ActionCable, WebSocket), Turbo gem download all JS dependencies, và Redis store temporary data khi browse WebSocket.
Không cần học language khác (JS) để có speed của single-page web app vì Turbo complemented bởi Turbo Drive, Frames, Streams, Native. Drive speed-up links và forms giảm cần reload networks, Frames chia networks thành independent contexts dễ load hơn.
Cách dùng với Rails?
Section này show step-by-step example.
Ví dụ này cần:
- Ruby
- Ruby on Rails
- Redis
- SQLite (default database)
- Gem Hotwire
- TurboRails
- StimulusJS
- WebSocket
- ActionCable
Dự án gì sẽ tạo?
Tạo social media project.
Mở terminal. Bắt đầu new rails project:
rails new social-media
Vào project:
cd social-media
Thêm Hotwire
Thêm Hotwire gem vào project:
bundle add hotwire-rails
Hoặc mở Gemfile thêm:
gem "hotwire-rails"
Nếu dùng option 2, chạy bundle ngay.
bundle install
Sau đó, install nó.
rails hotwire:install
Initial settings
Bây giờ là lúc analyze initial settings Hotwire làm. Khi visit Gemfile, nên trông như:
gem 'redis', '~> 4.0'
Tại sao cần Redis?
Redis gem added vì ActionCable cần nó store temporary data khi browse WebSocket. Tuy nhiên, chỉ cài Redis không đủ dùng. Cần check configured đúng. Khi vào config/cable.yml, nên trông như:
development:
adapter: redis
redis://localhost:6379/1
Đảm bảo Redis running khi start app (redis-server).
JS dependencies
Check dependencies trên package.json:
dependencies: {
@hotwired/turbo-rails: ^7.0.0-beta.5,
@rails/actioncable: ^6.0.0,
@rails/activestorage: ^6.0.0,
@rails/ujs: ^6.0.0,
@rails/webpacker: 4.3.0,
stimulus: ^2.0.0
}
Generating model
Sau check tất cả files, generate views, controllers, models, migrations cho table posts với columns body, likes. Chạy terminal:
rails g scaffold posts body:text likes:integer
Tạo database
Đã generate xong, cần send changes đến database, chạy terminal:
rails db:create db:migrate
Nếu mọi thứ ok, chạy server (rails server) check everything OK. Chạy server rồi visit posts page, là https://localhost:3000/posts:
printscreen of the posts directory
Listing posts
Bây giờ list posts. Tạo file app/views/posts/_post.html.erb thêm code sau:
<div style="background: lightgrey; width: 300px; padding: 10px;">
<%= post.body %>
<br>
<%= link_to :edit, edit_post_path(post) %>
<%= button_to "likes (#{post.likes || 0})", post_path(post, like: true), method: :put %>
</div>
<br>
Validations
Cần validate body field (không null), và broadcast display tweet trên same screen as first tweet sau khi tạo. Edit file app/models/post.rb insert code:
class Post < ApplicationRecord
validates_presence_of :body
after_create_commit { broadcast_prepend_to :posts }
end
Ordering
Phải order posts, mở controller (app/controllers/posts_controller.rb).
...
def index
@posts = Post.all.order(created_at: :desc)
@post = Post.new
end
...
Wrapping up
Bây giờ edit index (app/views/posts/index.html.erb) show new post page và list all posts.
<%= turbo_stream_from :posts %>
<%= turbo_frame_tag :post_form do %>
<%= render 'posts/form', post: @post %>
<% end %>
<%= turbo_frame_tag :posts do %>
<%= render @posts %>
<% end %>
Redirecting to the index
Cuối cùng, modify create method trong controller. Để avoid redirect to show post page, giữ mọi thứ same page.
...
def create
@post = Post.new(post_params)
respond_to do |format|
if @post.save
format.html { redirect_to posts_path }
format.json { render :show, status: :created, location: @post }
else
format.turbo_stream { render turbo_stream: turbo_stream.replace(@post, partial: 'posts/form', locals: { post: @post }) }
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
end
end
...
Final page trông như:
printscreen of the final page
Checking the logs
Khi post tạo, check terminal logs; nên trông như:
[ActionCable] Broadcasting to posts: "<turbo-stream action=\"prepend\" target=\"posts\"><template><div style=\"background: lightgrey; width: 300px; padding: 10px;\">\n first post\n <br>\n <a href=\"/posts/1/edit\">edit</a>\n <form class=\"button_to\" method=\"post\" action=\"/posts/4?like=true\"><input type=\"hidden\" name=\"_method\" value=\"put\" /><input type=\"submit\" value=\"likes (0)\" /><input type=\"hidden\" name=\"authenticity_token\" value=\"<token>==\" /></form>\n</div>\n<br>\n</template></turbo-stream>"
Thoughts
Nghĩa là ActionCable working với turbo stream super fast performance, như thấy: Completed 302 Found in 18ms.
Deploying
Cần executable khác chạy alongside Rails server?
Không, vì ActionCable work fine với popular servers như Unicorn, Puma, Passenger.
Heroku support Hotwire?
Có, Heroku support base của Hotwire (websockets). Nếu thêm questions, check Heroku's official documentation.
Tại sao dùng Redis, còn một lý do nữa
Nếu Heroku dùng không Redis, messages không sent đến everyone khi app scale hơn single dyno. Do stateless nature current app, clients connected đến first dyno không get messages sent bởi clients connected đến second dyno. Redis giải quyết bằng store message state trong global storage.
Setup Redis trên Heroku
Để establish Redis CLI session với remote Redis instance, dùng heroku redis:cli. Nếu không specify instance, instance tại REDIS_URL dùng default. Nếu nhiều instance, specify instance muốn connect.
Hoạt động trên application server như thế nào?
Cần setup mỗi dyno dùng Redis pub-sub system. Tất cả dynos subscribe same channel (default) và wait messages. Khi mỗi server get message, publish đến connected clients. Cover subscribe trong separate thread, vì subscribe blocking function, wait messages stop execution flow. Thêm, second Redis connection needed vì once subscribe command made trên connection, connection chỉ unsubscribe hoặc receive messages. Learn more about scaling tại đây.
Security
Hiện tại, app exposed và vulnerable nhiều attacks. Hãy setup WSS và sanitizes input. Learn more about WebSocket Security tại đây.
Kết luận
Có thể dùng Hotwire qua new application, như show ở đây, nhưng cũng possible update từ Turbolinks.
Bài này nên giúp bạn hiểu "magic framework" hoạt động như thế nào. Trong đó, bạn có possibility develop, in practice, small simple application show requests via aggregated turbo stream với manipulation và navigation qua WebSocket.