『piyopiyo.ex#3：LiveViewでポートフォリオを作ろう』手順

本記事は2022/1/8開催『piyopiyo.ex#3：LiveViewでポートフォリオを作ろう』の手順をまとめたものです。

https://piyopiyoex.connpass.com/event/232532/

:smiley:見てほしいポイント

* html/cssで構成した静的WebサイトをPhoenixアプリのビューに組み込む
* daisyUI、Tailwind CSSの便利さ
* Gigalixirへのデプロイ

この手順内では、それほどLiveViewを使っていませんが、文末でLiveViewの練習コラムを紹介しますので、LiveViewをはそちらをご参考ください。

Elixir／Phoenixの環境が未だ出来ていない方は、下記いずれかのコラムを先に行っておいてください。

https://qiita.com/kn339264/items/9d80f6cadfe341581df6

https://qiita.com/piacerex/items/6b64dfe70f48c89d8385

それではやっていきましょう。

## 手順を検証した筆者の環境

```
$ mix phx.new --version
Phoenix installer v1.6.5

$ elixir --version
Erlang/OTP 24 [erts-12.1.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]

Elixir 1.12.3 (compiled with Erlang/OTP 24)
```

## 1.Phoenixアプリ作成〜LiveView1枚ページ追加

### 1-1.DB無しのプロジェクト作成

今回はDBを使用しないので`--no-ecto`オプションを指定してPhoenixアプリを作成します。

```
$ mix phx.new piyopiyoex3_sample_portfolio --no-ecto

* creating piyopiyoex3_sample_portfolio/config/config.exs
* creating piyopiyoex3_sample_portfolio/config/dev.exs
* creating piyopiyoex3_sample_portfolio/config/prod.exs
〜略〜
Fetch and install dependencies? [Yn] Y ←依存関連のインストールを実施
* running mix deps.get
* running mix deps.compile
```

作成が完了すると、下記のようなメッセージが表示されます。

```
We are almost there! The following steps are missing:

    $ cd piyopiyoex3_sample_portfolio

Start your Phoenix app with:

    $ mix phx.server

You can also run your app inside IEx (Interactive Elixir) as:

    $ iex -S mix phx.server
```

メッセージ通りにディレクトリを移動、`mix phx.server` でアプリを起動し、
`http://localhost:4000` にブラウザでアクセス、プロジェクトが起動できるのを確認します。

### 1-2.LiveViewページ追加

DBスキーマ設定なしのLiveViewページを追加します。

```
$ mix phx.gen.live Portfolios Portfolio portfolios --no-context --no-schema

* creating lib/piyopiyoex3_sample_portfolio_web/live/portfolio_live/show.ex
* creating lib/piyopiyoex3_sample_portfolio_web/live/portfolio_live/index.ex
* creating lib/piyopiyoex3_sample_portfolio_web/live/portfolio_live/form_component.ex
* creating lib/piyopiyoex3_sample_portfolio_web/live/portfolio_live/form_component.html.heex
* creating lib/piyopiyoex3_sample_portfolio_web/live/portfolio_live/index.html.heex
* creating lib/piyopiyoex3_sample_portfolio_web/live/portfolio_live/show.html.heex
* creating test/piyopiyoex3_sample_portfolio_web/live/portfolio_live_test.exs
* creating lib/piyopiyoex3_sample_portfolio_web/live/live_helpers.ex
* injecting lib/piyopiyoex3_sample_portfolio_web.ex

Add the live routes to your browser scope in lib/piyopiyoex3_sample_portfolio_web/router.ex:

    live "/portfolios", PortfolioLive.Index, :index
    live "/portfolios/new", PortfolioLive.Index, :new
    live "/portfolios/:id/edit", PortfolioLive.Index, :edit

    live "/portfolios/:id", PortfolioLive.Show, :show
    live "/portfolios/:id/show/edit", PortfolioLive.Show, :edit
```

localhost:4000ではこの時点でエラーが発生します。＜参照するDBが無いので！
なので、DB参照しているファイルで使わないものを削除、適宜書き換えをします。

#### 1-2-a.使わないファイル削除、コード書き換え

```
$ tree lib/piyopiyoex3_sample_portfolio_web/live
lib/piyopiyoex3_sample_portfolio_web/live
├── live_helpers.ex
└── portfolio_live
    ├── form_component.ex ←削除
    ├── form_component.html.heex ←削除
    ├── index.ex
    ├── index.html.heex
    ├── show.ex ←削除
    └── show.html.heex ←削除
```

lib/piyopiyoex3_sample_portfolio_web/live/portfolio_live/index.exを書き換えます。

```elixir:lib/piyopiyoex3_sample_portfolio_web/live/portfolio_live/index.ex
defmodule Piyopiyoex3SamplePortfolioWeb.PortfolioLive.Index do
  use Piyopiyoex3SamplePortfolioWeb, :live_view

  # 下記2行は削除
  #alias Piyopiyoex3SamplePortfolio.Portfolios              # ←コメントアウト
  #alias Piyopiyoex3SamplePortfolio.Portfolios.Portfolio    # ←コメントアウト

  def mount(_params, _session, socket) do
    # {:ok, assign(socket, :portfolios, list_portfolios())}
    {:ok, socket}                                           # ←書き換え
  end

# ↓コメントアウト
#  defp apply_action(socket, :edit, %{"id" => id}) do
#    socket
#    |> assign(:page_title, "Edit Portfolio")
#    |> assign(:portfolios, Portfolios.get_portfolio!(id))
#  end

# ↓コメントアウト
#  defp apply_action(socket, :new, _params) do
#    socket
#    |> assign(:page_title, "New Portfolio")
#    |> assign(:portfolios, %Portfolio{})
#  end

  defp apply_action(socket, :index, _params) do
    socket
    |> assign(:page_title, "Listing Portfolios")
    #|> assign(:portfolio, nil)　                           # ←コメントアウト
  end
…
```

lib/piyopiyoex3_sample_portfolio_web/live/portfolio_live/index.html.heexは1行目以降を削除（DB参照、new、edit、deleteを使わないので）

```erb:lib/piyopiyoex3_sample_portfolio_web/live/portfolio_live/index.html.heex
<h1>Listing Portfolios</h1>

（上記h1以外の以下は全て消す）
<%= if @live_action in [:new, :edit] do %>
  <.modal return_to={Routes.portfolio_index_path(@socket, :index)}>
…
```

これでエラーが消えたと思います。

#### 1-2-b. ルーター設定の追記

lib/piyopiyoex3_sample_portfolio_web/router.exに作成したページのルーター設定を追記します。

```elixir:lib/piyopiyoex3_sample_portfolio_web/router.ex
defmodule Piyopiyoex3SamplePortfolioWeb.Router do
  use Piyopiyoex3SamplePortfolioWeb, :router
…
  scope "/", Piyopiyoex3SamplePortfolioWeb do
    pipe_through :browser

    get "/", PageController, :index

    live "/portfolios", PortfolioLive.Index, :index  # ←追記
  end
…
```

ここまでファイルの編集が多くて詰まった方もいるかと思います。
こちらを参照してください。
https://github.com/kn339264/piyopiyoex3_sample_portfolio/commit/f7a1f004c6cec8c3c3f0769ff19101e2b0e14f89

これで `http://localhost:4000/portfolios` で表示ができるようになりました。

#### 1-2-c. ルートディレクトリ（/）で表示させる

/直下でポートフォリオページを表示できるよう、lib/piyopiyoex3_sample_portfolio_web/router.exを書き換えます。

```elixir:lib/piyopiyoex3_sample_portfolio_web/router.ex
defmodule Piyopiyoex3SamplePortfolioWeb.Router do
  use Piyopiyoex3SamplePortfolioWeb, :router
…
  scope "/", Piyopiyoex3SamplePortfolioWeb do
    pipe_through :browser

    #get "/", PageController, :index            # ←コメントアウト

    live "/", PortfolioLive.Index, :index       # ←変更
  end
…
```

これで`http://localhost:4000/`で表示できるようになりました。

## 2. 静的Webページのデザイン埋め込み

作成するサンプル（ポートフォリオサイト）は、下記のようになります。
https://kn339264.github.io/piyopiyo3_sample_portfolio_html/

CSSと画像を新たに作成し、各種.heexテンプレートに埋め込むことで構築していきます。

Githubはこちら。
https://github.com/kn339264/piyopiyo3_sample_portfolio_html

ポートフォリオは、daisyUI、Tailwind CSSを使ってレイアウトしています。
Tailwind CSSはCSSフレームワーク、daisyUIはTailwind CSS専用のUIコンポーネント集です。

https://tailwindcss.com/

https://daisyui.com/

cssファイルを見てもらうと分かるのですが、ヘッダー画像の表示以外はほぼcssを書いていません。
この程度であればdaisyUIとTailwind CSSでも書けるので便利ですね。

### 2-1. CSS／画像作成、.heexテンプレートの書き換え

行うことは、下記リストの通りですが、サンプルポートフォリオをそこそこ真面目に作ったらやることが多くなってしまいました。

* Tailwind CSSとdaisyUIのインストール
* ポートフォリオ用cssファイル新規作成（assets/css/portfolio.css）
* app.cssでportfolio.cssをimport（assets/css/app.css）
* ヘッダーまでの内容を変更（lib/piyopiyoex3_sample_portfolio_web/templates/layout/root.html.heex）
* mainタグの修正（lib/piyopiyoex3_sample_portfolio_web/templates/layout/live.html.heex）
* メインコンテンツを記載（lib/piyopiyoex3_sample_portfolio_web/live/portfolio_live/index.html.heex）
* 画像ファイルを設置（priv/static/images/配下）

ですので、編集対象のファイルを把握したら、githubのcommitの下記差分をそのままコピーしていただいて、次の手順（デプロイ）へ進んでほしいです。
https://github.com/kn339264/piyopiyoex3_sample_portfolio/commit/c3eb4a2729280f08a51979721cb6f29277e3c6ed

分からない場合や、上手くいかない場合は、下記リポジトリをgit clone（上記で同名のフォルダができているので、いったんリネームした後）し、``mix deps.get``し、``iex -S mix phx.server``してください。

なおTailwind CSSとdaisyUIは、CDNでインストールすることで、気軽に済ませています

```elixir:lib/piyopiyoex3_sample_portfolio_web/templates/layout/root.html.heex
<!DOCTYPE html>
<html lang="ja">
…
    <script defer phx-track-static type="text/javascript" src={Routes.static_path(@conn, "/assets/app.js")}></script>
    <!-- 下記、CDNでTailwind CSSとdaisyUIをインストール -->
    <link href="https://cdn.jsdelivr.net/npm/daisyui@1.16.4/dist/full.css" rel="stylesheet" type="text/css" />
    <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2/dist/tailwind.min.css" rel="stylesheet" type="text/css" />
…
```

これで`http://localhost:4000`の内容が書き換わりました。

## 3. gigalixirへデプロイしよう

ポートフォリオは公開してなんぼなので、gigalixirへデプロイしましょう。

### 3-1. gigalixirアカウント作成、gigalixir CLIインストール

Elixir/PhoenixをサポートしているPaaSのGigalixirを使います。

https://www.gigalixir.com/

#### 3-1-a. アカウントの登録

アカウントが無い方は登録しましょう。
無料で登録でき、有料プランを利用したい場合はログイン後のアカウントページでのアップグレード操作が必要です。

![スクリーンショット 2022-01-07 0.45.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109744/4fc3d8b6-696c-9fc9-b576-8804322b62c1.png)

#### 3-1-b. gigalixir CLIのインストール

Gigalixir環境へのデプロイにはgigalixir CLIを使います。
公式doc通りにgigalixir CLIをインストールしましょう。

https://gigalixir.readthedocs.io/en/latest/getting-started-guide.html#install-the-command-line-interface

インストールできたらターミナルからgigalixirへログインしてみましょう。

```
$ gigalixir login
```

ログインしているアカウント情報の確認。

```
$ gigalixir account
```

### 3-2. gigalixirアプリ作成

gigalixirアプリ（これからデプロイする環境）を作成します。

```
$ cd piyopiyoex3_sample_portfolio/
$ gigalixir create
```

アプリの情報は`gigalixir apps`コマンドで確認できます。
また、gitのリモートリポジトリにgigalixirが追加されます。

```
$ gigalixir apps
[
  {
    "cloud": "gcp", 
    "region": "v2018-us-central1", 
    "replicas": 0, 
    "size": 0.3, 
    "stack": "gigalixir-20", 
    "unique_name": "dismal-awesome-wolverine", 
    "version": 2
  }
]
$ gigalixir git:remote 【上記unique_nameを入力、この例ではdismal-awesome-wolverine】
$ git remote -v
```

### 3-3. Gitでバージョン管理をする

Phoenixアプリのディレクトリに移動し、空のGitリポジトリを作成します。

```
$ cd piyopiyoex3_sample_portfolio/
```

※git cloneしてない方はgit initを実行してください。

```
$ git init
```

作成したアプリのファイルをインデックスに追加、変更したファイルをリポジトリに反映します。

```
$ git add -A
$ git commit -m "Initialize repository"
```

### 3-4. 本番環境設定追記

#### 3-4-a. 何もせずにデプロイしてみる

```
$ git push gigalixir main
```

本番環境用のエラーログが出るはずです。

```
$ git push gigalixir main
Enumerating objects: 115, done.
Counting objects: 100% (115/115), done.
〜〜略〜〜
-----> Checking Erlang and Elixir versions
       Sorry, an elixir_buildpack.config is required. Please see https://github.com/HashNuke/heroku-buildpack-elixir#configuration
remote: 2022/01/07 15:06:06 exit status 1
remote: Deploy aborted
To https://git.gigalixir.com/lazy-trim-kitten.git/
 ! [remote rejected] main -> main (pre-receive hook declined)
```

ではデプロイ用の設定をしていきましょう。

#### 3-4-b. Gigalixirデプロイ用設定を追加

Elixir、Erlang、Node.jsのバージョンがGigalixirのデフォルトのビルドパックだと古いので、開発環境とバージョンを合わせます。（ご自分の環境のバージョンに合わせて適宜書き換えてください。）

```
echo "elixir_version=1.12.3" > elixir_buildpack.config
echo "erlang_version=24.0" >> elixir_buildpack.config
echo "node_version=14.17.6" > phoenix_static_buildpack.config
```

次に、Phoenix1.6.0以降に対応されたesbuildのビルド設定を追加します。
assets/package.jsonファイルがなければ新規作成します。

```json:assets/package.json
{
  "scripts": {
    "deploy": "cd .. && mix assets.deploy && rm -f _build/esbuild*"
  }
}
```

コミットするのを忘れないようにしましょう。

```
git add elixir_buildpack.config phoenix_static_buildpack.config assets/package.json
git commit -m "set elixir, erlang, and node version"
```

#### 3-4-c. 本番環境設定の書き換え

本番環境のPhoenixアプリのエンドポイントの設定を追記します。

```elixir:config/prod.exs
config :piyopiyoex3_sample_portfolio, Piyopiyoex3SamplePortfolioWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json"
#　↓ 書き換える
config :sample_portfolio, SamplePortfolioWeb.Endpoint,
  url: [host: "{アプリのunique_name}.gigalixirapp.com"],
  check_origin: ["//{アプリのunique_name}.gigalixirapp.com"],
  cache_static_manifest: "priv/static/cache_manifest.json"
```

アプリのunique_nameは`gigalixir apps`で確認できます。

こちらも変更内容をコミットしましょう。

```
$ git add config/prod.exs
$ git commit -m "add prod endpoint"
```

#### 3-4-d. 再度デプロイ

```
$ git push gigalixir main
```

下記コマンドで、アプリのステータスが`Healthy`になるまで待ちましょう。

```
$ gigalixir ps
```

なお、デプロイ中にエラーが出る場合は、下記をお試しください

```
$ git push -f gigalixir main
```

Gigalixirで稼働しているアプリを表示します。
反映されるまで数分かかることがあるので、まったり待ちましょう。

```
$ gigalixir open
```

表示できたらデプロイおわりです、おめでとうございます！

※デプロイ設定のコードはこちらです。
https://github.com/kn339264/piyopiyoex3_sample_portfolio/tree/deploy_gigalixir

## 最後に

当日の勉強会では1時間弱でここまで書いた内容を進行する予定ですが、時間的にボリュームが多すぎるなと思いながら記事を書いています。
参加者の皆様おつかれさまでした！

## 参考：LiveViewの練習コラムのご紹介

- LiveViewでCRUD操作するDB操作ページ群を生成（Scaffold）

https://qiita.com/t-yamanashi/items/89b63335d5a3d87efea6

- LiveViewでグラフィック操作

https://qiita.com/piacerex/items/9b9e2fc59b74b529b66b

- LiveViewでQiita記事をリアルタイム検索するSPAを開発

https://qiita.com/piacerex/items/3f8ee18c9443d63955bf

- LiveView CRUDのAPI化

https://qiita.com/omini/items/a157960ebe24cf890091