静的サイトをturbolinksで高速化したのでTipsを紹介する

もともとRuby on Railsの一部だったturbolinksですが、Rails5からはturbolinksは独立し、Rails以外のプロジェクトでも使えるようになりました。そこで、静的サイトでも使ってみることにしました。かなり体感速度が上がったので、オススメです。

# turbolinksとは？

- [turbolinks](https://github.com/turbolinks/turbolinks)

turbolinksとは、ページの移動をすることなく、`body`内のデータの入れ替えと、`head`内のデータのマージを行うことで、表示を高速化するライブラリです。
ページの移動をしないため、新たにJSファイルやCSSファイルのダウンロードやキャッシュの確認もすることがなく、かなり速く感じます。

## turbolinksのメリット

- 表示が速い
- ページがキャッシュされるので一度表示したページは高速に表示される

## turbolinksのデメリット

- jQueryのライブラリで動かないものがある可能性がある(`$(document).ready`でのみ評価するものとか)
- 戻るボタンで戻ったら動かなくなるものが（略
- `window`, `document`オブジェクトは変更されないので、そこを意識する必要がある

とはいえ、対処できないものはあんまりないと思います。

# インストール

## npm

turbolinksはnpmで提供されているので、導入も簡単です。

```bash
$ npm install turbolinks --save
```

es2015とかで使う場合はrequireしておく必要があります。

```js
const Turbolinks = require("turbolinks");
Turbolinks.start();
```

# jQueryユーザーのためのturbolinks入門

## イベントリスナー

### $(document).readyイベント

turbolinksを使うと、画面は1度しかロードされないので、`$(document).ready`が一度しか実行されません。
そのため、ページを移動するとそれまで存在しなかったDOMに対してのイベント類が設定されません。

turbolinksは独自にイベントリスナーがあるので、それを使います。
`$(document).ready`の代わりに、`document.addEventListener('turbolinks:load', function(event){});`を使います。


```js:jQueryのみの場合
$(document).ready(function() {
    $(".foo").on('click', function(event) {
        console.log("clicked!");
    });
});
```

```js:turbolinks版
document.addEventListener('turbolinks:load', function(event) {
    $(".foo").on('click', function(e) {
        console.log("clicked!");
    });
});
```

### $(window).loadイベント

windowオブジェクトも変更されないので、このイベントも1度しか実行されません。
よって、なるべく使わないように変更したほうがいいでしょう。

### turbolinksのイベント一覧

loadだけでなく、turbolinksには多くのイベントがあります。
それをうまく使えば、デメリットに書いていた部分はほぼほぼ解消できると思います。

| イベント名 | 説明 |
|:--|:--|
|turbolinks:click| リンクをクリックしたときのタイミングで実行される |
|turbolinks:before-visit| リンク先に移動する前に実行される |
|turbolinks:visit|リンク先に移動しようとしたらすぐ実行される|
|turbolinks:request-start|ページを取得するためのネットワーク通信を始めるときに実行される。ヘッダにデータを追加する必要があるときなどはここで処理する。戻るボタンだと発火しない（キャッシュしている場合）|
|turbolinks:request-end|ネットワーク通信が終了したら実行される。通信エラーなどはここで処理する。戻るボタンだと発火しない（キャッシュしている場合）|
|turbolinks:before-cache|ページがキャッシュされる前に実行される。キャッシュされるとDOMは残るけれどイベントが解除されてしまうので、不要なDOMの削除とかはここでしておく|
|turbolinks:before-render|ページがレンダリングされる前に実行される|
|turbolinks:render|ページがレンダリングされたら実行される。2度呼ばれることがある。キャッシュ表示で1度目。実際にデータを取得して2度目|
|turbolinks:load|ページが表示されたタイミングで1度目が実行される。あとは移動するたびに実行される。戻るボタンで遷移しても実行される|

静的なサイトの場合は

- `turbolinks:load`
- `turbolinks:before-cache`

くらいしか使いませんでした。

## JS, CSSの置き場はheadタグ内に

CSSはともかくとして、JSファイルの読み込みはbodyタグを閉じる直前に書くのが一般的になってます。
これは、htmlのレンダリングをJSのダウンロードよりも先に行わせるためのテクニックですが、turbolinksを使う際にはこれではいけません。

```html:turbolinksを使わない場合
<body>
  ...
  <script src="/assets/application.js"></script>
</body>
```

turbolinksを使う場合、`body`タグ内にJSファイルを置くと、毎回ダウンロード・実行されてしまい、高速化どころか遅くなりますし、挙動がおかしくなります。
`head`内に移動させましょう。

```html:turbolinksを使う場合
<head>
  <script src="/assets/application.js"></script>
</head>
<body>
  ...
</body>
```

## windowオブジェクトへのイベント定義について

turbolinksを使うと、`window`, `document`オブジェクトは変更されません。
そのため、これらのオブジェクトに対して`turbolinks:load`のタイミングでイベント設定をしていたら、ページを移動するたびにイベントを設定してしまうことになります。

```js:jQueryだけの頃
// 基本的に1度しか実行されなかったのでこれでもよかった
$(document).ready(function(){
    $(window).scroll(function(e) {
        // スクロールしたら何かやる
        console.log("scroll");
    });
});
```

```js:ダメな例
//　ページ遷移する度にwindowに対してイベントが設定されるので絶対にダメ
document.addEventListener('turbolinks:load', function(event) {
    $(window).scroll(function(e) {
        // スクロールしたら何かやる
        console.log("scroll");
    });
});
```

これはそもそも、`$(document).ready`のタイミングで定義する必要のないものなので、イベントリスナーの中で定義するのをやめましょう。

```js:正解
$(window).scroll(function(e) {
    // スクロールしたら何かやる
    console.log("scroll");
});

document.addEventListener('turbolinks:load', function(event) {
    // 他の処理
});
```

### $(document).on も同様

実は`$(document).on`の定義も、`$(document).ready`の中でする必要はありませんでした。なので、これらも外に出します。

```js
// 動的に追加する要素に対してのイベント設定はturbolinks:loadで設定しない
$(document).on('click', '.item', function(event) {
    console.log("item clicked!")
});

document.addEventListener('turbolinks:load', function(event) {
    $(".foo").on('click', function(event) {
        // turbolinks:loadで設定するのは静的な要素に対してのみ
        console.log("foo clicked!");
    });
});
```

# まとめ

静的なサイトに関していえば、これくらいで十分でした。
また何かTipsがあったら追加していきます。