クライアント側に Elm を用いて、サーバー側のPhoenix.Channelと連携する方法を記述します。
サーバーには、Phoenix LiveViewによる動的サーバーサイドレンダリング2で作成したものをそのまま利用します。
題材としては、上記ページと同じようにカウンターを作成します。
クライアントとサーバー間は、WebSocket を用いて通信を行います。
また、ElmとPhoenixのバージョンはそれぞれ以下です。
項目 | バージョン |
---|---|
Phoenix | 1.4.10 |
Elm | 0.19 |
本テーマは、以下の3回に分けて記述します。
Elm を簡単に説明します。
ElmはJavaScriptにコンパイルされる関数型プログラミング言語です。
以下の特徴を持っています。
言語の雰囲気はAn Introduction to Elmを見ていただくとわかると思います。
こちらには、クライアントのみで動くカウンターが載っています。
このサーバーと連携しないバージョンのカウンターを用いて Elm の概要を説明します。
※文法自体の説明は行いません
まず、カウンターを Elm で表現すると以下のようになります。
module Main exposing (main)
import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
type alias Model =
{ count : Int }
initialModel : Model
initialModel =
{ count = 0 }
type Msg
= Increment
| Decrement
update : Msg -> Model -> Model
update msg model =
case msg of
Increment ->
{ model | count = model.count + 1 }
Decrement ->
{ model | count = model.count - 1 }
view : Model -> Html Msg
view model =
div []
[ button [ onClick Increment ] [ text "+1" ]
, div [] [ text <| String.fromInt model.count ]
, button [ onClick Decrement ] [ text "-1" ]
]
main : Program () Model Msg
main =
Browser.sandbox
{ init = initialModel
, view = view
, update = update
}
ポイントは以下です。
上記ポイント踏まえ、「ページ表示後ボタンがクリックされた」というケースをシーケンス図に表すと以下のようになります。
これを The Elm Architecture と呼んでいます。
このことにより、開発者は以下の事に集中できます。
このようなシンプルな仕組みでWebアプリケーションが構築できます。
規模が大きくなってもこの仕組みは変わりません。
また、viewが呼ばれる度にDOMを丸ごと再構築しているように見えますが、実際はそうではありません。
viewが返したDOMは、VirtualDOMと呼ばれるメモリ上のDOMに書き出され、ブラウザで表示されるDOMにはその差分が適用されます。
これが Elm の概要です。
それではPhoenix上でElmのカウンターを作る手順を以下に記します。
今までは asset の生成に brunch を用いていましたが、Phoenix1.4 から webpack に変更になりました。
そのため、webpack で Elm をセットアップします。
更にElmで構築したページを表示するための入り口を作ります。
Elm のバージョン0.19では良さそうな WebSocket 用パッケージを見つけることができませんでした。
というわけで、この部分を自作することにします。
自作するライブラリに関しては、複数のプロジェクトで使用できるように意識しながら構築していきます。
このプロジェクトでの実装は必要最小限にして、他のプロジェクトで機能が不足した場合にその時点で追加するようにします。
どのような機能を実装するかに関しては、「Phoenix LiveViewによる動的サーバーサイドレンダリング2」を踏襲します。
WebSocketでサーバーと通信を行い、サーバーが状態を持つタイプのカウンターを作ります。
これは、「Phoenix LiveViewによる動的サーバーサイドレンダリング2」のときと同じものです。
違いは、クライアント側をElmで構築するところです。
ポイントとしては以下のとおり。
Phoenixでクライアント側をElmで開発するための設定を行います。
以下は全てassetsディレクトリ下で作業します。
elmディレクトリを作成し、elm.jsonファイルを生成します。
# assets/
$ mkdir elm && cd elm && elm init
srcディレクトリとelm.jsonファイルが作成されます。
srcディレクトリにMain.elmを作成します。
現在のディレクトリ構成は以下になります。
elm
├── elm.json
└── src
└── Main.elm
ここにクライアント側のコードを記述します。
とりあえずは、動作確認用にダミーのページを作成します。
-- assets/elm/src/Main.elm
module Main exposing (main)
import Html exposing (text)
main =
text "Hello Elm and Phoenix!"
Elmのソースコードからトランスパイルできるようにするための環境を作ります。
まずは、elm-web-pack-loader をインストールします。
# assets/
$ npm i --save-dev elm-webpack-loader
webpack.config.js にてパッケージを使用するための設定を行います。
作業ディレクトリは elm.json のあるディレクトリを指定します。
// assets/webpack.config.js
module: {
rules: [
...
{
test: /\.elm$/,
exclude: [/elm-stuff/, /node_modules/],
use: {
loader: 'elm-webpack-loader',
options: {
cwd: __dirname + '/elm'
}
}
},
...
]
},
Phoenix上で実装してます。
/elm-counter
が指定されたらElmで作成したページが表示されるようにします。
いつものようにRouterから追加します。
# lib/lv_demo_web/router.ex
scope "/", LvDemoWeb do
pipe_through :browser
...
get "/elm-counter", ElmCounterController, :index ...
end
コントローラーを実装します。 Router で指定した、ElmCounterController を作成します。
# /lib/lv_demo_web/controllers/elm-counter-controller.ex
defmodule LvDemoWeb.ElmCounterController do
use LvDemoWeb, :controller
def index(conn, _params) do
render(conn, "index.html", %{val: 0})
end
end
View を作成します。
# lib/lv_demo_web/views/elm_counter_view.ex
defmodule LvDemoWeb.ElmCounterView do
use LvDemoWeb, :view
end
クライアント側の要素は Elm で構築するので、Elm 用にIDを指定したdiv要素を一つだけ作ります。
# lib/lv_demo_web/templates/elm_counter/index.html.eex
<div id="elm-main"></div>
この要素に Elm で記述した内容が表示されます。
これで Elm 開発の準備は完了しました。
ここまででセットアップは完了しました。
これから JavaScript 側のライブラリと Elm によるページ構築を行っていくことになります。