Dialupフレームワークを使ったアプリケーションの作成方法。
前提条件
- Elixir 1.19以上
インストール
1. ジェネレータのインストール
mix dialup.new コマンドをグローバルに使えるよう、まず dialup_new をインストール:
mix archive.install hex dialup_new
2. 新規プロジェクト作成
mix dialup.new my_app
cd my_app
mix deps.get
これにより、以下のファイルが自動的に生成:
my_app/
├── README.md
├── mix.exs # {:dialup, "~> 0.1.3"} を含む
├── .gitignore
├── priv/static/ # 静的ファイル置き場(画像・フォント・favicon など)
└── lib/
├── my_app.ex # Application モジュール
├── root.html.heex # HTML シェル(<head>・hooks・analytics をカスタマイズ)
└── app/
├── layout.ex # Dialup.App.Layout(ルートレイアウト)
├── layout.css # レイアウト共通スタイル(コロケーション CSS)
├── page.ex # Dialup.App.Page(ホームページ)
├── page.css # ホームページ固有スタイル(コロケーション CSS)
├── error.ex # エラーページ(404 / 500)
└── error.css # エラーページスタイルジェネレータのオプション
# アプリ名をカスタマイズ
mix dialup.new hello --app hello_world
# モジュール名もカスタマイズ
mix dialup.new my_project --app my_project --module MyAwesomeProject
# ヘルプ確認
mix help dialup.new
2. 起動
mix run --no-halt
ブラウザで http://localhost:4000 にアクセス。
手動でのセットアップ(既存プロジェクトへの追加)
既存のMixプロジェクトにDialupを追加する場合:
1. dialupの依存追加
mix.exs に追加:
defp deps do
[
{:dialup, "~> 0.1.3"}
]
endmix deps.get
2. アプリケーション設定
lib/my_app.ex を作成:
defmodule MyApp do
use Application
use Dialup,
app_dir: __DIR__ <> "/app",
title: "My App",
lang: "en"
@impl Application
def start(_type, _args) do
children = [
{Dialup, app: __MODULE__, port: 4000}
]
opts = [
strategy: :one_for_one,
name: __MODULE__.Supervisor
]
Supervisor.start_link(children, opts)
end
end3. root.html.heex の作成
lib/root.html.heex を作成:
<!DOCTYPE html>
<html lang="{@lang}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{@title}</title>
</head>
<body>
<div id="dialup-root">{raw(@inner_content)}</div>
<script src="/idiomorph.js"></script>
<script src="/dialup.js"></script>
<script>Dialup.connect();</script>
</body>
</html>4. 最初のページ作成
lib/app/page.ex を作成:
defmodule Dialup.App.Page do
use Dialup.Page
def mount(_params, assigns) do
{:ok, Map.put(assigns, :count, 0)}
end
def handle_event("increment", _value, assigns) do
{:patch, "count", render_count(assigns),
Map.update!(assigns, :count, &(&1 + 1))}
end
defp render_count(assigns) do
~H"""
<p id="count">Count: {@count}</p>
"""
end
def render(assigns) do
~H"""
<h1>Hello Dialup</h1>
<%= render_count(assigns) %>
<button ws-event="increment">+</button>
"""
end
end5. レイアウトの追加(オプション)
lib/app/layout.ex を作成:
defmodule Dialup.App.Layout do
use Dialup.Layout
def mount(session) do
{:ok, session}
end
def render(assigns) do
~H"""
<nav>
<a ws-href="/">Home</a>
</nav>
{raw(@inner_content)}
"""
end
end注意: Layout は
#dialup-rootの中身のみを生成します。<html>,<head>,<body>タグはlib/root.html.heexが担当します。
6. 起動
mix run --no-halt
開発時ホットリロード
開発環境(Mix.env() == :dev)では自動的に Dialup.Reloader が起動します。lib/ 以下の .ex / .css ファイルを保存するたびに:
- 変更を検出(ポーリング間隔: 500ms)
- 自動でリコンパイル(
.css変更は@external_resource経由で対応する.exも再コンパイル) - 既存のWebSocket経由でブラウザを更新(ページリロードなし)
プロジェクト構成
推奨ディレクトリ構造:
my_app/
├── priv/static/ # 静的ファイル(/images/logo.png → /images/logo.png)
└── lib/
├── my_app.ex # Application モジュール(use Dialup)
├── root.html.heex # HTML シェル(<head>・Dialup.connect・analytics)
└── app/
├── layout.ex # Dialup.App.Layout(共通レイアウト)
├── layout.css # 全ページ共通スタイル(オプション)
├── page.ex # Dialup.App.Page(/ ルートページ)
├── page.css # / 固有スタイル(オプション)
├── error.ex # エラーページ(404 / 500)
├── about/
│ └── page.ex # Dialup.App.About(/about)
└── users/
├── layout.ex # Dialup.App.Users.Layout(/users 以下のレイアウト)
├── layout.css # /users 配下全ページのスタイル(オプション)
└── [id]/
└── page.ex # Dialup.App.Users.Id(/users/:id)root.html.heex
lib/root.html.heex は全ページ共通の HTML シェルです。<head> タグへの追加、JS ライブラリの読み込み、Dialup.connect() のカスタマイズをここで行います。
<!DOCTYPE html>
<html lang="{@lang}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{@title}</title>
<!-- ここに <meta>, <link>, analytics を自由に追加 -->
</head>
<body>
<div id="dialup-root">{raw(@inner_content)}</div>
<script src="/idiomorph.js"></script>
<script src="/dialup.js"></script>
<script>
Dialup.connect({
/* hooks: { MyHook: { mounted(el) {}, destroyed(el) {} } } */
});
</script>
</body>
</html>id="dialup-root" と Dialup.connect() は必須です。削除すると動作しません。
静的ファイル配信
priv/static/ に配置したファイルは / パスから自動配信されます。
<img src="/images/logo.png">
<link rel="icon" href="/favicon.ico">コロケーション CSS
page.ex / layout.ex と同名の .css ファイルをコロケーション(同ディレクトリに配置)すると、コンパイル時に自動でスコーピングされて注入される。ビルドツール不要。
/* lib/app/page.css */
.hero {
text-align: center;
padding: 4rem 2rem;
}
h1 { font-size: 3rem; }CSS はモジュール名由来の一意なクラス(d-xxxxxxx)でラップされ、他ページへの影響を防ぐ。layout.css はその layout が包む全子ページに自然にカスケードする。詳細は Routing を参照。エージェントが「ページごとに CSS が分離されている」と誤解しがちな点は Agent pitfalls を参照。
命名規則
| ファイルパス | モジュール名 | URL |
|---|---|---|
app/page.ex | Dialup.App.Page | / |
app/layout.ex | Dialup.App.Layout | 全ページ |
app/about/page.ex | Dialup.App.About.Page | /about |
app/users/[id]/page.ex | Dialup.App.Users.Id.Page | /users/:id |
app/users/layout.ex | Dialup.App.Users.Layout | /users/* |
基本的な動作確認
ボタンをクリックしてカウントが増えれば成功。ページ遷移はSPAとして動作し、WebSocket経由でサーバーと通信しています。
Plug ミドルウェア
use Dialup に plugs: オプションを指定することで、HTTP リクエスト処理のパイプラインにカスタム Plug を追加できる。認証、CORS、ロギングなどに使用する。
defmodule MyApp do
use Application
use Dialup,
app_dir: __DIR__ <> "/app",
title: "My App",
plugs: [
MyApp.AuthPlug,
{Corsica, origins: "*"}
]
# ...
endPlug は指定した順序で実行される。Plug が conn を halt した場合、後続の Plug と Dialup の処理はスキップされる。
各 Plug は標準の Plug ビヘイビア(init/1 と call/2)を実装する必要がある。