PhoenixFramework が JavaScript のコードを書かずに RealTime Web を実装できる LiveView をリリースしたので、2つアプリケーションを作ってみた。
LiveView とは何なのか?
あたりを見るとよく分かる。
特に新しい技術が使われているわけではないのだが、コンセプトがとても面白い。通信には WebSocket が使われていて、 HTML はサーバ側で生成されクライアント側では JavaScript で要素の置き換え程度をしている。これだけ聞くと Turbolinks のようだが、そうではない。
仕組み
通信されるデータとしては、初回はテンプレートと変数一式をクライアントサイドに送り、2回目移行は変わった変数だけを送る。
もう少し具体的には、サーバ側ではElixir のテンプレート(eex等)を変数の箇所で split し、変数の位置を記憶しておく。クライアント側では、split されたテンプレート文字列と変数の List を単純に join して HTML を組み立てる。2回目移行は変わった変数部分を置き換えて、再度 join すれば良い。
つまり、こういうことだ。(本質的な部分のみを簡易化して説明する)
下記のようなテンプレートがあるとする。 @title
, @content
が変数で、その他の部分は毎回固定文字列となる。
<h1><%= @title %></h1>
<p><%= @content %></p>
初回(mount時)
変数の箇所で split すると、固定文字列は下記のように分割できる。
[
"<h1>",
"</h1>\n<p>",
"</p>"
]
変数の位置は、1個目と2個目の間に @title
, 2個目と3個目の間に @content
となる。
初回は細切れのHTMLテンプレート文字列全てと、2つの変数を送る。
テンプレ文字列の間に変数を挟むと下記の配列を得る。
["<h1>", @title, "</h1>\n<p>", @content, "</p>"]
あとは単純に文字列として join すれば良い。 @title が "タイトル", @content が "本文" だったとすると、
["<h1>", "タイトル", "</h1>\n<p>", "本文", "</p>"]
|> Enum.join()
=>
<h1>タイトル</h1>
<p>本文</p>
となる。実際の WebSocket 通信の中身はこんな感じ。
ここまでは特に変わったことはない。
2回目以降
では、@title
が "すごいタイトル" に変わったとする。この変更をフロント側で描画したい。全ての値は PhoenixFramework が持つコネクションごとの socket.assigns という場所に格納されている。サーバ側で socket.assigns に新しい値を代入すると、WebSocket がその値をフロントへ運ぶ。
テンプレート文字列は不変なのだから、再度送る必要はない。送れば良いのは変数部分だけだ。1個目と2個目のチャンクの間(0番目)に入る新しい値を送れば十分である。
{ 0: "すごいタイトル" }
@title
が "すごいタイトル" になったが、フロント側では特にそんなことは関係なく、先ほどの最初の変数位置の値を "すごいタイトル" に変更し。再度 join すれば良い。
["<h1>", "すごいタイトル", "</h1>\n<p>", "本文", "</p>"]
|> Enum.join()
=>
<h1>すごいタイトル</h1>
<p>本文</p>
とても効率的である。 WebSocket 自体がオーバーヘッドがとても少ないのだが、実データもかなり小さい。
実際の通信はこんな感じ。
もちろん、テンプレート内に配列や if 等があっても問題ない。通信されるデータがややこしくなるが、読めば何となくどういうことをしているのか分かる。実装にあたって、特に中身を気にする必要はない。
作ってみた
2つアプリケーションを作ってみた。Phoenixフレームワークに慣れていれば、8割くらいは迷うことなく書ける印象。画面をまたぐときに結構考える事が多かった。
使用感など
- テンプレートは今までのものがそのまま使える。つまり、辞めたくなってもコードが無駄にならず通常遷移に切り替えられる。
- 環境構築が非常に楽で、JS側の環境維持から開放されるのが良さそう。
- JavaScript を書くことももちろん出来る。Elixir から JavaScript をフックする事もできる。実際のアプリケーションを作るならある程度はJavaScriptを書いたほうが楽なはずだ。
- JavaScript が無効な環境でも表示はできる(検索エンジンからのリクエスト)
テンプレートが通常の画面遷移で作ったときと同じで動くのがかなり良い。このおかげで、既存アプリケーションを LiveView 化するのも、LiveView を通常遷移に移行するのも、そこまで大変ではないはず。
使いたい部分だけに使えばよいので、あまりデメリットはなさそう。すごく大変になったらベーシックな作りに切り替えれば良い。また基本が websocket channel に載っているため、ネイティブアプリと WebSocket で連携する拡張をしようとしても、おそらくそこまで大変な思いをせずに拡張できそうだなと思った。