Phoenix+Elmでマルチページを実現するための枠組みの基本を考えてみました。SPA(シングルページアプリケーション)ではなくマルチページです。Brunchのなじみが浅い私の個人的な備忘録なので、もっとベターな方法があると思いますが、ご指摘いただければ幸いです。
#1. ページ設計
まず今回の実験のページ設計ですが、以下のように3枚のページ(html)を用意するつもりです。それぞれのパスでアクセスすれば、elmプログラムを含んだページを表示します。目標はシンプルです。
"/" -- トップページ (Elm無し)
"/page1" -- ページ1(Hello.elm)
"/page2" -- ページ2(MyButtons.elm)
#2. Plug.Staticの設定
それではプロジェクトを作成します。
mix phx.new multi_pages --no-ecto
後で述べますが、Elmプログラムをコンパイルした結果のjsファイルはテンプレートファイルから以下のようにして直接ロードします。
<script src="/js/mybuttons.js"></script>
そのためPhoenixの設定でstaticファイルを読み込む設定を変えます。onlyをコメントアウトします。許可ファイルを追加しても良いですが、ここではコメントアウトしpriv/staticにあるファイルを全てpublicなものにします。PhoenixでStatic Fileをサーブする - Qiita
#
plug Plug.Static,
at: "/", from: :multi_pages, gzip: false
# only: ~w(css fonts images js favicon.ico robots.txt)
#
#3. routerの設定
上の「1.ページ設計」で示した通りにrouter.exを変更します。
#
scope "/", MultiPagesWeb do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
get "/page1", Page1Controller, :hello
get "/page2", Page2Controller, :buttons
end
#
#4. トップページの設定
最初にトップページを変更します。ヘッダーに他のページ(page1, page2)へのリンクを貼ります。デフォルトのレイアウト(app.html.eex)を変更します。
#
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
<a href="/page1">ページ1</a> /
<a href="/page2">ページ2</a>
<main role="main">
<%= render @view_module, @view_template, assigns %>
</main>
</div> <!-- /container -->
</body>
</html>
またこの時以下の行を削除しておきます。トップページではjsやelmは使わないので不要です。
<script src="<%= static_path(@conn, "/js/app.js") %>"></script>
またトップページのテンプレートは変更しません。
lib/multi_pages_web/templates/page/index.html.eex
controllerもviewもデフォルトのままで変更しません。
以上でトップページは終わりです。
#5. ページ1の設定
ページ1を追加していきます。controllerを以下のように定義します。今回は実験ですのでレイアウトも変更しています。
defmodule MultiPagesWeb.Page1Controller do
use MultiPagesWeb, :controller
def hello(conn, _params) do
conn
|> put_layout("layout_hello.html")
|> render("hello.html")
end
end
viewも定義します。中身はデフォルトのままです。
defmodule MultiPagesWeb.Page1View do
use MultiPagesWeb, :view
end
テンプレートを追加するので、ディレクトリを作成します。
mkdir lib/multi_pages_web/templates/page1
テンプレートを追加します。elmプログラムのためのプレースフォルダを設定し、elmプログラムを明示的に指定していることに注意してください。まあ、通常のelmの設定ですね。
<h1>Page1</h1>
<h3>Hello page</h3>
<div id="elm-hello-area"></div>
<script src="/js/vendor/hello.js"></script>
<script>
Elm.Hello.embed(document.getElementById("elm-hello-area"));
</script>
レイアウトはトップページのものをコピーし、リンクを以下のように変更します。
#
<a href="/">トップページ</a> /
<a href="/page2">ページ2</a>
#
以上でページ1の設定は終わりです。
#6. ページ2の設定
ページ2の設定は、ページ1とほぼ同じです。ファイル名やモジュール名、関数名などが異なるだけです。
controllerです。
defmodule MultiPagesWeb.Page2Controller do
use MultiPagesWeb, :controller
def buttons(conn, _params) do
conn
|> put_layout("layout_buttons.html")
|> render("buttons.html")
end
end
viewです。
defmodule MultiPagesWeb.Page2View do
use MultiPagesWeb, :view
end
テンプレートディレクトリを作成します。
mkdir lib/multi_pages_web/templates/page2
テンプレートです。
<h1>Page2</h1>
<h3>My Buttons page</h3>
<div id="elm-buttons-area"></div>
<script src="/js/vendor/mybuttons.js"></script>
<script>
Elm.MyButtons.embed(document.getElementById("elm-buttons-area"));
</script>
レイアウトです。
#
<a href="/">トップページ</a> /
<a href="/page1">ページ1</a>
#
以上でページ2の設定を終わります。
#7. Elmクライアントの設定
Brunchの設定をしていきます。
cd assets/
npm install --save-dev elm-brunch
brunch-config.jsを変更します。今回の肝の部分です。
#
// Phoenix paths configuration
paths: {
// Dependencies and current project directories to watch
// (1)"elm"を追加
watched: ["static", "css", "js", "elm", "vendor"],
// Where to compile files to
public: "../priv/static"
},
// Configure your plugins
plugins: {
babel: {
// Do not use ES6 compiler in vendor code
ignore: [/vendor/]
},
// (2)elmBrunchを追加
elmBrunch: {
elmFolder: "elm",
mainModules: ["Hello.elm", "MyButtons.elm"], // Elmプログラム
outputFolder: "../static/js/vendor" // Elmの実行形をstaticに吐き出す
}
},
#
(※重要)outputFolderにはvendorサブディレクトリが指定されていることに注意してください。brunchにおいてはvendorにあるfileが優先的にロードされるようです。つまりElmのjsファイルが安全にロードされます。単にoutputFolder を "../static/js"と vendor抜きで指定すると、かなりの確率でElmのjsファイルのロードに失敗してしまいます。
今回は2つのelmプログラムを使います。page1でHello.elmを、page2でMyButtons.elmを走らせます。elmプログラムをmainModulesで指定し、assets/static/jsにコンパイルするように指定しました。brunchはassets/static/ 下のファイルををそのままpriv/static/にコピーします。priv/static/は「2.Plug.Staticの設定」での設定によりpublicとなっており、テンプレートから以下のように指定することができます。
<script src="/js/vendor/hello.js"></script>
<script src="/js/vendor/mybuttons.js"></script>
次にassets/static/jsディレクトリとasstes/elmディレクトリを作成し、必要なelmパッケージをインストールしておきます。
mkdir static/js
mkdir elm
cd elm
elm-package install elm-lang/html
Hello.elmがpage1で走らせるelmプログラムです。
module Hello exposing (..)
import Html exposing (text)
main =
text "Hello, World!"
MyButtons.elmがpage2で走らせるelmプログラムです。これはMyButtonsBody.elmをimportしています。動作を確認するためにわざとElmプログラムを分割してみました。そもそもelmコンパイラは、複数の分割ファイルをリンクしてくれますので、この点ではbrunchに頼る必要がありません。複数ファイルを一つの実行形にまとめるのはelmコンパイラであり、brunchではありません。ちなみにbrunchがまとめたapp.jsファイルは使わないで無視します。
module MyButtons exposing (..)
import Html
import MyButtonsBody exposing (model, update, view)
main =
Html.beginnerProgram { model = model, view = view, update = update }
MyButtons.elmはMyButtonsBody.elmをimportしています。
module MyButtonsBody exposing (model, update, view)
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)
-- MODEL
type alias Model = Int
model : Model
model =
0
-- UPDATE
type Msg = Increment | Decrement
update : Msg -> Model -> Model
update msg model =
case msg of
Increment ->
model + 1
Decrement ->
model - 1
-- VIEW
view : Model -> Html Msg
view model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [] [ text (toString model) ]
, button [ onClick Increment ] [ text "+" ]
]
以上でクライアント側のElmの設定は終わりです。
#8. プログラムの実行
トップページです。レイアウトを変更してページ1とページ2のリンクを設けました。
ページ1です。リンクを変更しました。「Hello world!」はelmプログラムが走った結果です。
ページ2です。リンクを変更しました。カウンターを増減させるボタンが表示されています。これもelmプログラムです。
以上です。