#390 Turbolinks
- Download:
- source codeProject Files in Zip (63.9 KB)
- mp4Full Size H.264 Video (21.5 MB)
- m4vSmaller H.264 Video (9.41 MB)
- webmFull Size VP8 Video (10.9 MB)
- ogvFull Size Theora Video (22.9 MB)
Turbolinksは新規作成したRails 4のアプリケーションにデフォルトで含まれるgemです。Rails 3とも互換性があるので、既存のアプリケーションで使用することも可能です。このgemによって、ページ全体をロードすることなく、新規ページのHTMLのボディ部分をJavaScriptで置き換えることで、ユーザに対してアプリケーションの動作を速く見せることができます。今回のエピソードではRails 3アプリケーションでこのgemを試してみます。使用するアプリケーションは、複数のプロジェクトに対応したTodoリストで、各プロジェクトが複数のタスクを持つことができます。新規タスクは項目名の横に最初は「未完了」のマークが付いていて、チェックボックスをチェックすることで完了にすることができます。
Turbolinksを追加する
このアプリケーションにTurbolinksを追加して、その動きがどうなっているかを見ていきます。最初にgemfileのgemのリストに追加して、bundleコマンドを実行してインストールを行ないます。
gem 'turbolinks'
次にアプリケーションのJavascriptのmanifestファイルを開いて、turbolinks
をrequireする行を追加します。TurbolinksはjQueryに依存しないので、アプリケーションがjQueryを使用していなくても利用することが可能です。
//= require jquery //= require jquery_ujs //= require turbolinks //= require_tree .
ここでRailsアプリケーションを再起動すると、前と同じようにブラウズでき、違いがあるようには見えません。Turbolinksが動作しているかどうかを確認するために、ネットワークインスペクタを開いてサイトのところどころをクリックしてみます。するとリンクを押すたびにページ全体がリロードされているようなので、Turbolinksは機能していないようです。これは、使用しているブラウザをTurbolinksがサポートしていない可能性があるので、アップグレードするか別のブラウザを使います。Turbolinksは最新のブラウザで使用されることを想定していますが、サポートされていないブラウザの場合にもgraceful degradationによって、アプリケーションは想定した通りに動作します。ここでは最新版のChromeを使ってページを見てみることにしましょう。リンクをクリックすると、今度はページの全体がリロードされずに、turbolinks.js
ファイルによって、次ページに移動するためのAJAXリクエストが生成されます。
これによってページがロードされるたびにJavaScriptとCSSが解釈し直されることがなくなり、ユーザに対してサイトの速度が向上したように見えます。これはどういう仕組みで動いているのでしょうか? Turbolinksは、ページ上の各リンクのclick
イベントを待機します。イベントが起動されると、JavaScriptによってリクエストが生成され、Turbolinksがレスポンスのbodyをチェックします。そしてJavaScriptを使って現在のページをこの新しい内容で更新して、title
とbody
要素を置き換えて新しいページに見えるようにしています。またPush State APIを使って、URLを変更し新しいページに移動したように見せます。この方法は、エピソード294で紹介したPJAXによく似ています。
既存のJavaScriptの動作の問題
このアプリケーションではTurbolinksがうまく機能しているように見えますが、既存のJavaScriptのいくつかが動作しないことに気づくでしょう。例えば、タスクを更新して「完了」にチェックしても何も起こりません。現在のページをリロードして再度チェックボックスをチェックすると、今度はうまく動作します。このページのCoffeeScriptのコードは次のようになります。
jQuery -> $('.edit_task input[type=checkbox]').click -> $(this).parent('form').submit()
このコードは、各タスクのチェックボックスのclick
イベントを待機します。いずれかがクリックされると、そのチェックボックスを含んだフォームが送信されて、タスクに完了のマークが付きます。このコードの中で重要なのは、1行目のjQuery
の関数を呼び出している部分です。このコードは、ページのDOMのロードが完了したときに起動されるready
イベントを待機します。この関数の残りのコードをラップしなかった場合、jQueryがチェックボックスに対してそれらがロードされる前にclick
イベントを付加しようと試みます。
Turbolinksを使用した場合、このコールバックは最初にページがロードされるときだけ起動されます。もし別のページを表示していて、タスクを見るためにこのページに来たという場合、実際には同じHTMLページにいることになるので、DOMのready
イベントは起動されません。Turbolinksが起動するイベントはいくつかあり、その中の一つがpage:load
です。これを使って、CoffeeScriptファイルの中でDOM readyの振る舞いをシミュレートできます。
ready = -> $('.edit_task input[type=checkbox]').click -> $(this).parent('form').submit() $(document).ready(ready) $(document).on('page:load', ready)
ここでready
という変数に、チェックボックスをチェックする関数を設定して、それをdocument.ready
とpage:load
の両方のイベントに渡します。このようにすることで、Turbolinksを使用しているかどうかにかかわらず、チェックボックスのイベントがページをロードしたときに付加されます。この変更によってチェックボックスは従来の動きをするようになり、未完了のタスクにチェックを付けるとそのタスクは完了リストに移動します。
自動的にこのような振る舞いにしたい場合は、JQuery Turbolinks gemを利用します。この問題を回避する、もう一つ別の方法もあります。要素を選択してそのclick
イベントを待機するのではなく、ドキュメントのclick
イベントを待機して、イベントを発生させた要素がタスクのチェックボックスである場合に、このイベントの起動をチェックします。
$('document').on 'click', '.edit_task input[type=checkbox]', -> $(this).parent('form').submit()
これによってDOM readyのイベントに対してこのコードを起動する必要がなくなります。これはHTMLに追加されたすべてのチェックボックスに対して、それがたとえこのJavaScriptが実行された後であってもイベントが起動されるからです。この方法には別の利点もあります。AJAXでさらにタスクを加えた場合、それらに自動的にこのイベントが適用されます。
Turbolinksのissue trackerを定期的にチェックするのがいいでしょう。というのも気をつけなくてはいけない問題がいくつかあるからです。サードパーティのライブラリの中には、例えばTwitter BootstrapやjQuery UI Calendarなどのように、Turbolinksと互換性がないものもいくつかありますが、現在対応が取られているところです。また一部のブラウザで、ページをリロードしたり「戻る」ボタンをクリックした場合におかしな状況が発生して期待しない挙動を行なう場合があります。例えば、一定の状況でGETの代わりにPOSTリクエストを送信したような場合です。このような状況で発生する問題を回避するには、Turbolinksを無効にするという方法がありますが、将来のバージョンのTurbolinksではそれも必要なくなるでしょう。
このような問題があるのにTurbolinksをわざわざ使う意味があるのかと迷っている人もいることでしょう。迷っているあなたに試してみようと思わせるかもしれないのがTurbolinks Testです。このツールはベンチマークを実行して、Turbolinksを使うことで速度の向上を見込めるかどうかをチェックしますが、大抵の場合は速度は向上します。大きい場合にはレスポンス時間を半分に削減することもあります。もちろんアプリケーションによって状況はそれぞれですが、自分のアプリケーションに組み込んで反応速度が向上するかどうかを試してみることは簡単です。
新規にRails 4のアプリケーションを作成して、Turbolinksを使用しないと決めた場合には、gemfileのgemの行と、アプリケーションのJavaScript manifestファイルのrequire
の行をコメントアウトします。