エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

Phoenix LiveView で Markdwon Preview

Phoenix LiveView を使って、textarea に入力した Markdown のテキストを逐次プレビューするサンプルです。

f:id:E_Mattsan:20200626201515p:plain

Phoenix 1.5 になって簡単に LiveView を利用できるようになり、動的なページを作るのが本当に簡単になりました。

もちろん万能ではないですし弱点も少なくありません。 それでも従来の web ページを構築する要領で動的なページを構築できるのは大きな利点だと思います。

Earmark - a pure-Elixir Markdown converter

Markdown から HTML への変換にここでは Earmark を利用します。

Earmark は ex_doc がドキュメントを生成するときにも利用されているパッケージです。

as_html!/2で簡単に変換することができます。

iex(1)> Earmark.as_html!("""
...(1)> # 第一部            
...(1)> ## 第一章           
...(1)> ### 第一節          
...(1)> 
...(1)> | a | b |
...(1)> |---|---|
...(1)> | 1 | 2 |
...(1)> 
...(1)> - a
...(1)> - b
...(1)> """) |> IO.puts()
<h1>
  第一部
</h1>
<h2>
  第一章
</h2>
<h3>
  第一節
</h3>
<table>
  <thead>
    <tr>
      <th style="text-align: left;">
        a
      </th>
      <th style="text-align: left;">
        b
      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left;">
        1
      </td>
      <td style="text-align: left;">
        2
      </td>
    </tr>
  </tbody>
</table>
<ul>
  <li>
    a
  </li>
  <li>
    b
  </li>
</ul>

MdPreviw - Markdownプレビュー app

アプリケーションを作成していきます。

Phoenix プロジェクトを用意する

LIveView を利用するプロジェクトを新たに作成します。 ここでは作業を簡単にするためデータベースを利用せず --no-ecto を指定します。

$ mix phx.new md_preview --live --no-ecto
$ cd md_preview

パッケージを追加する

mix.exs を編集して、依存パッケージに Earmark を追加します。

  defp deps do
    [
      {:phoenix, "~> 1.5.3"},
      {:phoenix_live_view, "~> 0.13.0"},
      {:floki, ">= 0.0.0", only: :test},
      {:phoenix_html, "~> 2.11"},
      {:phoenix_live_reload, "~> 1.2", only: :dev},
      {:phoenix_live_dashboard, "~> 0.2.0"},
      {:telemetry_metrics, "~> 0.4"},
      {:telemetry_poller, "~> 0.4"},
      {:gettext, "~> 0.11"},
      {:jason, "~> 1.0"},
      {:plug_cowboy, "~> 2.0"},
      {:earmark, "~> 1.4"} # 追加
    ]
  end

パッケージを取得します。

$ mix deps.get

ルーティングを追加する

live/md_preview_web/router.ex を編集して LiveView のルーティングを追加します。

  scope "/", MdPreviewWeb do
    pipe_through :browser


    live "/", PageLive, :index
    live "/markdown", MarkdownLive # 追加
  end

LiveView を書く

LiveView を書いていきます。

…とは言っても LiveView のドキュメントにある実装例ぐらい簡単なコードです。

textarea の内容の更新に逐一反応しないように phx-debounce="1000" を指定しています。 更新が止まってから 1,000 ミリ秒 = 1 秒経過してからイベントをサーバに送ります。

またレンダリング済みの HTML をエスケープせずに挿入するために Phoenix.HTML.raw/1 を利用しています。

defmodule MdPreviewWeb.MarkdownLive do
  use MdPreviewWeb, :live_view

  @impl true
  def mount(_, _, socket) do
    {:ok, assign(socket, body: "")}
  end

  @impl true
  def render(assigns) do
    ~L"""
      <div>
        <form phx-change="update">
        <textarea name="markdown[source]" class="source" phx-debounce="1000"></textarea>
        </form>
      </div>
      <div class="preview">
        <%= raw @body %>
      </div>
    """
  end

  @impl true
  def handle_event("update", %{"markdown" => %{"source" => source}}, socket) do
    body = Earmark.as_html!(source)
    {:noreply, assign(socket, body: body)}
  end
end

assets/css/app.scss にスタイルを追加して見た目を少し整えました。

.source {
  height: 20vh;
  resize: none;
  font-family: monospace;
}

.preview {
  border: solid thin #e0e0e0;
  padding: 10px;
}

app を動かす

サーバを起動し http://localhost:4000/markdown にアクセスすると、記事の先頭の画像のように textarea に入力した Markdown のテキストがすぐに変換されて表示されます。

$ iex -S mix phx.server 

レンダリングを非同期にする

イベント送信の負荷を減らすために、また編集途中の半端な状態でレンダリングされないように、入力が止まってから 1 秒ごにテキストをサーバに送信するようにしています。 つまりテキストの入力とレンダリング結果の表示のタイミングは同期していません。

このような関係にあるばあい、ブラウザからのテキストの送信とサーバからの結果の送信は分離してしまっても問題ありません。

MdPreviewWeb.MarkdownLiveイベントハンドラを次のように編集します。

"update" を受けたらすぐに自分に対して :render メッセージを送り、LiveView のイベントハンドラから抜けます。

その後 :render メッセージを受けとたら Markdown から HTML に変換して表示内容を更新します。

  @impl true
  def handle_event("update", %{"markdown" => %{"source" => source}}, socket) do
    send(self(), {:render, source})

    {:noreply, socket}
  end

  @impl true
  def handle_info({:render, source}, socket) do
    {:noreply, assign(socket, body: Earmark.as_html!(source))}
  end

このテクニックは The Pragmatic Studio の Phoenix LiveView コースで解説されています。 2020年6月27日現在 $0 で提供されています。 興味のある方はぜひ受講してみてください。

online.pragmaticstudio.com

いつか読むはずっと読まない:Real-Time Phoenix

The Pragmatic Bookshelf の「Real-Time Phoenix」。今年の3月3日に正式に出版され「Hands-On with Phoenix LiveView」で LiveView に触れられています。

pragprog.com