複数ページのSPAをElmで書くときのボイラープレート部分をalchelmyで自動的に†錬成†する

![Alchemik_Sedziwoj_Matejko.JPG](https://qiita-image-store.s3.amazonaws.com/0/64695/a5a6c3ca-a2fe-1ba8-28a3-733156f5ae92.jpeg) 

Elmのようなフレームワークに接すると、そのうちウェブサイト全体をElmで書き直したいという衝動に駆られる人も多いと思います。でも、The Elm Architectureで複数ページからなるシングルページアプリケーション[^spa]を作ろうとすると、ルーティングでも`update`でも`view`でも、とにかく**大量の分岐を書かされる**の地味につらいですよね。ひとつページを追加するだけでも、複数のファイルに散らばったコードのあちこちをちょっとづつ修正しなくてはならなくて、とにかく面倒です[^1]。

ElmでSPAを実現する仕組みについては、今回のアドベントカレンダーで[kawausoさんがちょうど記事にしてくれている](https://qiita.com/kawauso7/items/daed519ec3a1d3e3555d)ので、そちらもご覧になるといいと思います。The Elm ArchitectureでSPAを実現するためには、この「ページを切り替える仕組み」のパターンを覚えなくてはならず、これを理解するまでは少々大変です。慣れてしまえば、めちゃくちゃ難しいというほどではないのですが、出来ることならこんなことに頭を悩ませたくはありません。

PHPなんかだと、適当なディレクトリに適当に空の`*.php`なファイルを作れば、それだけでページを追加できるので、静的なHTMLと同じくらいわかりやすいですしお手軽です。ページを消すにもその`*.php`を削除すればいいし、パスを移動したければその`*.php`を別のディレクトリへ移動すればいいでしょう。もちろんサーバサイドの技術とクライアントサイドの技術という根本的な違いはありますが、Elmでもそれと同じくらい気軽にページを追加したいものです。

[^spa]: ところでこういう、シングルページアプリケーションなのに、History APIでURLを変えながら仮想的な複数のページを切り替えられるアプリケーションて、なんて呼べばいいんでしょうか。

[^1]: このあたりのつらさがピンとこない人のために、つらみをクドクドと説明しようとしたのですが、あまりにクドくなってしまったので消しました。代わりに[rtfeldman/elm-spa-exampleにある大量の規則的なコンストラクタや条件分岐](https://github.com/rtfeldman/elm-spa-example/blob/master/src/Main.elm)をご覧いただければと思います。ひとつページを増やすたびに、これらの分岐に枝をひとつづつ足していくわけです。基本的には難しくはないですがとにかく面倒くさいですし、`update`はとくに`( _, _ ) ->`でパターンマッチングをしており、枝を足さなくてもコンパイルが通ってしまうので、問題は深刻です。

そこでalchelmy[^2]という**闇のツール**を使うと、多少の柔軟性を対価にして、The Elm Architectureの分岐の部分のボイラープレートを自動的に錬成してくれます。アプリケーションに新たなページを追加するときには、そのページに対応するソースファイルをひとつ追加するだけで済み、既存のコードを手作業でいじる必要が事実上なくなります。もちろん、あくまでシングルページアプリケーションとしてです。kawausoさんが解説してくれたいろんなアレの大半は、知らなくても済むのです。すごい！　まあ作ったのは私なんですけども。リポジトリはこちらです。

[^2]: 名前はalchemy + elmでつけました。読みかたはたぶん『アルケルミー』だと思います。elchemy という名前も考えたのですが、その名前の Elm のツールがすでに存在していたので止めました。


* https://github.com/aratama/alchelmy

## Alchelmyのしくみ

Alchemlyの大まかな仕組みを簡単に紹介しておきます。Elmのシングルページアプリケーションでは、通常は[Browser.application](https://package.elm-lang.org/packages/elm/browser/latest/Browser#application)関数に以下のような型のオブジェクトを与えてアプリケーションを定義します（Elmの初期化については、 [jinjorさんの2日目の記事](https://qiita.com/jinjor/items/245959d2da710eda18fa#browserapplication)も参考にどうぞ）。

```elm  
    { init : flags -> Url -> Key -> ( model, Cmd msg )
    , view : model -> Document msg
    , update : msg -> model -> ( model, Cmd msg )
    , subscriptions : model -> Sub msg
    , onUrlRequest : UrlRequest -> msg
    , onUrlChange : Url -> msg
    }
```

対してAlchelmyでは、それとは少し型が異なる次のようなオブジェクトを定義したモジュールをページごとに用意することで、ページを定義します。

```elm  
    { init : Flags -> Url -> Key -> route -> Maybe Session -> ( model, Cmd msg )
    , view : model -> Document msg
    , update : msg -> model -> ( model, Cmd msg )
    , subscriptions : model -> Sub msg
    , onUrlRequest : UrlRequest -> msg
    , route : Parser (route -> a) a
    , session : model -> Session
    }
```

`view`や`update`、`sucscriptions`、`onUrlRequest`はまったく同じですが、`init`には引数が追加されているほか、`route`と`session`というプロパティが追加され、それから`onUrlChange`が削除されています。

`route`はこのページへのパスをパースする`Parser`を指定するものです。Alchelmyでは、URLのパーサは一箇所にまとめて書くのではなく、各ページのモジュールにそれぞれ分けて定義します。パスをパースして成功すると、得られた結果の`route`はが`init`の引数として渡されます。

また、ページを遷移したときに、任意に定義できる`Session`型のデータをページ間で持ち越すことができるようになっています。各ページで共通して参照できるグローバルな状態のようなものです。`pushUrl`でURLが変化したときに、直前のページの`model`から上の`session : model -> Session`関数を使って`Session`型を取り出し、それが次のページの`init`の引数に渡される仕組みです。アプリケーションが読み込まれた最初の`init`では`Nothing`になります。

そして、各ページに対応するこのようなモジュール複数定義されるわけですが、Alchelmyはこのような複数のモジュールをそのパスに従って自動的に配置して切り替えるようなコードを生成し、`Alchelmy.elm`という名前のモジュールとして定義します。ちなみに、`Alchelmy.elm`にはどんなコードが生成されているのかは、[example/src/Alchelmy.elm](https://github.com/aratama/alchelmy/blob/master/example/src/Alchelmy.elm)を見ていただくと一目瞭然だと思います[^gitignore]。

[^gitignore]: `Alchelmy.elm`は自動的に生成されるファイルなので`.gitignore`に書いてしまってもいいのですが、サンプルとして見せるためにあえてリポジトリに残してあります。



## Alchelmyのサンプルアプリケーション

![elmexamples.png](https://qiita-image-store.s3.amazonaws.com/0/64695/aea22719-7440-114d-f09c-bba4002f4ea8.png)

ドキュメントはまだあまり書かれていませんので、Alchelmyの仕組みについては、以下のサンプルコードを見ていただくのが一番手っ取り早いかと思います。このサンプル自体は何の変哲もない簡単なポートフォリオみたいなアプリケーションで、[昔のElmの公式サンプル](https://web.archive.org/web/20181101092610/http://elm-lang.org/examples)から、カウンタや、AJAXで特定のテーマのGIFを取ってくるサンプル、SVGで描かれた時計などが移植されています[^old-sample]。このサンプルアプリケーション自体は特に面白いところはありませんが、ビルドは netlify に置いてありますので、以下で実際に試すことができます。ちなみに、各ページは背景部分の色が異なっているのですが、リンクをクリックしてページを遷移すると、CSSのトランジションを使って滑らかに色が変化することがわかると思います。これもシングルページアプリケーションを採用するメリットのひとつですね。

* https://alchelmy.netlify.com/

サンプルのコードはこちらで閲覧できます。

* https://github.com/aratama/alchelmy/tree/master/example


[^old-sample]: そういえばこのElmのサンプル群 ( https://elm-lang.org/examples )は、私がこの Alchelmy を作り始めたときにはまだあったのですが、今はなくなっちゃいましたね。言語仕様がだいぶ変わってきたので、学習のためのリソースを全面的に見なおしているようです。



## さいごに



実際に大規模なElmのシングルページアプリケーションを運用しているサービスではどうしているのかと思い、いろいろ調べてみたのですが、たとえばElmのオリジナル作者Evanさんや、前述のFeldmanさんも所属しているNoRedInkでは、[もともとRubyで作られたサービスをElmに順次置き換えつつあるという段階](https://dev.to/rtfeldman/comment/21e)のようで、Elmでこの手の大きなサービス全体をSPAとして作る方法というのは、私が知る限りでは確立されているとまではまだ言えないようです。

あまたのメタプログラミングの中でも、**コード生成は特に邪悪な黒魔術**のひとつだと筆者は思うのですが、今回ばかりは他に現実的な方法が思いつきませんでした。それでできたツールがコレです。あらゆるユースケースに対応できるわけではないと思いますし、この方法でどこまでうまくいくかは今後もよく検証していく必要があります。かなり前に[一晩でヤケクソで作ったやつ](https://twitter.com/cubbit2/status/1018900387045113856)が元になっていて、最初JavaScriptで書きなぐったやつをPureScriptに移植したりしたりして、今もいろいろ試行錯誤が続いています。

それにまだ課題もいろいろ残っています。例えば複数のページで共通の振る舞いをどう書くかは難しいところで、初期のバージョンでは共通部分と個別の部分が親子のコンポーネントのようになっていて、親子間でメッセージを送り合うみたいな仕組みをいったん実装したときもあったのですが、あまりに複雑なうえ、例外的なケースがあると逆に面倒くさくなるので、その種の機能はあえて削りました。上のサンプルアプリケーションでは404のページなどを除いて各ページが３ペイン式の共通のレイアウトになっていますが、各ページで共通する部分をどのように書くかの検討にもなっています。


昨日の記事はGoryudyumaさんの『[ElmとJSONの話](https://qiita.com/Goryudyuma/items/e4c558bd309bc9c4de52)』でした。静的な言語で永続的なデータを扱う方法というのは、永遠のテーマなのかもしれません。明日の記事はA_kirisakiさんです。



# 参照と参考文献

* [Alchemist Sendivogius (1566–1636) by Jan Matejko, 1867](https://commons.wikimedia.org/wiki/File:Alchemik_Sedziwoj_Matejko.JPG) アイキャッチ画像の錬金術士
* [Can you explain the structure of the main NoRedInk Elm app?](https://dev.to/joshhornby/comment/1ip)
* [Modifying “parent state” from child page in an elm-spa-example-like architecture](https://discourse.elm-lang.org/t/modifying-parent-state-from-child-page-in-an-elm-spa-example-like-architecture/2437/16)

* [Elm-spa: a tool for building single page apps](https://discourse.elm-lang.org/t/elm-spa-a-tool-for-building-single-page-apps/4597) ( [GitHub](https://github.com/ryannhg/elm-spa) ) Alchelmyと似たような目的のツール？まだよく調べてないです
* [Introducing elm-pages 🚀 - a type-centric static site generator](https://elm-pages.com/blog/introducing-elm-pages/) ( [GitHub](https://github.com/dillonkearns/elm-pages) ) Elmで静的サイトを書くためのパッケージ。SPAを構成するという意味では近いものがありそうです