Hatena::ブログ(Diary)

Life on Rails

2012-01-28

pjaxというもの

pjaxというものの話題を目にしたのは昨年だった。

javascriptを用いて動的なページ変化を作ろうと流行ったのはajaxだった。そこにpushStateを用いた履歴管理を含むpjax(pushState + Ajax)なるものが登場した。

Ajaxによるページ遷移はそうではないものに比べて、jsファイルの再ロードや冗長なレンダリングにかかる時間を削減することができる。加えてサーバ側では冗長なHTMLを吐くことを削減できるため、処理時間が短く済む。

通常、Ajaxによる画面遷移では履歴が残らないという問題を持つ。それをpushState関数を使うことで追加できるという。それだけでなく、Ajaxを利用したページ遷移に関連するいくつかの問題も解決している。

Ajaxを用いたページ遷移を行う場合、専用のjsもしくはhtmlの断片を送信することになる。このURLアクセスした場合、意味を成さない断片をユーザーは手にする。履歴管理をしていても無駄である。この問題の解決にpjaxではHTTPヘッダを用いる。pjaxを利用した画面遷移のURLリクエストに関してはHTTPヘッダにPJAX(HTTP_X_PJAX)を追加する。WebアプリケーションはPJAXヘッダの有無を見ることで、それがpjaxリクエストかどうかを判断し、PJAXリクエストであれば断片となるHTMLを、そうでなければフルセットのHTMLを返信する。

pjaxの最たる例はgithubレポジトリブラウザとされている。レポジトリブラウザにて何回かの遷移を行った後、アドレスバーに表示されるURLをコピー&ペーストしても、断片ではないフルセットのHTMLが表示される。またレポジトリブラウザの遷移にはアニメーションが用いられるため、フォルダの階層を移動していることが分かりやすい。

Railsにおけるpjax

このPjaxを実現するためのRailsプラグインとしてpjax_railsが作成されている。

フレームワークにとってこのプラグインは、PJAXヘッダの確認、jquery-pjaxのロード周りの整備などを行う。PJAXヘッダは、HTTPヘッダにおけるPJAXヘッダの有無を確認して、一部のHTMLを返すのか、フルセットのHTMLを返すのか、の判断を行う。

pjax特有のものとして、ページ読み込み時のjavascript読み込み(例えばhead内のscriptタグによる)が行われないため、初期化すべき箇所は自分でハンドリングしならない。

また、このプラグインでは、フルセット部分のレイアウトが行われなくなるため、ページに合わせてtitleタグを変更することも自分で行う必要がある。

最近ではRailsよりもWebサーバ側に近いRack側でpjaxに対応するプラグインが登場している。

https://github.com/eval/rack-pjax/

このプラグインは既存のpjax_railsにおいて、タイトルの変更が難しい点を解決している。短いコードでpjaxを実装している。

https://github.com/eval/rack-pjax/blob/master/lib/rack/pjax.rb

HTTPリクエストがあればRailsがフルセットのHTMLRackに応答する。

PJAXヘッダがHTTPリクエストに存在していたかを確認し、存在していればpjaxとして扱う。その応答をHTMLパーサ(Hpricot)にてパースし、data-pjax-container属性のあるタグとtitleタグを抜き出して、ブラウザに応答する。抜き出して送信するため、送信サイズは小さくなる。PJAXヘッダが存在していなければ、そのまま返す。

Hpricotによるパーサの影響のためか、aタグの下にdivタグなどを置くと、変なことになる(そもそも置いてはいけない)。

サーバ側の処理を少なくするという利点を殺しているが、レンダリングはpjax_railsよりも意図したとおりに行われるので、手間はこちらの方が少ない。

斜め上、jQuery Mobile

また、別のものとして、jQuery Mobileが存在する。

jQuery MobileもAjaxによるページ遷移を行うが、これはHTMLの抜き出しを必要とせずに、常にフルセットのHTMLを読み込む。フルセットのHTMLの中から、jQuery Mobileが必要とする部分をクライアントブラウザ上のjsにて判断し、抜き出して表示する。

jQuery Mobileのための独特な属性をHTML上に書く必要がある。

フルセットのHTMLを受信するため、サーバ側の送信データサイズは小さくならないが、画面遷移のための対応をサーバアプリケーションでする必要がない、という利点がある。ajaxによるページ遷移の利点のうち、js再ロードとレンダリングが入らないので、これもそれなりに使える。

が、ページ遷移時のscript問題はそのままなので、例えばgoogle analysisなどは独自に対応する必要がある。

ちなみにjQuery Mobileはαの時代に触ったきりなので、リリース後のことはしらない。

rack-pjaxとjQuery Mobile

rack-pjaxはjQuery Mobile的なフルセットな生成を必要としながらも、サーバ上でパーサーを働かせて抜き出してサイズを小さくしてくれる的なもの、に見える。つまり、サーバ側でパースするのか、ブラウザ側でパースするのか、の違い、ということだ。

まとめ

で、いろいろ、あれだけれども、rack-pjaxはそれなりに気に入っているのです。

プラグインサーバ処理データ送信量ブラウザ側処理手間
pjax_rails-layout部分view大(layoutしない部分の対応)
rack-pjax+HTMLパース小(pjax独特の初期化
jQuery Mobile普通普通中(独自タグ習得)

実際にこの手のサイトを作ったことがないので、どうとは言えないのだけれども、pjaxやり始めならrack-pjaxあたりで良いと思う。ソースを見ても分かるとおり、とても単純なので。慣れてきたらpjax_railsあたりを使うと良いかと。

jQuery Mobileは昔はもっさりしている気がしたのだが、今は知らない。

あれ

  • jsロード時間とレンダリングって、そんなに時間かかったっけ?
  • データ送信量によってデータ受信時間ってそんなに変わるっけ?
  • layout部分の処理ってcacheで救えるんじゃね?

という疑問も出てくるので測定は大事ですね。。

pjaxプラグインの問題点

例えば、この部分をpjaxで変える、と決めたら、そのdivタグにdata-pjax-containerという属性を加える。

 <div data-pjax-container></div>

そこに決めうちされているので、例えば、左右2ペインのページで左右それぞれをpjaxで更新したい場合は独自に作りこむ必要がある。後は変更したい箇所が入れ子になっている場合も。

なので複雑なことをしようとすると、フレームワークのlayoutの部分と密接に関わるプラグインを作っていかないと、面倒なことになりそうな予感がする。サイトの1部分のみを変更していく、という話ならば、そこまで難しくはない。