【Rails】Turbolinksの基本情報と実装方法

はじめに

Ruby on Railsにはページの遷移を高速化するTurbolinksという機能があります。Turbolinksは優れたJavaScriptライブラリですが、Rails 5からは標準で有効化されているため、Turbolinksを使っているということすら知らずにRailsアプリを作成している人も多いと思います。しかし、Turbolinksの仕様についてきちんと理解していないとバグを生み出す原因になりかねません。

本記事では、Turbolinksの基本的な仕様や、Turbolinksを無効化する方法についてまとめています。

Turbolinksについて

Turbolinksはページ遷移を高速化するJavaScriptライブラリです。Rails 4から正式導入され、Rails 5から標準で有効化されるようになりました。Railsアプリの一機能として語られることが多いですが、元々はJavaScriptライブラリのためRailsアプリでなくとも使うことができます。

TurbolinksはリンクのクリックイベントやWebブラウザのナビゲーションイベント(進む/戻る)を監視し、通常の遷移イベントをキャンセルします。代わりに非同期通信(XMLHttpRequest/Ajax)で遷移先のページを取得し、現在のページのheadとマージし、bodyを差し替えることでページ遷移したように見せかけます。

遷移前/遷移後のページ間でアセット(画像/CSS/JavaScript)が共通化されるため、ページ遷移が高速化されるという仕組みです。

JavaScriptの使用

Turbolinksは独自のライフサイクルイベントやAPIを備えています。これらを適切に使うことで、より便利にTurbolinksを活用することができます。

ライフサイクルイベント

JavaScriptはHTMLが完全に読み込まれる前に実行すると正常に動作しないことが多いので、大抵の処理はロードイベントのブロック内に記述します。

// Pure JavaScript
document.addEventListener('load', function(event) { });
document.onload(function(event) { });

// ES6
document.addEventListener('load', (evnet) => { });
document.onload = (event) => { });

// jQuery
$(document).ready(function(event) { });
$(document).on('load', function(event) { });

しかし、Turbolinksは通常のページ遷移をキャンセルするので、このロードイベントは最初のページが表示されるとき以外は発火されません。Turbolinks使用下では以下のようにturbolinks:loadイベントを使用する必要があります。

// Pure JavaScript
document.addEventListener('turbolinks:load', function(event) { });

// ES6
document.addEventListener('turbolinks:load', (event) => { });

// jQuery
$(document).on('turbolinks:load', function(event) { });

ライフサイクルイベントの一覧は以下の通りです。

イベント 発生タイミング 利用可能なデータ
turbolinks:click Turbolinksの有効なリンクのクリック時に発生。 url(遷移先URL)
turbolinks:before-visit ページ遷移前に発生。 url(遷移先URL)
turbolinks:visit ページ遷移後に発生。 -
turbolinks:request-start 遷移先ページを取得する前に発生。 xhr(XHRオブジェクト)
turbolinks:request-end 遷移先ページを取得した後に発生。 xhr(XHRオブジェクト)
turbolinks:before-cache 現在のページをキャッシュする前に発生。 -
turbolinks:before-render ページをレンダリングする前に発生。 newBody(新しいbody要素)
turbolinks:render ページをレンダリングした後に発生。 -
turbolinks:load 最初のページの読み込み後に1回だけ発生。 timing(遷移タイミング)

各ライフサイクルイベントで利用可能なデータには以下のようにアクセスします。以下はturbolinks:clickイベントの例ですが、他のイベントも同様の方法でアクセスすることができます。

document.addEventListener('turbolinks:click', function(event) {
  console.log(event.originalEvent.data.url);
  // => http://www.example.com/destination
});

API

JavaScriptでTurbolinksによるページ遷移を行いたい場合は以下のように記述します。actionオプションを省略すると"advance"が使用されます。

const location = '/destination';
Turbolinks.visit(location);
Turbolinks.visit(location, { action: ["advance"|"replace"] });

JavaScriptでTurbolinksのキャッシュを削除したい場合は以下のように記述します。

Turbolinks.clearCache();

JavaScriptでTurbolinksのプログレスバーが表示されるまでの時間を変更したい場合は以下のように記述します(単位はミリ秒)。デフォルトは500msです。

const delay = 800;
Turbolinks:setProgressBarDelay(delay);

JavaScriptで現在のブラウザがTurbolinksをサポートしているかを確認したい場合は以下のように記述します。

if (Turbolinks.supported) { }

Turbolinksの無効化

Turbolinksはある場面によっては無効化したい場合があります。無効化するには様々な方法がありますが、Turbolinksの恩恵を最大限に受けるために無効化する箇所は最小限に留めた方がいいでしょう。なるべく小さな単位から無効化していくことを検討してください。

要素単位で無効化

特定のリンクやある要素配下のみTurbolinksを無効化するには以下のように記述します。

index.html.erb

<%# 特定のリンクの無効化 %>
<%= link_to '無効', '/', data: { 'turbolinks': false } %>

<%# 要素配下の無効化 %>
<div data-turbolinks="false">
  <%= link_to '無効', '/' %>
  <%= link_to '有効', '/', data: { 'turbolinks': true } %>
</div>

index.html.slim

/ 特定のリンクの無効化
= link_to '無効', '/', data: { 'turbolinks': false }

/ 要素配下の無効化
div[data-turbolinks="false"]
  = link_to '無効', '/'
  = link_to '有効', '/', data: { 'turbolinks': true }

Turbolinksを要素単位で無効化する方法としてdata-no-turbolinks="true"という属性について書かれていることがありますが、これはTurbolinks 4までの書き方なので注意してください。Turbolinks 5からはdata-turbolinks="false"を使用します。

ページ単位で無効化

特定のページのみTurbolinksを無効化するには、無効化したいページのheadに以下のmetaタグを追記します。

application.html.erb

<meta name="turbolinks-visit-control" content="reload">

application.html.slim

meta[name="turbolinks-visit-control" content="reload"]

アプリ全体で無効化

Railsアプリ全体でTurbolinksを無効化することもできますが、まずは要素単位やページ単位で無効化することを検討してください。

アプリ作成時に無効化

TurbolinksなしでRailsアプリを作成することができます。

$ rails new rails-no-turbolinks --skip-turbolinks

アプリ作成後に無効化

Railsアプリ作成後にTurbolinksを無効化したいという場合は以下の手順を行います。レイアウトapplication.html.erbまたはapplication.html.slimを以下のように修正します。

application.html.erb

<head>
  <%# 以下を修正 %>
  <%= stylesheet_link_tag 'application', media: 'all' %>
  <%= javascript_include_tag 'application' %>
</head>

application.html.slim

head
  / 以下を修正
  = stylesheet_link_tag 'application', media: 'all'
  = javascript_include_tag 'application'

Sprocketsを使っている場合は以下の行を削除します。

app/assets/javascripts/application.js

//= require turbolinks

Webpackerを使っている場合は以下の行を削除します。

app/javascript/packs/application.js

require("turbolinks").start()

Gemfileから以下の行を削除し、bundle updateを実行します。

Gemfile

gem 'turbolinks', '~> 5'

これでRailsアプリからTurbolinksが削除されました。もう一度インストールしたい場合は逆の手順を実行してみてください。

プログレスバーのカスタマイズ

Turbolinksは通常のページ遷移をキャンセルするので、ブラウザにページ遷移の進捗状況が表示されません。そのため、Turbolinksでは独自のプログレスバーをページ上部に表示しています。このプログレスバーはスタイルを設定したdiv要素なので、スタイルを上書きすることでカスタマイズすることができます。

.turbolinks-progress-bar {
  height: 5px;
  background-color: green;
}

プログレスバーを表示したくない場合は以下のように記述します。

.turbolinks-progress-bar {
  visibility: hidden;
}

まとめ

Turbolinksはインストールしているだけでページ遷移を高速化してくれる便利なJavaScriptライブラリです。しかし、その挙動を理解せずに使い続けていると思わぬ落とし穴に落ちる可能性があります。特に、自分でJavaScriptの処理を実装している場合、誰しも必ず一度はライフサイクルイベントの罠に引っかかるのではないかと思います。

本記事を参考にして、Turbolinksについて理解していただければと思います。

関連記事

【Rails】Webサーバー「Unicorn」の基本情報と実装方法
# はじめに Railsアプリを本番環境で稼働させるには、クライアントからのリクエストを捌くWebサーバーを導入する必要があります。WebサーバーはクライアントからのリクエストをRailsアプリに伝達し、Railsアプリで処理されたレスポンスをク [...]
2021年4月15日 12:17
【Rails】デプロイツール「Capistrano」の基本情報と実装方法
# はじめに アプリを本番環境にアップロードして誰でもアクセスできる状態にすることをデプロイと言います。デプロイで行うべきことは多岐にわたります。Railsアプリの場合で言えば、本番環境にアップロードすることはもちろんですが、Gemのインストール [...]
2021年4月14日 9:56
【Rails】Webpackerの基本情報と実装方法
# はじめに Rails 6からWebpackerが正式採用されました。Rails 5ではオプションで追加することができたWebpackerですが、Rails 6からは普通にアプリを作成するだけでWebpackerがインストールされ、必要な設定も [...]
2021年4月12日 14:36
【Rails】アセットパイプライン(Sprockets)の基本情報と実装方法
# はじめに Ruby on Railsにはアセットパイプラインという機能があります。アセットパイプラインは画像、CSS、JavaScriptといったアセットファイルを連結/圧縮することでRailsアプリを高速化します。また、より高級な言語で書か [...]
2021年4月11日 14:08
【Rails】UIkit - HTML editorを使って簡単にマークダウンエディターを実装
# はじめに Ruby on RailsではAction Textという機能を使うことで簡単にリッチテキストエディターを実装することができます。 <iframe class="hatenablogcard" style="width:100 [...]
2021年4月6日 22:59