Rails' fragment caching ให้ greater speedups เมื่อ bigger part ของ page ถูก cached นี่ยากขึ้นสำหรับ pages ที่มี dynamic หรือ user-specific content มาก โซลูชันคือใช้ "JavaScript sprinkles" ซึ่งเหมือน hagelslag แต่ไม่มีช็อกโกแลตและมี extra requests โหลด user-specific content หลังจาก rest ของ page ถูก served จาก cache โดยตรง
Fragment caching
Techniques เช่น fragment caching ใช้ speed up responses ใน Rails applications โดย caching fragments ของ rendered page ด้วย smart cache keys fragments ถูก invalidate อัตโนมัติเมื่อ content เปลี่ยนเพราะ data ที่แสดงใน view ถูก updated
Caching view fragments ให้ larger speed boost จาก smaller cache เมื่อ cached fragments ถูกใช้บ่อยขึ้น เมื่อ content ของ page ขึ้นอยู่กับ currently signed in user เช่น caching page's fragments จะกลายเป็นยากขึ้น
Unread Responses
เป็นตัวอย่าง มาใช้ Rails-based blog บน blog นี้ แต่ละ article สามารถมี responses หลายๆ ตัว ระบบ authentication ถูกเพิ่มเพื่อให้ users sign in และ post responses ด้วย accounts บนแต่ละ article page เรา list responses เพื่อทำให้ง่ายสำหรับ users เรา mark new responses ด้วย icon และ background color ที่ต่างกัน
ถ้าเรา wrap แต่ละ article ใน cache block เรา risk marking wrong responses เป็น unread เมื่อ user request index page response ถูก cached เมื่อ user อื่น request หน้าเดียวกันภายหลัง พวกเขาจะได้ cached fragment พร้อม unread responses ของ user แรก
# app/views/articles/show.html.erb <% cache(@article) do %> <%= @article.title %>
<%= simple_format(@article.content) %> Responses
<% @article.responses.each do |response| %> <%= time_tag(response.created_at) %> <%= response.name %>: <%= response.content %> <% end %> <% end %>วิธีแก้คือเพิ่ม currently signed in user เข้า cache key โดยใช้ [@article, @current_user] แทนแค่ @article เป็น argument ส่งให้ cache helper method
# app/views/articles/show.html.erb <% cache([@article, @current_user]) do %> <%= @article.title %>
# ... <% end %>สิ่งนี้ ensure ว่าแต่ละ user เห็น unread responses ของตัวเอง แต่ negate ส่วนใหญ่ของ speedup จาก caching fragments เพราะ article fragments ตอนนี้ cached แยกกันสำหรับแต่ละ user
JavaScript Sprinkles
เพราะ most ของ page เหมือนกันสำหรับทุก users เราอยาก reuse cached article fragment สำหรับแต่ละ visitor เพื่อทำนี้ เราสามารถ load article จาก cache และเพิ่ม extra request เพื่อ fetch user-specific content หลังจาก page loaded ผ่าน JavaScript request การใช้ sprinkle ของ JavaScript functionality สำหรับนี้มี advantages หลายอย่าง:
- Page สามารถ cached หนึ่งครั้งและ reuse สำหรับ unauthenticated guests และ users อื่นๆ โดยไม่ต้อง cache fragments แยกสำหรับทุก user
- Most important content โหลดก่อนสำหรับ fastest response time และ secondary features เช่น unread count โหลดภายหลัง
- เพราะ extra request ทำผ่าน JavaScript whole page สามารถ edge-cached บน CDN ได้อีกเพื่อ improve performance เพิ่ม
Cleanup
เราจะ remove dynamic content จาก pages ก่อนเพื่อทำให้ cache ง่ายขึ้น เราจะ remove @current_user จาก cache key ใน cache block อีกครั้ง ดังนั้นพวกมันไม่ cached per user แล้ว จากนั้นเราจะ remove query ที่หา unread counts จาก controller และ remove CSS class names จาก view
# app/views/articles/show.html.erb <% cache(@article) do %> <%= @article.title %>
<%= simple_format(@article.content) %> Responses
<% @article.responses.each do |response| %> <%= time_tag(response.updated_at) %> <%= response.name %>: <%= response.content %> <% end %> <% end %>เราเหลือ generic page ที่ cache ง่ายขึ้นแต่ขาด unread responses feature มาเพิ่มกลับมา
The Endpoint
เราจะสร้าง endpoint หา unread responses สำหรับ user เราอยาก alter current state ของ page หลังจาก rendered ดังนั้นเราจะ request JSON จากมัน
# app/controllers/unread_responses_controller.rb class UnreadResponsesController < ApplicationController def index @article = Article.find(params[:article_id]) @responses = @article.unread_responses_for(@current_user) end end# app/views/unread_responses/index.json.jbuilder json.array! @responses do |response| json.extract! response, :id end# config/routes.rb Rails.application.routes.draw do resources :articles do resources :responses resources :unread_responses end endEndpoint ของเราจะผลิต list ของ unread response IDs
# GET /articles/1/unread_responses.json [{"id":1},{"id":2},{"id":3}]Tip: เมื่อ load dynamic component ที่สามารถ pre-render บน server มักเร็วกว่าที่จะทำ HTML rendering server-side แล้ว inject HTML เข้า page โดยตรงผ่าน JavaScript
Showing Unread Responses
แทนที่จะ hardcode URL สำหรับ unread responses endpoint ใน JavaScript code เราจะเพิ่มมันใน data attribute ใน view เพื่อ refer ภายหลัง
# app/views/articles/show.html.erb # ... หลังจาก page โหลดเสร็จ เราจะ request unread response IDs จาก endpoint ใหม่ เราจะใช้ data จาก list นั้น mark แต่ละ article ว่า unread โดยเพิ่ม CSS class
// app/assets/javascripts/application.js document.addEventListener("turbolinks:load", function () { responses = document.getElementById("responses"); if (!responses.dataset.loaded) { Rails.ajax({ url: responses.dataset.url, type: "GET", success: function (data) { responses.dataset.loaded = true; data.forEach(function (response) { element = document.querySelector( "[data-response-id='" + response.id + "']" ); element.classList.add("unread"); }); }, }); } });เพราะ Rails application ของเราใช้ Turbolinks เราจะรอ page loaded โดย listen สำหรับ event turbolinks:load เมื่อ event fire เราจะหา responses box ด้วย ID
เราจะ check ถ้า responses element มี loaded data attribute เราจะ set attribute นี้หลัง update unread responses ภายหลัง ดังนั้นเราไม่ทำ extra requests ถ้า page ถูก reload ด้วย back button ของ browser
ใน first load loaded attribute ยังไม่ set ดังนั้นเราจะ continue ทำ request ไป endpoint เมื่อ success เราจะ loop over แต่ละ article ใน returned result หา response's element ด้วย ID และเพิ่ม "unread" CSS class ให้มัน
Sprinkles!
Fragment caching reusable content และ sprinkling JavaScript บน pages เพื่อเพิ่ม user-specific dynamic bits ภายหลัง สามารถทำให้ app's initial response time เร็วขึ้นโดย serve ส่วนใหญ่ของ important content ตรงจาก cache แม้จะใช้เวลาทำ extra request และ update page หลัง main content loaded deferring dynamic content จะทำให้ app 感覺 snappier แม้ full request จะนานกว่า serve ทุกอย่างรวม user-specific parts จาก cache
สำหรับ setups ที่ elaborate กว่า ตรวจสอบ Stimulus JavaScript library ที่ wrap sprinkles pattern เข้า framework ที่ tie HTML views ไปกับ JavaScript
หวังว่าคุณชอบ introduction นี้ของ JavaScript sprinkles ใน Rails applications ถ้ามีความคิดเห็นเกี่ยวกับบทความ blog หรือ topics อื่นๆ ที่อยากให้ cover โปรด drop us a line at @AppSignal