PjaxとSPAの違い

PjaxとSPAが全く同じ技術構成なのを知らずにPjaxを過去の技術だと思ってる知ったかぶりが多いので最も高度で完成度の高いPjaxライブラリであるpjax-apiの作者の自分が説明しておこう。

SPAとはPjaxをバンドルしたフレームワーク

Pjaxとはその語源からしてpushState+Ajaxである。そしてSPAの技術構成はフレームワーク+pushState+Ajaxである。すなわちSPAとは本質的にPjaxをバンドルしたフレームワークに過ぎない(Pjaxと対比されるSPAはツールセットとしてのSPAでありこれはアプリケーションフレームワークとしてしか存在し得ない。Reactはライブラリを自称しているがAngularやVueがフレームワークを自認していることからも明らかなようにSPAとして使用する場合はルーターなどと組み合わせフレームワーク化しなければならないものでありこれが主たる使用方法であるため悪質な詭弁に過ぎない。字義通りのSPAはSPAフレームワークで作ろうとPjaxで作ろうと何で作ろうとSingle Page ApplicationであればSPAである)。SPAを使っている限りPjaxも使っているのでありPjaxが過去の技術のごとき言説は無知を晒しているだけである。なおウェブ標準的にはAjaxはfetchに置き換えられているがPjaxも内部のAjaxをfetchに置き換えるのに何の支障もないので名前が古いままになるだけである。

実質的差異はデータフォーマットだけ

PjaxはAjaxなどの非同期通信APIによりHTMLを取得しページを更新するがSPAはJSONを取得しページを更新する。PjaxとSPAの共通部分の実質的差異は取得するデータのフォーマットがHTMLかJSONかという非同期通信の運用上の差異しかないのである。そしてその差異もそれぞれの利用方法において効率的なデータフォーマットを選んだ結果でありPjaxはページ遷移時にHTMLを差し替えるのみで足りるのでHTMLを取得しSPAはJavaScript上でDOMを常時状態管理してるのでJavaScript上の状態更新に適したJSONを取得しているに過ぎない。

HTMLとJSONで何が変わるか

データフォーマットをHTMLからJSONに変える主な利益はデータサイズである。HTMLよりもJSONのほうが簡潔で小さい。しかしPjaxでもそのような実装はやろうと思えば当然可能であり設計方針の違いでしかない。それよりもSPAがサーバーサイドの技術選択を制限する不利益のほうが大きい。

クライアントサイドにおいてはJSONを使用するSPAのほうが効率的に見えるがPjaxにおいてもダミーのXHRオブジェクトをJSONで取得した差分データで更新することで完全に同じ動作と効率を実現できpjax-apiはこの方式をサポートしている。

import Pjax, { FakeXMLHttpRequest } from 'pjax-api';

new Pjax({
  fetch: {
    rewrite: url =>
      FakeXMLHttpRequest.create(
        url,
        fetch(url, { headers: { 'Content-Type': 'application/json' } })
          .then(res => res.json())
          .then(data =>
            new DOMParser().parseFromString(
              `<title>${data.title}</title><body>${data.body}</body>`,
              'text/html'))),
  },
});

これによりSPAのすべての技術的優位性が完全に失われ、SPAは単に貧弱な独自構文とフレームワークを密結合したPjaxでしかなくなった。フレームワーク化だけなら意義があるがゴミみたいな独自構文など簡素な初期プロダクトは簡単に作れても後は死ぬまで10倍苦労するリボ払いのようなものである。

サーバーサイドにおいてもSPAは専用のフレームワークやライブラリのサーバー上での使用を要求しこれはほとんどの場合JavaScriptであるためJavaScriptという低速な言語によりサービスのスループットもレイテンシも非常に悪い不経済なものとなる。GoやRustなど他の高速な言語との組み合わせは不可能ではないが一般的とは言い難く自力で解決しなければならない部分が多いだろう。対してPjaxはブラウザ上で完結したライブラリであるためサーバーサイドの制約がなく任意の高速な言語と技術構成でサーバーを構築できる。またPjaxであればPjaxなし、Pjax(HTML)、Pjax(JSON)を容易(サーバーにJSON APIを追加するだけ)に混在させPjaxの設定のみでページ単位で変更できるがSPAでは多大なコストを要するSPA用サーバー(アプリケーション)の開発と運用を要するためPjaxのような段階的導入と一部変更は不可能か極めて非効率である(PjaxはJSON APIの追加以外既存サーバーを変更しないため設定を切り替えても単にもとのHTMLと行き来するだけだがSPAは既存HTMLサーバーをJavaScriptで作り直した高コストかつ低速な二重苦のSPA用サーバーで置き換えるためSPA用サーバーのSPA無効化は増加したコストが無駄になりドブに捨てることを意味する)。

SPA on frameworks*1 SPA on Pjax
Type Framework Library
Abstraction level High Low
Original template language Yes No
Intermediate layer Yes No
Data format JSON JSON or anything*2
UX integrity*3 Low High
Technical property Exclusive Coexistence
Ecosystem Dedicated Common
Old servers Dispose Keep
New servers Required Optional
Work on servers Redevelop all Add JSON APIs*4
Work on clients Redevelop all Reconstruct scripts*5
Partial introduction Coarse Fine
Location Servers and Clients Clients
Server constraints Strong Nothing
Dependence High Low
Learning costs High Low
Development costs High Low
Operation costs High Low
Running costs High Low
Removal costs High Low
Renewal costs High Low

*1 Such as React.
*2 You can send any efficient format instead of JSON and parse it on clients.
*3 Between MPA(standard web sites) and SPA.
*4 Just do it.
*5 To manage the lifetime of existing scripts. Since such management doesn't depend on SPA, these scripts are sharable and reusable with MPA and other SPA libraries.

github.com

さらに従前レスポンスを単一のHTMLにまとめてサーバーから送っていたところにJSONの各フィールドに分解して送りクライアントでHTMLまたはDOMに組み立て直す工程を追加するのはSPAかPjaxかにかかわらず非常に無駄が多く基本的に自己満足のためにクライアントのスクリプトへの依存を追加しメンテナンスコストを著しく上げるだけの結果に終わるためヘッダを見て差分HTMLを返すほうが一般的に全体として遥かに効率的でありまずそこから始めるべきである。JSONによる更新に適しているのはタイムラインやメールのようなデータだがこれはページと独立してAjaxで非同期に読み込み更新するほうが適しており実際これが一般的かつ主流な方法であるためこれらをページの一部としてSPAによりすべてJSON化してまとめて更新するのは無駄でありそうする必要性もない(リクエスト数が増加するが現在の開発ではほとんど考慮されない。逆に言えばリクエスト数を気にするほど軽量なページならデータを分離すべきではない)。ページ更新はHTMLとPjax、データ更新はJSONAjaxに分離するのが賢明である。ただし現在のユーザーはデータの遅延読み込みに慣れているものの検索結果の表示は伝統的に遅延しないのが通常であるため検索結果は同時に読み込むべきである(上記コードの変更によりHTMLページとJSONデータの並列読み込み同時更新も可能でありすなわちあらゆるデータ構成が可能)。理想的にはログイン状態では常に共通HTMLとユーザー別JSONに分離するのが最もネットワークおよびデータベース効率がよいがこれを実現できている大手サービスは見当たらない(データとスクリプトのサイズが大きくリクエスト数も多い大規模サービスでHTMLをキャッシュ可能にしても寄与が小さく開発の柔軟性が下がる不利益のほうが大きいのだろう)。

SPAとPjaxの開発上の違いはフレームワークとライブラリの違いであり既成品の組み立てキットを組み立てるか自分で図面を引いて部品を作って組み立てるかの違いである。設計能力も実装能力も兼ね備えた優れた開発者にとってフレームワークは耐え難く不自由でライブラリでなければ思い通りのものを作れない。設計能力の欠けた者、セミオーダーの既成品を量産するような仕事をしている者、とにかく早くプロトタイプを作らなければならない者にはフレームワークが適しているが最高の一点物を作るにはライブラリである。さらに必要ならフレームワークもライブラリも自分で作るのが最高峰の世界である。そしてReactもAngularも自社使用のために作られたフレームワークである。アプリケーションフレームワークというものは思い通りのアプリケーションを作ろうとするとどうしても細部に手が届かずフレームワークごと作り込まなければならなくなり、それができない外部の利用者は不便と妥協を強いられるものなのである。対してライブラリであれば不足分は基本的に独自アプリケーションの一部として生じるためライブラリに手を加える必要はない。一般的にアプリケーションはフレームワークではフレームの中に作られるのでフレームが妨げになると拡張困難になるがライブラリではライブラリの外に作られるので拡張を妨げずライブラリが適さなければ代わりの最小独自実装を内部的に作ればいいだけである。また陳腐化した場合の刷新もライブラリは段階的に行えるがフレームワークは一括して行うしかない。フレームに合わせて作る以外自由がないのがフレームワークである。

将来のPjax

Pjaxを構成するpushStateなどのHistory APIに代わるNavigation APIが現在策定中でありこれが標準化されればPjaxは名称と実装が完全に異なるものとなってしまうがライブラリとしては依然として内部実装を更新するだけで済むことである。機能的にもNavigation APIは基本的に既存APIを使いやすく再構成した便利API集で、現在のSPAとPjaxがすでに特に不足のないサービスを実現しているように基本機能は既存APIで足りている。従来不可能だったことが可能になる部分もあるものの現在特に不便を感じないように実際上ほとんど必要にならないものである。具体的にはリンクなどによるページ遷移の完全な捕捉と、厳密な遷移状態と遷移履歴の提供であるがオーバーエンジニアリングとユーザー行動追跡が促進されるだけである。そんなことより既存の標準APIを正しく実装しろと言いたい。History APIの一部でありPjaxに必要なscrollRestoration APIは標準化から7年に渡ってChromeFirefoxで真逆の動作になっていた。単機能のHistory APIですら長年互換性が盛大に壊れていたのに遥かに複雑な統合的APIであるNavigation APIのプロダクションレベルのブラウザ互換が確保されるまで何年かかるかわからない。ちなみに他のPjaxライブラリを使うのはやめとけどれだけ有名でも平行性制御できてないクソ実装だ。