Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Tăng Tốc Rails Apps Với Hotwire: Server-Side Rendering Không Cần JavaScript

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:

Tăng Tốc Rails Apps Với Hotwire: Server-Side Rendering Không Cần JavaScript 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ư:

Tăng Tốc Rails Apps Với Hotwire: Server-Side Rendering Không Cần JavaScript 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.