Phoenix.LiveViewの最新インストール方法

2020-04-03

はじめに

最新の Phoenix.LiveView の組み込み方法を紹介します。
直近3回の LiveView の記事では、LiveView のバージョンを0.8に固定してプロジェクトを構築していました。
それはバージョン0.9および0.10では、CSSファイルが読み込めない等の問題が発生していたからでした。
今回その問題を解消しましたので、現バージョンでのプロジェクト構築方法を記述します。

本記事に使用したリポジトリです。

なぜだめだったのか

0.9からLayoutの仕様が変わり、LiveViewからは templates/layout/app.html.eex のファイルが使用できなくなった事に起因していました。
現在公開されている公式のドキュメントでは、その記述が無く仕様の変更を見落としていました。

なぜわかったか

私と同じ問題に出くわしている方が、issuesで質問してくれて、仕様変更の見落としに気づきました。
すぐにでも本家のページが更新される可能性がありますが、せっかくなので現時点での Phoenix.LiveView のインストール方法をまとめました。
ちなみに、本家のインストールページはこちらとなっております。
また、Phoenix 作者が記述しているサンプルプロジェクトのソースも参考にしました。
ただし、サンプルプロジェクトは0.10に対応していなかったので、そこのところはチェンジログを追いかけながら修正しました。

新しいLayoutについて

今までは以下の構造でした。

ファイル役割
app.html.eex通常のViewとLiveViewの両方に対応。

それが、以下のように変更されました。

ファイル役割今までとの違い
root.html.eex通常のViewとLiveViewの両方に対応app.html.eexの役割がroot.html.eexに変更。
app.html.eex通常のViewのみに対応通常のViewで必要な部分のみ記述するように変更。
live.html.leexLiveViewのみに対応LiveViewで必要な部分のみ記述するように変更。

そのためLayoutに関する記述方法が変わりました。

バージョン情報

記事公開時点で使用しているバージョンは以下です。
このバージョンをもとに説明します。

項目バージョン
erlang22.3
elixir1.10.2-otp-22
Phoenix1.4.16
Phoenix.LliveView0.10.0

Phoenix 1.4.16 の環境では、本家のインストールページで記載されている手順のいくつかが既に組み込まれています。そのためいくつかの手順が本記事の記述と異なります。

手順

手順は以下のようになります。
説明のために、LiveViewページの追加も行います。

  1. プロジェクトの作成
  2. LiveViewのインストール
  3. LiveViewページの追加

プロジェクトの作成

まずはプロジェクトを作成します。
プロジェクト名は hello_lv10 で、ectoを使用しない設定にします。

$ mix phx.new hello_lv10 --no-ecto

作成が終わったらプロジェクトのルートに移動します。

LiveViewのインストール

Phoenix.LiveViewをインストールする手順を記述します。

LiveViewモジュールの取得

mix.exs に以下の記述を追加します。

# mix.exs
def deps do
  [
    {:phoenix_live_view, "~> 0.10.0"},
    {:floki, ">= 0.0.0", only: :test}
  ]
end

以下のコマンドによりモジュールを取得します。

$ mix deps.get

Routerモジュールへの記述の追加

router.ex にて行の先頭が + で示されている箇所を追加し、- で示されている箇所を削除します。

# lib/my_app_web/router.ex
+ import Phoenix.LiveView.Router

pipeline :browser do
  ...
  plug :fetch_session
- plug :fetch_flash
+ plug :fetch_live_flash
  ...
+ plug :put_root_layout, {HelloLv10Web.LayoutView, :root}
end

ここで、以下の行が新しく加わった部分であり、新しい Layout に対応した部分でもあります。

plug :put_root_layout, {HelloLv10Web.LayoutView, :root}

HelloLv10Webモジュールへの記述の追加

hello_lv10_web.ex でハイライトされている行を追加します。

# lib/hello_lv10_web.ex

def controller do
  quote do
    ...
    import Phoenix.LiveView.Controller  end
end

def view do
  quote do
    ...
    import Phoenix.LiveView.Helpers  end
end

def router do
  quote do
    ...
    import Phoenix.LiveView.Router  end
end

Endpointモジュールへの記述の追加

endpoint.ex にてハイライトされている行を追加します。

# lib/my_app_web/endpoint.ex
defmodule MyAppWeb.Endpoint do
  use Phoenix.Endpoint
  # ...
  socket "/live", Phoenix.LiveView.Socket,    websocket: [connect_info: [session: @session_options]]  # ...
end

JavaScriptモジュールの追加

package.json にてハイライトされている行を追加します。

// assets/package.json
{
  "dependencies": {
    "phoenix": "file:../deps/phoenix",
    "phoenix_html": "file:../deps/phoenix_html",
    "phoenix_live_view": "file:../deps/phoenix_live_view"  }
}

プロジェクトルートのコマンドプロンプトから、以下コマンドでモジュールを追加します。

$ npm install --prefix assets

app.jsへの記述

app.js へ以下の記述を追加します。

// assets/js/app.js
import {Socket} from "phoenix"
import LiveSocket from "phoenix_live_view"

let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content");
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}});

// connect if there are any LiveViews on the page
liveSocket.connect();

これで LiveView のインストールは完了です。

LiveViewページの作成

動作確認のため LiveView のページを作成します。

レイアウトファイルの追加

以下のハイライトされている行にある3ファイルを作成します。

├── lib
│   ├── hello_lv10_web
│   │   ├── templates
│   │   │   ├── layout
│   │   │   │   ├── app.html.eex│   │   │   │   ├── live.html.leex│   │   │   │   └── root.html.eex
root.html.eexの作成

生成された app.html.eex をもとに root.html.eex を作ります。
以下はもともとの app.html.eex です。 ハイライト箇所は通常のViewに固有のものなので削除します。

<!-- lib/hello_lv10_web/templates/layout/app.html.eex -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title><%= assigns[:page_title] || "HelloLv10 · Phoenix Framework" %></title>
    <link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
    <%= csrf_meta_tag() %>
  </head>
  <body>
    <header>
      <section class="container">
        <nav role="navigation">
          <ul>
            <li><a href="https://hexdocs.pm/phoenix/overview.html">Get Started</a></li>
          </ul>
        </nav>
        <a href="https://phoenixframework.org/" class="phx-logo">
          <img src="<%= Routes.static_path(@conn, "/images/phoenix.png") %>" alt="Phoenix Framework Logo"/>
        </a>
      </section>
    </header>
    <main role="main" class="container">      <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>      <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>      <%= render @view_module, @view_template, assigns %>    </main>    <script type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
  </body>
</html>

削除した場所に以下の記述を追加します。

<%= @inner_content %>

この記述が、もともとの以下の記述のハイブリッド版になります。

<%= render @view_module, @view_template, assigns %>

app.js の読み込み位置を head 要素内に移動して完成です。
最終的に root.html.eex は以下のようになりました。

<!-- lib/hello_lv10_web/templates/layout/root.html.eex -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title><%= assigns[:page_title] || "HelloLv10 · Phoenix Framework" %></title>
    <link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
    <%= csrf_meta_tag() %>
    <script type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>  </head>
  <body>
    <header>
      <section class="container">
        <nav role="navigation">
          <ul>
            <li><a href="https://hexdocs.pm/phoenix/overview.html">Get Started</a></li>
          </ul>
        </nav>
        <a href="https://phoenixframework.org/" class="phx-logo">
          <img src="<%= Routes.static_path(@conn, "/images/phoenix.png") %>" alt="Phoenix Framework Logo"/>
        </a>
      </section>
    </header>
    <%= @inner_content %>  </body>
</html>
app.html.eexの作成

通常のViewで必要なのは、もともとの app.html.eex で記述していた以下になります。
この記述のみをそのまま残してこのファイルは終了です。

 <!-- lib/hello_lv10_web/templates/layout/app.html.eex -->
<main role="main" class="container">
  <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
  <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
  <%= render @view_module, @view_template, assigns %>
</main>
live.html.leexの作成

このファイルだけ拡張子が eex ではなく leex となります。
LiveViewページ用のレイアウトですが、以下のようにしました。
root.html.eex でのレイアウトが適用された上に、このレイアウトが適用されることになります。

<!-- lib/hello_lv10_web/templates/layout/live.html.eex -->
<h1>This is LiveView</h1>
<div id="main">
  <%= @live_module.render(assigns) %>
</div>

LiveViewの追加

LiveView のページとして、以下の2ページを作ります。
それぞれ内容を変えます。
リンクは特に作成せず、アクセスはダイレクトにURLを打ち込むものとします。

ページURL内容
LivePage1/live1レイアウトファイルとしてroot.html.eex のみを使用する。
LivePage2/live2レイアウトファイルとしてroot.html.eex に加えて、live.html.leexも使用する。
ファイルの追加

それぞれのページ用に以下の2ファイルを作成します。

├── lib
│   ├── hello_lv10_web
│   │   ├── live
│   │   │   ├── live_page1.ex
│   │   │   └── live_page2.ex
Routerモジュールへ

ハイライトで示した2ページ分を追加します。

# lib/hello_lv10_web/router.ex
scope "/", HelloLv10Web do
  pipe_through :browser

  get "/", PageController, :index
  live "/live1", LivePage1  live "/live2", LivePage2end
LivePage1

通常通りに LiveView を作成します。
必須である2つの関数のみ実装します。

# lib/hello_lv10_web/live/live_page1.ex
defmodule HelloLv10Web.LivePage1 do
  use Phoenix.LiveView

  def render(assigns) do
    ~L"""
      <div>Hello LiveView Page1.</div>
      <div>There is no layout.</div>
    """
  end

  def mount(_arg1, _session, socket) do
    {:ok, socket}
  end

end
LivePage2

LiveView 用のレイアウトを読み込む形で実装します。
ハイライトされている行でレイアウトファイルを指定しています。
それ以外は LivePage1 と本質的な違いはありません。

# lib/hello_lv10_web/live/live_page2.ex
defmodule HelloLv10Web.LivePage2 do  use Phoenix.LiveView, layout: {HelloLv10Web.LayoutView, "live.html"}

  def render(assigns) do
    ~L"""
      <div>Hello LiveView Page2.</div>
      <div>I'm in the live layout.</div>
    """
  end

  def mount(_arg1, _session, socket) do
    {:ok, socket}
  end

end

これで実装は完了です。

おわりに

とりあえず、これで最新のLiveViewを使って開発を進められるようになりました。
過去のプロジェクトについても、適宜アップデートしていこうと思います。

本記事に使用したリポジトリです。