下記Elixirコミュニティ運営/所属の piacere です、ご覧いただいてありがとございます
ElixirでリアルタイムUIを作るLiveView/Livebookで盛り上がるコミュニティ「LiveView JP」
Phoniex 1.5以降、React的SPA/リアルタイムフロントをElixirサーバサイドのみで書ける「LiveView」が標準搭載され、Phoenix 1.4までで必要だったLiveView各種設定が不要となったのですが、本設定が凄まじいボリュームだったので、それが今では不要となった功績に感謝しつつ、記念として残しておくとしました
Phoenix 1.5以降をご利用の方は、ここに書いている手順を一切すること無く、LiveViewが利用可能ですので、下記コラムからお試しください
内容が、面白かったり、役に立ったら、「LGTM」よろしくお願いします
本コラムの検証環境、事前構築のコマンド
本コラムは、以下環境で検証しています(Windowsで実施していますが、Linuxやmacでも動作する想定です)
- Windows 10
- Elixir 1.8.0 ※最新版のインストール手順はコチラ
- Phoenix 1.4.2 ※最新版のインストール手順はコチラ
LiveViewはこんなリアルタイム検索アプリが開発できます
Phoenix 1.4でのLiveViewセッティングは、LiveViewがリリースされたばかりのため、割と手順が多く、途中で心が折れてしまうかも知れないため、完成したときに、何ができるかを先に動画で紹介しておきます
サーバサイドで書かれたコードにも関わらず、ReactやVue.jsのようなリアルタイムUIが構築できます … 反応速度もスピーディで、もたつく感じは一切無く、ReactやVue.jsで書いたのと変わらないフィーリングです
LiveView向けPhoenix PJの作成
LiveViewを動かすためのPhoenix PJを作成します
mix phx.new basic --no-ecto
…(ファイル作成ログが続く)…
Fetch and install dependencies? [Yn] 【nを入力し、Enter】
cd basic
LiveViewとSmallexのインストール
ライブラリとして、LiveView(phoenix_live_view)を追加します
あと、LiveViewのサンプルとして、外部API呼出をするので、smallexも追加します
defmodule LvSample.MixProject do
…
defp deps do
[
{:phoenix_live_view, github: "phoenixframework/phoenix_live_view"}, # <- add here
{:smallex, "~> 0.0"}, # <- add here
{phoenix, "~> 1.4.2"},
…
ライブラリをインストールします
mix deps.get
Phoenix 1.4におけるLiveView環境の設定
①セッション用saltの設定
LiveViewが利用するセッションをハッシュするためのsaltを設定します
まず、32バイトのsaltを生成します
mix phx.gen.secret 32
Gduxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
上記で生成した32バイトsaltを、下記の通り、LiveView向けに設定します
# This file is responsible for configuring your application
…
config :lv_sample, LvSampleWeb.Endpoint,
live_view: [ signing_salt: "Gduxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ],
# ^--- add here
url: [host: "localhost"],
secret_key_base: "AcPkxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
…
②ビューとルータのマクロ設定
ビューとルータのマクロに、LiveView用の設定を追加します
【2020/03/09追加】
LiveView 0.5以降では、「def controller do」配下への追加が増え、「def view do」の追加内容が変わりました
defmodule LvSampleWeb do
…
def controller do
quote do
use Phoenix.Controller, namespace: LvSampleWeb
import Plug.Conn
import LvSampleWeb.Gettext
alias LvSampleWeb.Router.Helpers, as: Routes
import Phoenix.LiveView.Controller # <- add here
end
end
def view do
quote do
…
import LvSampleWeb.ErrorHelpers
import LvSampleWeb.Gettext
alias LvSampleWeb.Router.Helpers, as: Routes
import Phoenix.LiveView.Helpers # <- add here
end
end
def router do
quote do
use Phoenix.Router
import Plug.Conn
import Phoenix.Controller
import Phoenix.LiveView.Router # <- add here
end
end
…
③LiveViewファイルのための設定
LiveViewファイルを置くためのフォルダを作成します
mkdir lib/lv_sample_web/live
LiveViewファイルが更新された際のLiveReloadを2箇所、設定します
use Mix.Config
…
config :lv_sample, LvSampleWeb.Endpoint,
live_reload: [
patterns: [
~r{lib/lv_sample_web/live/.*(ex)$}, # <- add here
~r{lib/lv_sample_web/templates/.*(leex)$}, # <- add here
~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
~r{priv/gettext/.*(po)$},
…
④LiveView用エンドポイントの追加
LiveView用のエンドポイントとして、「/live」を追加します
【2020/03/09追加】
LiveView 0.5以降では、Plug.Session情報をエンドポイントに設定することができます(下記ではコメントアウトしています)
defmodule LvSampleWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :lv_sample
// v- add here
# @session_options [
# store: :cookie,
# key: "_lv_sample_key",
# signing_salt: "xxxxxxxxx"
# ]
# plug Plug.Session, @session_options
# socket "/live", Phoenix.LiveView.Socket, websocket: [ connect_info: [ session: @session_options ] ]
socket "/live", Phoenix.LiveView.Socket, websocket: true
// ^- add here
…
⑤JavaScript側のLiveViewソケット接続設定の追加
app.jsに、「phoenix_live_view」をインポートし、「/live」にソケット接続するための設定を追加します
【2020/03/09追加】
LiveView 0.5以降では、CSRFトークンをLiveSocketのパラメータとして指定することができます(下記ではコメントアウトしています)
// We need to import the CSS so that webpack will load it.
…
// v- add here
import { Socket } from "phoenix"
import LiveSocket from "phoenix_live_view"
let liveSocket = new LiveSocket( "/live" )
//let csrfToken = document.querySelector( "meta[name='csrf-token']" ).getAttribute( "content" )
//let liveSocket = new LiveSocket( "/live", Socket, { params: { _csrf_token: csrfToken } } )
liveSocket.connect()
// ^- add here
…
<head>
<%= csrf_meta_tag() %>
…
⑥Nodeモジュールのインストール
package.jsonの「dependencies」に、「phoenix_live_view」のエントリー(下記dependenciesの直下1行)を追加します
{
"dependencies": {
"phoenix_live_view": "file:../deps/phoenix_live_view",
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html"
},
…
assets配下で、Nodeモジュールのインストールを行います
【2020/03/09追加】
LiveView用WebSocketが開かないことがあるため、Phoenix関連モジュールの強制アップデートをします
cd assets
npm install --force phoenix_live_view phoenix_html phoenix
cd ..
LiveViewサンプルの作成
さぁ、LiveView環境が準備できたので、リアルタイムにQiitaを検索するSPAを作ってみます
検索用の入力フィールドをフロントUIに持ち、LiveViewの機能でサーバ側に自動連携させ、入力が変わるたびにサーバからQiitaを検索し、その検索結果をフロントに反映させます
そのコードは、以下の通りです
【2020/03/09追加】
LiveView 0.6以降では、mountの引数が、2つから3つに変更(第1引数paramsが追加)されました
defmodule BasicWeb.QiitaSearchRealtime do
use Phoenix.LiveView
def render( assigns ) do
~L"""
<p>
<%= if @message do %><%= @message %><% end %>
</p>
<form phx-change="change">
<input type="text" name="query" value="<%= @query %>" placeholder="empty" />
Query: <%= @query %><br>
</form>
<table>
<tr>
<th>ID</th>
<th>タイトル</th>
<th>作成日</th>
</tr>
<%= for result <- @results do %>
<tr>
<td><%= result[ "id" ] %></td>
<td><%= result[ "title" ] %></td>
<td><%= result[ "created_at" ] %></td>
</tr>
<% end %>
</table>
"""
end
def mount( _params, _session, socket ) do
{ :ok, assign( socket, query: "", message: "[Init]", results: [] ) }
end
def handle_event( "change", %{ "query" => query }, socket ) do
send( self(), { :submit, query } )
{ :noreply, assign( socket, message: "[Searching...]", query: query ) }
end
def handle_info( { :submit, query }, socket ) do
results = Json.get( "https://qiita.com", "/api/v2/items?query=#{ query }" )
{ :noreply, assign( socket, message: "[Complete!!]", results: results ) }
end
end
①LiveViewサンプルにおける初期画面表示
LiveViewのコードは、大枠としては、画面初期時に、render()とmount()が呼ばれ、render()に配置されたパーツが、mount()でassignされた内容で画面が初期化されます
mount()は、Vue.jsで言うところの「mounted」に相当します
③LiveViewサンプルにおけるハンドラ
その後は、render()で配置されたパーツ毎に設定されたハンドラが動きます
たとえば、「phx-change」を使うことで、入力フィールドの変更があるたびに、handle_event()が呼び出されます
ここは、Vue.jsで言うところの「v-on:change」に相当する訳ですが、Vue.jsとの違いは、フロントサイドの関数を呼ぶのでは無く、サーバサイドの関数が呼ばれる点です
③LiveViewサンプルにおけるハンドラ内処理
handle_event()に渡される引数であるqueryは、フロントUIの入力フィールドに入力された値がサーバ側に渡されており、サーバサイドで処理が進められます
その中では、メッセージパッシングであるsend()でhandle_info()を呼び出し、その裏で、messageの更新を行っています
そして、サーバサイドで行われたmessageの更新は、フロントUI上のメッセージフィールドに反映されます
handle_info()では、Qiita APIを呼び出し、その結果をresultsに格納し、messageを更新します
LiveView最大の特徴:フロントとサーバの通信は記述不要
handle_event()/handle_info()共に、呼び出された時点で、フロントサイドの入力値や状態が、サーバサイドに渡され、以降の処理はサーバサイドで実行され、その結果をフロントサイドに反映できる…これを、サーバサイドのコードを書くだけで実現できるのが、LiveViewの最大の特徴です
これをVue.js等で開発した場合、サーバサイドにAPIを構築し、フロントサイドからAPIを呼び出す…といった手間がかかるところ、LiveViewは、通常のElixirコードをサーバサイドで書くだけで、API無にフロントサイドとサーバサイドのデータバインディングが可能です
機能規模のあるフロントを開発すると、APIがすぐに何十本、何百本と増え、管理が煩雑になるところを、LiveViewは、フロントとサーバの通信を明示的に書く必要が無く、あくまでサーバサイドのコードだけで表現できるため、非常にシンプルにまとまります
LiveView用plug、レイアウト外枠、ルーティングの追加
さて、SPAも書けたので、後はルーティングを設定したら、実行できるようになります
router.exの「pipeline :browser」中にある、「plug :fetch_flash」の直後に 「plug Phoenix.LiveView.Flash」 「:fetch_live_flash」を追加し、LiveView用のレイアウト外枠を設定し、「/realtime」でLiveView専用のルーティングを追加します
【2020/03/09追加】
LiveView 0.8以降では、「Phoenix.LiveView.Flash」の代わりに「:fetch_live_flash」の追加が必要になり、「plug :fetch_flash」はコメントアウトが必要です
次に、「:put_layout」 「:put_live_layout」「:put_root_layout」で、レイアウト外枠として「templates/layout/app.html.eex」を指定します(このレイアウト外枠から、/js/app.js中のLiveView用WebSocket接続が呼び出されます)
【2020/03/09追加】
LiveView 0.8以降では、「:put_layout」による指定が効かなくなったので、「:put_live_layout」でレイアウト外枠を指定する必要があります
【2020/04/09追加】
LiveView 0.11以降では、「:put_live_layout」による指定が効かなくなったので、「:put_root_layout」でレイアウト外枠を指定する必要があります
最後に、通常のコントローラだと、「resource」や「get」と、コントローラのモジュール名+関数を指定しますが、LiveViewでは、「live」と、LiveViewモジュール名を指定します
defmodule BasicWeb.Router do
use BasicWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, {BasicWeb.LayoutView, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
end
…
scope "/", BasicWeb do
pipe_through :browser
…
live "/realtime", QiitaSearchRealtime # <- add here
get "/", PageController, :index
end
Phoenix起動、LiveViewサンプル実行
iex -S mix phx.server
ブラウザで「http://localhost:4000/realtime
」にアクセスすると、こんな感じのQiita検索SPA画面が表示されます
入力フィールドに文字を入れると、入力フィールド下部に入力した文字列がリアルタイム更新され、入力フィールド上部には「[searching...]」と検索中であるメッセージが表示され、裏では、Qiitaへの検索が走り始めます
少しすると、メッセージが「[complete!!]」に変わり、画面下部に検索結果が反映されます
実際に動いている動画もアップしたので、コチラをご覧いただくと、より実感が沸くかと思います
けっこう反応速度がスピーディで、もたつく感じは一切ありません(Vue.js等でフロントネイティブで書いているフィーリングと、ほぼ変わらないです)
終わり
今回は、LiveView環境を構築して、リアルタイムにQiita検索を行うSPAを作りました
サーバサイドのElixirコードを書くだけで、フロントUIのリアルタイム操作/反映が可能であり、SPAを始めとするWebアプリを作るための強力過ぎる土台だという感触が伝わったでしょうか?
リリース前から、かなりの可能性を感じていたLiveViewですが、実際に動かしてみて、Vue.js等とほぼ変わらない速度やフィーリングで、しかもフロントとサーバの書き分けが一切要らず、入力内容やデータ状態をAPI構築無かつサーバサイドで一元管理できる…なんだか魔法のような体験でした
次回は、「LiveViewにおけるsubmitボタンの制御」について解説します
p.s.「いいね」よろしくお願いします
ページ左上の や のクリックを、どうぞよろしくお願いします
ここの数字が増えると、書き手としては「ウケている」という感覚が得られ、連載を更に進化させていくモチベーションになりますので、もっとElixirネタを見たいというあなた、私達と一緒に盛り上げてください!