turbolinks
Rails5

Turbolinks5についてまとめてみる

More than 1 year has passed since last update.

Rails5と同時にリリースされたTurbolinks5についてまとめてみます。

前に書いたturbolinks-classicの記事はこちら (Turbolinksをオフしないためにやった事)

READMEの翻訳は(未完成)Turbolinks 5 のREADME.mdを日本語訳してみるを参照してください。

Features

  • ページ間の遷移をXHR化し、bodyだけを変えることでレンダリングコストを下げる。js, cssの読み込みを最小限にする。
  • 1度訪れたことのあるページに再度訪れるときにキャッシュから前回の表示を一度表示し、その間にリクエストを送り、レスポンスが返ると最新の状態を表示する(プレビュー)
  • 戻るボタンなどで1度訪れたことのあるページに遷移するときにキャッシュがあればリクエストなしに遷移する。
  • Railsに依存しなくなったので、他の言語でも使用可能に。
  • ついでにjQueryにも依存していない。turbolinks-classicではjQueryが入っている前提でドキュメントが書かれていた。
  • iOS, Androidをサポート
    • 自分がiOS, Android環境で確認してないので今回は省略。分かる人いたら記事書いてください!

実装

Navigating with Turbolinks5

  • 同じサブドメインの a href のclickで動作する
  • bodyを置き換え、headのコンテンツをマージ
  • window, document, html要素は維持される
  • ページのfull reloadが行われないのでjsを書くときに注意する

Turbolinksを組み込むためには

document.addEventListener("turbolinks:load", function() {
  // ...
})
  • documentに"turbolinks:load"イベントをlistenするようにする。turbolinksのイベントはdocumentで発火される。

Visits

Turbolinksの遷移の1つひとつをvisitという概念で表現する。

Application Visits

  • linkをクリックすることで行われるvisit。新しいページを読み込み遷移する。
  • すでに遷移先のページがキャッシュにある場合はそのキャッシュをプレビューとしてレンダリングし、xhrリクエストを発行、レスポンスが返るとそのHTMLを描画する。
  • 遷移が成功すると遷移前のページをキャッシュする

advance visit

replace visit

  • advance, replaceがある
  • advance はhistory.pushStateを使ってブラウザの履歴スタックに新しいエントリを追加する。
  • replaceは一番上の履歴スタックを破棄し、新しいhistoryで置き換える
  • replace visitを作動するようにするにはリンクにdata-turbolinks-action="replace"と入れる

Restoration Visits

  • 戻るボタンをおした時などに動作、ブラウザのhistoryにあるページに遷移する(戻る)
  • キャッシュがあれば即座にhistoryのキャッシュを使用しレンダリング、なければリクエストしレンダリングする

Disabling Turbolinks on Specific Links

  • data-turbolinks="false"をつける

Javascriptでの描画の注意点

公式ドキュメントのMaking Transformations Idempotent(冪等性の確保)の章で書かれている内容。

turbolinks-classic では on page:load 以下に処理を記入するのが一般的だった。page:loadは現在のApplication Visitが行われる時に発火するイベント。

Turbolinks 5以降では on turbolinks:load 以下に処理を記入する。この turbolinks:load は Application VisitとRestoration Visits 両方で発火する。

そのため、ページを遷移し戻るボタンで戻ってきた時に再度イベントが発火してしまう。この時にJavascriptでDOMを描画していたりすると、2重に実行され意図しない挙動になることがある。turbolinks:load以降の処理は冪等性を考慮して書く必要がある。
Making Transformations Idempotent(冪等性の確保)では、data attributeを使って実行済みか判断するテクニックや、生成されるDOMを捜査し存在するかどうかの条件分岐を入れるテクニックが紹介されている。後者がよりシンプルで堅牢であると紹介されている。

DOMにバインドされているeventに関しては、キャッシュにも記述するが、Application Visit時にcloneNode(true)を使用しDOMをキャッシュする。これはイベントはコピーされない。そしてページを戻る際にRestoration VisitsがキャッシュからDOMを復元、turbolinks:loadで再度バインドし直す。そのためあまり意識せずに再度イベントをバインドすることができている。

キャッシュ

キャッシュの目的

  • restoration visitでネットワークリクエストなしにレンダリングするため
  • application visitでpreviewとしてレンダリングするため
  • キャッシュからDOMを生成するのにcloneNode(true)を使用している。これはイベントはコピーされない。

キャッシュのクリア

  • 明示的にTurbolinks.clearCache()を呼ぶことでキャッシュをクリアできる。
  • GET以外のリクエストでredirectが発生した時はTurbolinks.clearCache()が呼ばれ、キャッシュがクリアされる。POSTやPUT, DELETEで表示が変わる可能性があるためTurbolinksがそうしてる。(turbolinks-rails)
    • ユーザーがログインした場合は、キャッシュクリアされるのでヘッダーなどが変わってても心配ない
    • カートに商品を追加した時もキャッシュクリアされるので問題ない。

キャッシュさせる前に処理を差し込みたい時

document.addEventListener("turbolinks:before-cache", function() {
  // ...
})
  • "turbolinks:before-cache"を使用する。フォームの初期化や表示のリセットなど

elementの永続化

<div id="cart-counter" data-turbolinks-permanent>1 item</div>
  • Turbolinksで遷移した時にelementを変化させない事が可能
  • 商品を追加して、ページバックした時にカートのカウントを戻さないようにする

プレビュー

application visit時にすでに遷移先のページがキャッシュにある場合はそのキャッシュをプレビューとしてレンダリングし、同時にxhrリクエストを発行、レスポンスが返るとそのHTMLを描画する仕組み。previewを使うことでユーザーへのレスポンスを高速化し、ユーザー体験を向上させる前回のキャッシュされたページがレスポンスが返るまで表示されるので、最新のページの状態との差異が発生する可能性があるのでその点は注意する必要がある。

プレビュー表示時に処理を挟む

if (document.documentElement.hasAttribute("data-turbolinks-preview")) {
  // Turbolinks is displaying a preview
}
  • リアルタイムに表示されるべき内容とかはここで、loading spinner出すとかblurするとかありそう

プレビューさせない

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

ページごとにpreviewをさせないことも可能。

Progress bar

.turbolinks-progress-bar {
  height: 5px;
  background-color: green;
}
  • Turbolinks5ではデフォルトでプログレスバーが実装されている
  • cssで表示を変更可能。非表示にするときはvisibility: hidden;
  • 500ms以上かかる場合に表示される

Turbolinks Rails エンジン

turbolinks-rails

Turbolinksは言語に制限されなくなったので、Rails以外の言語、Webフレームワークでも使える。各言語ごとにエンジンの実装は必要。

やってること

Redirect

  • redirectの追従だけはクライアントサイドだけで行うことは出来ないのでサーバー側でTurbolinks-Locationヘッダを返すことで、ブラウザの一番上の履歴のlocationを置き換える。

GET以外のメソッド時のredirectの最適化

  • POSTリクエスト後のredirect_toをした時にlocationを書き換える