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.2.0"} を含む
 .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.2.0"}
  ]
end
mix 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
end

3. 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
end

5. レイアウトの追加(オプション)

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 ファイルを保存するたびに:

  1. 変更を検出(ポーリング間隔: 500ms)
  2. 自動でリコンパイル(.css 変更は @external_resource 経由で対応する .ex も再コンパイル)
  3. 既存の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.exDialup.App.Page/
app/layout.exDialup.App.Layout全ページ
app/about/page.exDialup.App.About.Page/about
app/users/[id]/page.exDialup.App.Users.Id.Page/users/:id
app/users/layout.exDialup.App.Users.Layout/users/*

基本的な動作確認

ボタンをクリックしてカウントが増えれば成功。ページ遷移はSPAとして動作し、WebSocket経由でサーバーと通信しています。

Plug ミドルウェア

use Dialupplugs: オプションを指定することで、HTTP リクエスト処理のパイプラインにカスタム Plug を追加できる。認証、CORS、ロギングなどに使用する。

defmodule MyApp do
  use Application

  use Dialup,
    app_dir: __DIR__ <> "/app",
    title: "My App",
    plugs: [
      MyApp.AuthPlug,
      {Corsica, origins: "*"}
    ]

  # ...
end

Plug は指定した順序で実行される。Plug が conn を halt した場合、後続の Plug と Dialup の処理はスキップされる。

各 Plug は標準の Plug ビヘイビア(init/1call/2)を実装する必要がある。

認証(オプション)

ユーザー認証が必要なアプリでは mix dialup.gen.auth で Ecto ベースの scaffold を生成し、 auth_accountsDialup.Auth.Plug を Application に配線する。

mix dialup.gen.auth Accounts User users
use Dialup,
  app_dir: __DIR__ <> "/app",
  auth_accounts: MyApp.Accounts,
  plugs: [{Dialup.Auth.Plug, accounts: MyApp.Accounts}]

HTTP ログイン・MCP 権限・CSRF・セッション同期の詳細は Authentication を参照。

次のステップ