turbolinksチートシート

なんか速くなるやつ、程度の理解だったturbolinksを調べてみました。

「(ロード制御がややこしいので)静的なサイトに関してのみ使うべし」のような意見を多く見かけたのですが、
機能を俯瞰してみると本来はSPAの代替(複数ページでフロントアプリを構成する)を意図したツールのように思えました。
react-redux等と組み合わせての運用を検討してみたいところです。


## 概要

turbolinks = Ajaxによるページ遷移の高速化のためのライブラリ

- オープンソース / MITライセンス

- 導入コストが低い
    - サーバ側の特別な対応は不要
    - モバイル環境にも対応
    - ユーザ側から見て、通常のページ遷移と同じように表示される/動作する

- Rails4で公式採用された
    - バインディング(turbolinks-rails)がデフォルトでGemfileに含まれている


## 仕組み

turbolinksにおいて、リンクがクリックからページ表示までの一連の流れを__Visit__と呼ぶ。
Visitは大きく二種類:

- Application Visits = リンククリックによるページ遷移
- Restoration Visits = ブラウザの戻る/進むボタンによるページ遷移


### Application Visitsの挙動
turbolinksは以下のように動く:

- 同じドメイン内にリンクする`<a href>`タグのクリックを監視する
- クリック時、リンクによるページ遷移を妨害する
- キャッシュが存在する場合、その内容を一時的にbody上に表示する(キャッシュプレビュー表示)
- Ajax(XMLHttpRequest)で次のページを取得する
- 取得したページを、現在のページと置換える
    - `<head>`はマージする
    - `<body>`は完全に差し替える
- スクロール位置を更新する
- HistoryAPIを操作して、URLを変える(`history.pushState`.ページ遷移したように見せる)


遷移にあたって今のページと共有するリソース(css/img/js)の再読込が不要となるため、読み込みが速くなる。
なお、フックイベントを使って遷移をキャンセルできる。


### Restoration Visitsの挙動

進む/戻る場合、turbolinksはキャッシュを積極的に利用する:

- キャッシュが存在する場合は、リクエストを出すことなくページを再表示させる
- スクロール位置は記録されており、遷移時に復元される


Restoration Visitsはリンクを動的にキャンセル__できない__。


## 導入

### Rails(w/ アセットパイプライン)

Railsかつアセットパイプラインを使う場合は、`turbolinks-rails`をインストールする(最初からGemfileに含まれている)


```ruby:Gemfile
gem 'turbolinks', '~> 5'
```

```bash
$ bundle install
```

### それ以外

Rails以外、あるいはRailsでもアセットパイプラインを使わない場合はnpmパッケージとしてインストールする。

```
$ yarn add turbolinks
```


## 使い方

### 開始する

Rails(アセットパイプライン)の場合は、対象のページが読み込むjsファイルにturbolinksをrequireするだけでいい。

```js:app/assets/javascripts/application.js
//= require turbolinks
```

npmパッケージとして利用する場合は、turbolinksモジュールをimport/requireした上で`start()`を実行して開始する。

```js:app/some/other/system/of/spa.js
// Node
var Turbolinks = require("turbolinks")
Turbolinks.start()

// ES6
import Turbolinks from 'turbolinks'
Turbolinks.start()
```

### 利用可否を確認する

`supported`プロパティで、ブラウザの対応状況を確認できる。
おおむね[HistoryAPI](https://developer.mozilla.org/ja/docs/Web/API/History)に対応している環境であれば動作するらしい。

```js
if(Turbolinks.supported){
  // ...
}
```

### 無効化する

jsの再読み込みなどに関してturbolinksによるリンクに問題が生じた場合は、特定のリンクのみ、この機能を無効化できる。

無効化状態は、対象の`<a>`タグかその親のタグに、`data-turbolinks`を指定することで制御できる。


```html:直接無効化する
<a href="..." data-turbolinks="false">turbolinksを使わずにリンクする</a>
```

```html:親によって無効化する
<div data-turbolinks="false">
  <a href="...">...</a>
</div>
```

### historyスタックを無視する
`data-turbolinks-action="replace"`を指定すると、ページ遷移しても、その履歴がhistory(戻る/進むボタン)に記録されなくなる。

```html
<a href="..." data-turbolinks-action="replace">...</a>
```

### ロードイベントをとる

turbolinksの場合、ページ遷移をても`window`以下の__オブジェクトの状態が保たれる__(メモリ上に残る)。
また`window.onLoad`イベントは、最初のページの表示時以外は__呼ばれない__。

そのため変わりに、画面表示時には`turbolinks:load`イベントが発行される。
このイベントを監視することで、ページ遷移ごとの処理を実装できる。

```
document.addEventListener("turbolinks:load", function(){
    ...
})
```

### その他のライフサイクルイベントをとる

ロード以外にも、turbolinksの挙動に関わるイベントを取得できる。

イベント                    | 内容                                      | 備考
---                         | ---                                       | ---
turbolinks:load             | ページの読み込み時                        | `window.onLoad`の代替として利用
turbolinks:click            | turbolinksが有効な`<a>`タグのクリック     | `e.data.url`でURLの取得可能
turbolinks:before-visit     | 遷移の開始前(Restorationを除く)           | ページ取得のキャンセルも可能
turbolinks:visit            | 遷移の開始直後                            | -
turbolinks:request-start    | ページ取得の開始時                        | 
turbolinks:request-end      | ページ取得の終了時                        | 
turbolinks:before-cache     | キャッシュの開始前                        | 
turbolinks:before-render    | ページ表示前                              | `e.data.newBody`で新しい表示内容を取得可能
turbolinks:render           | ページ表示後                              | キャッシュプレビューが表示された場合にも呼ばれる


### 最初のページ以外では実行させない

`<script>`タグの実行(評価)のタイミングは、それが書かれた場所によって変わる。


- `<head>` 内の場合は、__その画面がはじめて表示された__タイミングで1度だけ実行される
- `<body>` 内の場合は、その画面が表示されるたびに実行される


繰り返し実行されることによる処理の重複を避けるため、`data-turbolinks-eval`属性を指定する。
falseを設定することで、その`<script>`タグは__turbolinksを開始したページの初回表示時のみ__、実行される。

```html
<script src="path/to/src.js" data-turbolinks-eval="false"></script>
```

### ページ遷移しても特定のタグを残す

`data-turbolinks-permanent`を指定すると、ページが変わってもそのタグを残すことができる。


```html
<div data-turbolinks-permanent>
  ...
</div>
```

### js/cssファイルの更新を監視する

`<head>`内に記述されたリソースは初回しか読み込まれない。
`data-turbolinks-track="reload"`を設定すると、遷移時にリソースの更新を確認し、必要に応じてリロードする。


```html
<head>
  <script src="path/to/src.js" data-turbolinks-track="reload"></script>
</head>
```

### 動的に制御する

JavaScriptによって動的にページ遷移させることもできる。


```js
// キャッシュを消す(任意)
Turbolinks.clearCache()

// ページ遷移する
Turbolinks.visit(...)
```


### プログレスバーを表示する

turbolinksによる遷移はAjax制御のため、ブラウザのプログレスバーに進捗が表示されない。
代替として、ロード状況を画面の最上部に表示させることができる。

表示させるためにはまずプログレスバーを有効化する。
(公式の説明ではデフォルト有効のことだが、呼ばないと表示されなかった)

```js
Turbolinks.enableProgressBar()
```

有効にすると遷移時に`class="turbolinks-progress-bar"`のタグが挿入されるので、このクラスのCSSスタイルを任意に設定する。

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


## 参考
- [Github - turbolinks/turbolinks](https://github.com/turbolinks/turbolinks)
- [Github - turbolinks/turbolinks-rails](https://github.com/turbolinks/turbolinks-rails)
- [npm - turbolinks](https://www.npmjs.com/package/turbolinks)
- [Rails 4のturbolinksについて最低でも知っておきたい事](http://kray.jp/blog/must-know-about-turbolinks/)
- [Turbolinks再考](http://nisshiee.hatenablog.jp/entry/2016/12/04/152803)
- [Turbolinksをオフにしないためにやった事](https://gist.github.com/saboyutaka/8727377)
- [Turbolinksでprogress barを表示する](http://y-yagi.tumblr.com/post/102313991125/turbolinks%E3%81%A7progress-bar%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B)