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

Chuyển Turbolinks từ ác mộng thành lợi thế trong Rails Applications

Turbolinks - có lẽ một trong những từ bị ghét nhất trong vũ trụ Rails.

Có lẽ bạn thử nó. Include turbolinks trong project mới hoặc app hiện có. Và sớm thôi app fail cách kỳ lạ và wonderful. Khổ nỗi fix dễ như ăn kẹo - tắt turbolinks.

...nhưng một số công ty làm nó chạy. Ở Honeybadger, chúng tôi làm nó chạy - và chúng tôi không phải thiên tài.

Câu trả lời đơn giản đến nỗi tôi gần như ngại nhắc. Nhưng sau khi talk về chủ đề này tại Ruby Nation và Madison+Ruby, dường như mọi người thấy topic hữu ích. Vì vậy hãy đào sâu.

Chuyển Turbolinks từ ác mộng thành lợi thế trong Rails Applications

Turbolinks và PJAX hoạt động về cơ bản giống nhau. Chúng quá giống, tôi sẽ chỉ nói PJAX từ giờ. :)

Bạn có thể hiểu PJAX qua hai page requests. Lần đầu user request page, nó served như bất kỳ "traditional" Rails page nào. Nhưng khi user click PJAX-enabled link, điều đặc biệt xảy ra. Thay vì reload hoàn toàn page, chỉ portion của page được update. Được thực hiện qua AJAX.

Điều này cho chúng ta nhiều lợi ích của single page app, tránh một số khó khăn:

  • PJAX apps thường mượt như single page apps, vì page không reload hoàn toàn mỗi request.
  • Bạn dùng cùng stack cho front-end và back-end development
  • PJAX apps degrade gracefully mặc định khi user disable JS
  • PJAX apps dễ làm accessible và SEO friendly hơn

Nhiều library làm heavy lifting PJAX cho bạn. Turbolinks có lẽ nổi tiếng nhất. Setup chỉ việc include turbolinks gem trong Gemfile:

gem 'turbolinks' 

...và include JS trong app/assets/javascripts/application.js

//= require turbolinks 

Bây giờ reload app, mọi internal link thành turbolink. Khi click, trang mới request via AJAX và chèn vào document hiện tại.

Implementing jquery-pjax

Ở Honeybadger, chúng tôi dùng PJAX library gốc phát triển bởi Github. Nó cần config nhiều hơn Turbolinks, nhưng cũng flexible hơn.

Thay vì assume tất cả links là PJAX, nó cho bạn kiểm soát. Nó cũng cho bạn kiểm soát đâu trên page PJAX content sẽ được chèn.

Điều đầu tiên cần làm là thêm container vào HTML

<div class="container" id="pjax-container"> Go to <a href="/page/2">next page</a>. </div> 

Bây giờ setup PJAX link:

$(document).pjax('a', '#pjax-container') 

Cuối cùng, bảo Rails không render layout trên PJAX requests. Phải làm gì đó kiểu này, hoặc bạn sẽ có duplicate headers/footers. i.e. website trông như Inception.

def index if request.headers['X-PJAX'] render :layout => false end end 

Không dễ như vậy!

Ok, hơi phức tạp hơn tôi nói. Chủ yếu vì một hố khổng lồ ít người nói tới.

Khi DOM không bị clear mỗi page load, nghĩa là JS có thể chạy trên traditional Rails app giờ break cách kỳ lạ.

Lý do là nhiều người học viết JS cách khuyến khích accidental naming conflicts. Kẻ thủ phạm insidious nhất là simple jquery selector.

// Tôi trông ngây thơ, nhưng tôi không! $(".something") 

Viết JS cho pages không reload

Conflicts là vấn đề số một khi viết JS cho pages không bao giờ reload. Những vấn đề kỳ lạ, khó debug nhất xảy ra khi JS manipulate HTML nó không bao giờ meant to touch. Ví dụ, xem ordinary jQuery event handler:

$(document).on("click", ".hide-form", function() { $(".the-form").hide(); }); 

Điều này perfectly reasonable, nếu chỉ chạy trên một page. Nhưng nếu DOM không bao giờ reload, chỉ vấn đề thời gian ai đó thêm element khác với class .hide-form. Bây giờ bạn có conflict.

Conflicts như này xảy ra khi bạn có nhiều global references. Và làm sao giảm global references? Bạn dùng namespaces.

Namespacing Selectors

Trong Ruby class dưới, dùng một global name - class name - hide nhiều method names.

# One global name hides two method names class MyClass def method1 end def method2 end end 

Mặc dù không có built-in support cho namespacing DOM elements (ít nhất cho đến ES6 WebComponents) nhưng có thể simulate namespaces với coding conventions.

Ví dụ, tưởng tượng implement tag editing widget. Không namespacing, trông như sau. Lưu ý có ba global references.

// <input class="tags" type="text" /> // <button class="tag-submit">Save</button> // <span class="tag-status"></span> $(".tag-submit").click(function(){ save($(".tags").val()); $(".tag-status").html("Tags were saved"); }); 

Tuy nhiên, bằng cách tạo "namespace" và làm mọi element lookups relative to it, giảm global references xuống một. Chúng ta cũng loại bỏ vài classes.

// <div class="tags-component"> // <input type="text" /> // <button>Save</button> // <span></span> // </div> $container = $("#tags-component") $container.on("click", "button" function(){ save($container.find("input").val()); $container.find("span").html("Tags were saved"); }); 

Tôi nói nó đơn giản.

Trong ví dụ trên, tôi namespace DOM elements bằng cách đặt chúng trong container class "tags-component". Không có gì đặc biệt về tên đó. Nhưng nếu tôi adopt naming convention mọi namespace container có class kết thúc bằng "-component", điều thú vị xảy ra.

Nhận ra incorrect global selectors tại một cái nhìn

Nếu global selectors duy nhất bạn cho phép là components, và tất cả components có class kết thúc "-component", bạn thấy ngay nếu có bad global selectors.

// Bad $(".foo").blah() // Ok $(".foo-component".blah() 

Dễ tìm JS điều khiển HTML

Hãy revisit interactive tag form. Bạn viết HTML cho form. Bây giờ cần thêm JS và CSS. Nhưng đặt files đó đâu? May mắn, khi có naming scheme cho namespaces, nó dịch dễ dàng sang naming scheme cho JS và CSS files. Directory tree trông như sau.

. ├── javascripts | ├── application.coffee │ └── components │ └── tags.coffee └── stylesheets ├── application.scss └── components └── tags.scss 

Automatic initialization

Trong traditional web app, bình thường init tất cả JS on page load. Nhưng với PJAX và Turbolinks, elements được thêm/xóa từ DOM liên tục. Vì vậy, rất beneficial nếu code tự detect khi components mới enter DOM, và init JS cần thiết on the fly.

Consistent naming scheme, làm điều này dễ dàng. Có vô số approaches auto-initialization. Đây là một:

Initializers = { tags: function(el){ $(el).on("click", "button", function(){ // setup component }); } // Other initializers can go here } // Handler called on every normal and pjax page load $(document).on("load, pjax:load", function(){ for(var key in Initializers){ $("." + key + "-component").each(Initializers[key]); } } 

Làm CSS tốt hơn!

JavaScript không phải nguồn conflict duy nhất. CSS có thể tệ hơn! May mắn, namespacing system cũng làm viết CSS không conflict dễ dàng hơn. Thậm chí nicer trong SCSS:

.tags-component { input { ... } button { ... } span { ... } }