× 說心事 政治時事 生活 運動 汽車 機車 自行車 相機 科技 理財 旅遊美食 娛樂 健康 美麗時尚 人際關係 文學故事 關於《思書》
吃喝玩樂 關於《思書》

Rails Turbolinks™ 5 深度研究

紅寶鐵軌客
來關注...
關注:紅寶鐵軌客
關注有什麼好處?:當作者有新文章發佈時,「思書日報」就會匯總自動通知您,讓您更容易與作者互動。
x
紅寶鐵軌客
Rails 使用中,折磨中,享受中......
Victorian vintage round cut ruby engagement ring in 14k yellow gold 7mm center vs g h 984
    很久以前就是個「寫程式的」,其實,什麼程式都不熟⋯⋯
     
    就,這會一點點,那會一點點⋯⋯
作者最新發佈的文章:
  • Rails 如何檢查 URL 是否存在?
  • Rails 為什麼要使用 escape_javascript?
  • Rails 使用 Javascript Ajax 很簡單
作者最近參與的討論:
  • Rails 常見資安陷阱與解決方法
現在就加入《思書》,你就可以關注本作者了!
《思書》是一個每個人的寫作與論壇平台,特有的隱私管理,讓你寫作不再受限,討論更深入真實,而且免費。 趕快來試試!
還未加入《思書》? 現在就登錄! 已經加入《思書》── 登入
Rails 使用中,折磨中,享受中......
2017/10/13
112   0  

不管你是愛、還是恨(說得有點超過),但是真的很多 Rails 人乾脆把 Turbolinks 關掉了。

我還是繼續使用中...

Turbolinks 5.0 的使用與生命週期

每一個使用 JavaScript 的,window 跟 document objects load 都是一個很重要的起始點,但是在 Turbolinks 中,這兩個 object 的狀態變的不確定,所以如果是用:window.onloadDOMContentLoaded 或是 jQuery 的 ready 就要改成

document.addEventListener("turbolink:load", function() {
  // ...
})

這也是大家會在網路上找解答時,最多的答案,但是,很快,就會發現,在很多時候,這還是有問題,這並不能完全取代 window.onloadDOMContentLoaded 或是 jQuery 的 ready

Turbolinks 基本上就是把 <a href> 給改道了,所以當使用者按了同站內的連結後,他開始做網頁拼圖,這個改變,也造成了兩種新的提取 (Loading)不同,這在以後 Rails 中使用 Javascript ,造成了很大的問題。

這兩種新的提取就是:(及,它的基本行為)

  1. application visit - 基本上是指使用者按了網頁中的一個連結,前進到這個網頁,與提取新的網頁
    1. 發出一個網路的需求:request a network request
    2. 如果有 cache,就先把它 render 出來:render a preview of the page from cache
    3. 移到先前 cache 的停留點:scroll to the anchored element
    4. 等到 server 回應內容,就再把它 render 出來:response arrived, renders HTML
    5. 改瀏覽器的歷史紀錄:change to the browser’s history
  2. restoration visit - 大部分是使用者按了倒退鍵,回復到舊的網頁,或是類似的行為
    1. 把 cache 的內容 render 出來:render a copy of the page from cache without making a request
    2. 回到先前 cache 時的停留點:returns to this saved position

用文字說明真的不容易懂,所以就實際上做了個 javascript 來 log 追蹤 Turbolinks 的行為,就用以下這簡單的程式來追蹤:

$(document).on('turbolinks:load', function() {
console.log("turbolinks:load");
});
...
$(document).on('turbolinks:before-visit', function() {
console.log("turbolinks:before-visit");
});

以下是一個 Application visit 時的 tracking:

turbolinks:click
turbolinks:before-visit
turbolinks:request-start
turbolinks:visit
turbolinks:request-end
turbolinks:before-cache
turbolinks:before-render
turbolinks:render
turbolinks:load

以下是一個另一個 Application visit 時的 tracking,差別是這個頁面有拜訪過,也就是有 cache 過,也有使用很大的 Javascript code,執行比較久,這個 JS code 也會改變頁面內容: 

turbolinks:click
turbolinks:before-visit
turbolinks:request-start
turbolinks:visit
turbolinks:before-cache
turbolinks:before-render
turbolinks:render
turbolinks:request-end
turbolinks:before-render
turbolinks:render
turbolinks:load

以下是一個 restoration visit (按倒退鍵),原先網頁的內容都沒變

turbolinks:visit
turbolinks:before-cache
turbolinks:before-render
turbolinks:render
turbolinks:load

以下是一個 restoration visit (按倒退鍵),但是,原來網頁的內容已經改變了

turbolinks:request-start
turbolinks:visit
turbolinks:request-end
turbolinks:before-cache
turbolinks:before-render
turbolinks:render
turbolinks:load

以下是一個 restoration visit (按前進鍵),原先網頁的內容都沒變。

turbolinks:visit
turbolinks:before-cache
turbolinks:before-render
turbolinks:render
turbolinks:load

只按 Refresh,也就是 page reload,就只有

turbolinks:load

這些事件的名稱就如同它的行為,在 Turbolinks 的文件中也有很清楚的說明,從這些事件中,我們可以觀察到 Turbolinks 在 application 和 restoration visit 中,還是有很大的不同,其中,最重要的關鍵就是有沒有 cached 的內容,而會造成很大困擾的就是到底什麼樣的內容被 cached 了,這跟它 cache 的時間點有很大的關係,而它的時間點就顯示在 turbolinks:before-cache,這個事件會在 turbolinks 要開始 cache 之前觸發,所以只要仔細看這個事件的發生點,就知道 Turbolinks 存的內容是什麼了。

Application visit 中,有 cache 與無就有不同,無 cache 時,會 cache 的內容是 server 回應的內容,但是有 cache 時,就是 server 回應前的內容。 Restoration visit,也就是一般按了倒退鍵時,Turbolinks 也會盡量只讀 cache 而不再從 server 要資料,這在 application visit 中也是先用 cache,再等資料回來時改頁面,這都很符合 Turbolinks 的基本精神,先顯示 cache 內容,讓使用者感覺網頁速度很好,再慢慢等 server 資料回來,再改內容 。 以上的事件的列表,應該也很清楚的說明 Turbolinks 的行為,與它如何做網頁拼圖。  在 Turbolinks 文件中,還有幾個重點:

  1. 基本上 Turbolinks 會自動加入 head 內新增的 <script> 內容,evaluate 它,但只會改變 <body> 內的跟新的內容及加入新的
  2. Cache 是使用 cloneNode(true),也就是說所有的事件 event listeners 跟相關資料不會儲存。

我還找到另一篇很棒的介紹,值得一看:

TURBOLINKS' LIFECYCLE EXPLAINED

基本上這好文就是把 turbolink 的生命週期用另一中方式說明,我把重點筆記如下:

  1. VISITING AN UNCACHED PAGE
    1. HTTP/AJAX
    2. turbolinks:before-cache 
    3. load cached content
    4. turbolinks:before-render
    5. load HTTP/AJAX content
    6. turbolinks:render
    7. turbolinks:load
  2. VISITING A CACHED PAGE
    1. HTTP/AJAX
    2. turbolinks:before-cache
    3. turbolinks:before-render
    4. replace the body and header tags with the cached content loaded from the server
    5. turbolinks: render
    6. if AJAX do return content...
      1. turbolinks:before-render (for the cached version)
      2. turbolinks:render
    7. turbolinks:load

這篇文章建議了一些在使用 react 時,可以使用的不同事件,我不是很同意,但還是筆記了一下: 

如果 setup / teardown 執行後的 DOM 行為怪異,就用以下的方式:

  • document.addEventListener('turbolinks:load', this.setup(), {once: true})
  • document.addEventListener('turbolinks:render', this.setup())
  • document.addEventListener('turbolinks:before-render', this.teardown())

 

如果 setup / teardown 執行後的 DOM 行為還好,很正常,你也不在乎 cache 的內容與 javascript 的互動,就用以下的方式:

  • document.addEventListener('turbolinks:load', this.setup())
  • document.addEventListener('turbolinks:before-cache', this.teardown()

不過,我覺得真的沒那麼簡單,很多時候應該也只能試試看,當一個 javascript 在 Turbolinks 中執行有詭異行為時,非常大的機率就是因為 cache 的內容與 server 上的不同,這在 Turbolinks 的文件中也有一段說明或要求,要求加掛在 Turbolinks 上的程式,必須要是:Idempotence,一個難懂的字,這基本上是一個數學與電腦科學的專用字,是指這個程式或是算數式必須要每次執行結果都一樣,不管起始內容如何!

一定要知道的 Idempotence

這樣的要求很簡單合理嗎?其實不容易做到,基本上,只要任何一個程式執行後,把原先參考的資料改了,就很有可能會錯,更討厭的是,如果你的 javascript 程式是自己開發,那還容易做到Idempotence,如果是在網路上找的,就辛苦了。 題外話:這也說明 Rails 的開發人員最好是 full stack,如果前端、後端加UX,光這個問題會很難溝通。

把這個 Idempotence 難題再加困難一點,別忘了 Turbolinks 有兩種 visit,除了往前走,還有 Restoration visit,也就是使用者按倒退,這時,大部分的機率 Turbolinks 會直接把 cached 的內容抓出來,不會再到 server 上讀,如果你的網頁內容是被 javascript 改過的,特別是改了 Javascript 參考的 DOM,恭喜,你的程式一定會變的行為很詭異,這樣的實例太多了:Javascript 改時區時間顯示、新增或移動了 DOM 的位置與內容......

山窮水盡疑無路,繞開 Turbolinks 吧

是的,Turbolinks 的美,來自 Cache,但是麻煩也因它而起!

以上這個 Idempotence 的解法,是 Turbolinks 使用的正解,但是,有時就是真的很難做到,那有沒有必殺技? 沒有,但是可以兩個爛方法解決真的山窮水盡時:

  1. 關掉 Turbolinks cache,其實就是在那一網頁把 Turbolinks 給關了。
  2. Reload 網頁。 

我知道這兩個方法很爛,如果要用者兩個方法,那不等於把 Turbolinks 關掉? 但,也不全那麼爛啦,因為可以只關掉部分有問題的頁面,而且,當山窮水盡時,這可以救急。 

要如何關掉 Turbolinks cache,我們再來看一下 cache 的行為:當 Cache 發生前,Turbolinks 會發出 turbolinks:before-cache 事件,當網頁還是顯示 cache 時的資料時,<html> 的 attribute 會加入 data-turbolinks-preview,但是我發現只有在 application visit 已經有 cache 的網頁才會有這 attribute,其他不會,可以用以下的方法知道網頁還在 cached 的內容:

if (document.documentElement.hasAttribute("data-turbolinks-preview")) {
// Turbolinks is displaying a preview
}

網頁不要使用顯示 cache 的內容,就用 no-preview,  關掉 Turbolinks 的 cache 就是 no-cache,兩個都是加在 <head> 內,以下是關掉的方法:

<head>
  ...
  <meta name="turbolinks-cache-control" content="no-cache">
</head>

在連結 <a> 要把呼叫的網頁 Turbolinks cache 關掉,在 rails 中很間單,但是有點被搞得很昏,在 Turbolinks 5.0 前,好像都是用:

<%= link_to ...., 'data-no-turbolink' => true %>

但是在 5.0 後,是要這樣寫的:

<%= link_to ...., data:{ turbolinks: "false" } %>

設成 true 就會打開。  再來,就是 assets 控制了,很多人都是用:data-turbolinks-track = true,這是 Turbolink 5.0 前,或是現在稱為 classic 的用法了,在 turbolinks 5 上面用,不會顯示問題,5.0 是用 data-turbolinks-track = "reload",但我不知道 true 會不會等於 reload,應該會吧,要是不會,很多老的 rails 程式應該會有有問題,但是好像沒聽說。

data-turbolinks-track = "reload" 是唯一在文件上看到的設定,基本上是要讓 Turbolinks 無條件 reload assets,用法如下,只要 Assets 跟設定的不同,就會 reload:

<head>
...
<link rel="stylesheet" href="/application-258e88d.css" data-turbolinks-track="reload">
<script src="/application-cbd3cd4.js" data-turbolinks-track="reload"></script>
</head>

以上是對關掉 Turbolinks cache 的說明,應該算清楚了,那,瀏覽器 reload 頁面呢? 這一篇文章說有 535種 reload 方法,我還蠻支持用這個方法繞開 Turbolinks 問題的,只要是用在確定出問題的網頁,再清楚的排除不會出問題的狀況,就還是可以在大部分的情況下保有 Turbolinks 的使用者快速感,只有在必要的頁面與出問題的狀況下 reload page,而 reload page,就跟關掉 Turbolinks 的基本行為是一樣的,當然,會再慢一點,但是,如果處理得好,影響的就會是有限的頁面。

幾的常用的 reload page 方式:

  • location.reload() 
  • location=location 
  • location.assign(location) 
  • document.execCommand('Refresh') 

不知不覺就寫了一堆,實際上是我自己想要把這些記錄下來,都是一些我認為以後會用到的設定與用法,Turbolinks 本來就不應該會好用,它基本上還是一個 SPA,如果,網頁速度感不是那麼重要,開發時間很短,關掉它會是一個正確的決定!

我遇到的 Turbolinks 問題:

以下是我將已經遇到的「怪問題」列出,希望對大家有幫助。

Form & Submit: 

一個很簡單的網頁,裡面只有一個 form,但是當我用 jQuery 的 submit( ) listen 這個 form submit 時,卻發現從別的網頁連到這頁時,jQuery submit( ) 不會觸發,但是只要 refresh 網頁後,就一切正常了,解決的方法是在連進來的連結中,把 Turbulinks 關掉,很簡單:

link_to "a Form" forms_path, data: { turbolinks: false }

 

 

關於作者
很久以前就是個「寫程式的」,其實,什麼程式都不熟⋯⋯
 
就,這會一點點,那會一點點⋯⋯


標籤: javascript on turbolinks x 1 turbolink 5 x 1 rails x 27

分享:


參與討論!
x
現在就加入《思書》,馬上參與討論!
《思書》是一個每個人的寫作與論壇平台,特有的隱私管理,用筆名來區隔你討論內容,讓你的討論更深入,而且免費。 趕快來試試!
還未加入《思書》? 現在就登錄! 已經加入《思書》── 登入