🚀

PETAL(Phoenix/Elixir/Tailwindcss/Alpinejs/LiveView) 環境構築 2022/8

2022/08/04に公開
  • 新プロジェクトを構築した際のメモです。
  • おまけでdaisyUIも適用しています

PETALの導入方法

P: Phoenix

Dockerfileの例

FROM elixir:1.13

ENV PHOENIX_VERSION 1.6.11
ENV NPM_VERSION 8.16.0
ENV NODE_VERSION 18.x

# NODE/NPM
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION} | bash - \
  && apt install -y nodejs
RUN npm install npm@${NPM_VERSION} -g

# Phoenix
RUN mix local.hex --force && \
  mix archive.install --force hex phx_new ${PHOENIX_VERSION} && \
  mix local.rebar --force

E: Elixir

Docker環境ままです

T: Tailwindcss

plugin(({addVariant}) => addVariant('phx-no-feedback', ['&.phx-no-feedback', '.phx-no-feedback &']))

A: Alpine.js

Alpine.jsはLiveViweと役割が重なるところがありますが、

  • ベースとなるのはLiveViewで、クライアント側で完結するような処理にAlpine.jsを使う
  • LiveViewで扱うリソースとAlpine.jsで扱うリソースとは、扱いを混ぜない方が良い
  • LiveViewTestを活かす意味でもJSは控えめにしておきたい(ベースはLiveView)

という感想です。

パッケージインストール

cd assets && npm install alpinejs

設定差分 assets/js/app.js

+import Alpine from "alpinejs"
+Alpine.start()

-let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}})
+let liveSocket = new LiveSocket("/live", Socket, {
+  params: {_csrf_token: csrfToken},
+  dom: {
+    onBeforeElUpdated(from, to) {
+      if (from._x_dataStack) {
+        Alpine.clone(from, to)
+      }
+    }
+  }
+})

確認用HTML templates/page/index.html.heex

<div x-data="{open: false}">
    <button @click="open = true">Expand</button>
    <span x-show="open">
      Content...
    </span>
</div>

L: LiveView

LiveViewは、現在のPhenixではデフォルトで採用されています。

おまけ: DaisyUI

DaisyUIはTailwindcssを使用したコンポーネントセットです

パッケージインストール

cd assets && npm install daisyui theme-change
  • テーマ変更方法として公式でtheme-changeを使うのをお勧めされています

設定例 assets/tailwind.config.js

module.exports = {
  ...
  plugins: [
    ...
    require("daisyui")
  ],
  daisyui: {
    themes: ["garden", "light", "forest", "dark"],
    darkTheme: "forest"
  }
]
  • themes: 最初に指定したテーマがデフォルトで適用されます
  • darkTheme: ユーザーがダークモードを使用した際に適用されるデフォルトです
    • // themesからよしなにしてくれるようですが明示した方がわかりやすいと考えています

設定例 assets/js/app.js

import { themeChange } from "theme-change";
themeChange();

確認用HTML例 templates/page/index.html.heex

<select class="select" data-choose-theme>
  <option value="">Default</option>
  <option value="light">Light</option>
  <option value="forest">Forest</option>
  <option value="dark">Dark</option>
</select>

(追記)Alpine.jsとあわせて、現在の設定を取れるようにするには下記のような感じです。

<div
  x-data="{'theme': ''}"
  x-init="$nextTick(() => theme=document.querySelector('html').dataset.theme || 'cupcake')">
  <select
    class="select select-bordered"
    x-model="theme"
    data-choose-theme
  >
  略
  </select>
</div>

(追記)↑ もしかするとLiveViewと相性が悪いかもしれません。画面切り替えを行うと動かなくなる事象がありました。

参考させていただいた資料

コード差分

以下は、Tailwindcss導入時のコード差分です。

diff --git a/assets/css/app.css b/assets/css/app.css
--- a/assets/css/app.css
+++ b/assets/css/app.css
@@ -1,5 +1,8 @@
+@import "tailwindcss/base";
+@import "tailwindcss/components";
+@import "tailwindcss/utilities";
+
 /* This file is for your main application CSS */
 -@import "./phoenix.css";

  /* Alerts and form errors used by phx.new */
   .alert {
diff --git a/assets/tailwind.config.js b/assets/tailwind.config.js
new file mode 100644
--- /dev/null
+++ b/assets/tailwind.config.js
@@ -0,0 +1,22 @@
+// See the Tailwind configuration guide for advanced usage
+// https://tailwindcss.com/docs/configuration
+
+let plugin = require('tailwindcss/plugin')
+
+module.exports = {
+  content: [
+    './js/**/*.js',
+    '../lib/*_web.ex',
+    '../lib/*_web/**/*.*ex'
+  ],
+  theme: {
+    extend: {},
+  },
+  plugins: [
+    require('@tailwindcss/forms'),
+    plugin(({addVariant}) => addVariant('phx-no-feedback', ['&.phx-no-feedback', '.phx-no-feedback &'])),
+    plugin(({addVariant}) => addVariant('phx-click-loading', ['&.phx-click-loading', '.phx-click-loading &'])),
+    plugin(({addVariant}) => addVariant('phx-submit-loading', ['&.phx-submit-loading', '.phx-submit-loading &'])),
+    plugin(({addVariant}) => addVariant('phx-change-loading', ['&.phx-change-loading', '.phx-change-loading &']))
+  ]
+}
diff --git a/config/config.exs b/config/config.exs
--- a/config/config.exs
+++ b/config/config.exs
@@ -47,6 +47,16 @@ config :logger, :console,
 # Use Jason for JSON parsing in Phoenix
 config :phoenix, :json_library, Jason

+# Use Tailwindcss
+config :tailwind, version: "3.1.6", default: [
+  args: ~w(
+    --config=tailwind.config.js
+    --input=css/app.css
+    --output=../priv/static/assets/app.css
+  ),
+  cd: Path.expand("../assets", __DIR__)
+]
+
 # Import environment specific config. This must remain at the bottom
 # of this file so it overrides the configuration defined above.
 import_config "#{config_env()}.exs"
diff --git a/config/dev.exs b/config/dev.exs
--- a/config/dev.exs
+++ b/config/dev.exs
@@ -26,7 +26,9 @@ config :tontenkan, TontenkanWeb.Endpoint,
   secret_key_base: "SqNDkijN2qE5jf7X0cjdC3rgggxZnq/pyRCaKHsBf0ccJwxH6ZAGHRXizTrgOZsv",
   watchers: [
     # Start the esbuild watcher by calling Esbuild.install_and_run(:default, args)
-    esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}
+    esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]},
+    # Tailwindcss
+    tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]}
   ]

 # ## SSL Support
diff --git a/lib/tontenkan_web/templates/page/index.html.heex b/lib/tontenkan_web/templates/page/index.html.heex
--- a/lib/tontenkan_web/templates/page/index.html.heex
+++ b/lib/tontenkan_web/templates/page/index.html.heex
@@ -3,6 +3,10 @@
   <p>Peace of mind from prototype to production</p>
 </section>

+<h1 class="text-3xl font-bold underline">
+  Hello world!
+</h1>
+
 <section class="row">
   <article class="column">
     <h2>Resources</h2>
diff --git a/mix.exs b/mix.exs
--- a/mix.exs
+++ b/mix.exs
@@ -49,6 +49,8 @@ defmodule Tontenkan.MixProject do
       {:gettext, "~> 0.18"},
       {:jason, "~> 1.2"},
       {:plug_cowboy, "~> 2.5"},
+      # assets
+      {:tailwind, "~> 0.1", runtime: Mix.env() == :dev},
       # tools
       {:mecab, "~> 1.0"},
       # for test
@@ -68,7 +70,7 @@ defmodule Tontenkan.MixProject do
       "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
       "ecto.reset": ["ecto.drop", "ecto.setup"],
       test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
-      "assets.deploy": ["esbuild default --minify", "phx.digest"]
+      "assets.deploy": ["tailwind default --minify", "esbuild default --minify", "phx.digest"]
     ]
   end
 end

Discussion