Turbolinks5についてまとめてみる

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

前に書いた[turbolinks-classic](https://github.com/turbolinks/turbolinks-classic)の記事は[こちら (Turbolinksをオフしないためにやった事)](http://qiita.com/saboyutaka/items/bcc0966313c6f7399a6e)。

READMEの翻訳は[（未完成）Turbolinks 5 のREADME.mdを日本語訳してみる](http://qiita.com/takeyuweb/items/c52c8cdfc169b64fea6e)を参照してください。

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

## 実装
* https://github.com/turbolinks/turbolinks
  * TurbolinksのJavascript実装。coffeeで書かれてる。
* https://github.com/turbolinks/turbolinks-rails
  * TurbolinksのRailsのサーバーサイド実装のgem。redirect時のheader書き換えとかやってる
  * dependencyにturbolinks-source-gem
* https://github.com/turbolinks/turbolinks-source-gem
  * turbolinks.jsをRailsで読み込むためのgem。`lib/assets/javascript`にjsがおいてあるだけ。

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

## Turbolinksを組み込むためには
```js
document.addEventListener("turbolinks:load", function() {
  // ...
})
```
* documentに`"turbolinks:load"`イベントをlistenするようにする。turbolinksのイベントはdocumentで発火される。

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


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


#### advance visit
<img src="https://camo.githubusercontent.com/17c33fd7fe4e7a747d505b926f1a429efcf67be5/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f747572626f6c696e6b732d646f63732f696d616765732f616476616e63652e737667">


#### replace visit
<img src="https://camo.githubusercontent.com/9962ffa357e8cb009b46dd706b277d338952ae96/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f747572626f6c696e6b732d646f63732f696d616765732f7265706c6163652e737667">


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

### Restoration Visits
<img src="https://camo.githubusercontent.com/45a9333fdbfc41bfe962740cca86359a570fdfb8/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f747572626f6c696e6b732d646f63732f696d616765732f726573746f72652e737667">

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

### Disabling Turbolinks on Specific Links
* `data-turbolinks="false"`をつける


## Javascriptでの描画の注意点
公式ドキュメントの[Making Transformations Idempotent(冪等性の確保)](https://github.com/turbolinks/turbolinks#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(冪等性の確保)](https://github.com/turbolinks/turbolinks#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)](https://developer.mozilla.org/en-US/docs/Web/API/Node/cloneNode)を使用している。これはイベントはコピーされない。

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


### キャッシュさせる前に処理を差し込みたい時
```js
document.addEventListener("turbolinks:before-cache", function() {
  // ...
})
```

* `"turbolinks:before-cache"`を使用する。フォームの初期化や表示のリセットなど

## elementの永続化
```html
<div id="cart-counter" data-turbolinks-permanent>1 item</div>
```

* Turbolinksで遷移した時にelementを変化させない事が可能
* 商品を追加して、ページバックした時にカートのカウントを戻さないようにする


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

### プレビュー表示時に処理を挟む
```js
if (document.documentElement.hasAttribute("data-turbolinks-preview")) {
  // Turbolinks is displaying a preview
}
```
* リアルタイムに表示されるべき内容とかはここで、loading spinner出すとかblurするとかありそう

### プレビューさせない
```html
<head>
  ...
  <meta name="turbolinks-cache-control" content="no-preview">
</head>
```
ページごとにpreviewをさせないことも可能。

## Progress bar
```css
.turbolinks-progress-bar {
  height: 5px;
  background-color: green;
}
```

* Turbolinks5ではデフォルトでプログレスバーが実装されている
* cssで表示を変更可能。非表示にするときは`visibility: hidden;`
* 500ms以上かかる場合に表示される

## Turbolinks Rails エンジン
[turbolinks-rails](https://github.com/turbolinks/turbolinks-rails)

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

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

#### GET以外のメソッド時のredirectの最適化
* POSTリクエスト後のredirect_toをした時にlocationを書き換える