🌐

elm-spaを使って簡単SPA生活

2020/10/17に公開

elm-spaは、elmで難度が高めかつ冗長になりがちなSPAコードの大部分を自動生成してイージーにしてくれる補助ツールになります。最初一度きりの生成ではなくSPAを拡張する都度自動生成をすることができるので、テンプレートコードを毎度怠く書き殴らなくて済むのでとても便利だと感じました。一方でどう言ったコードが生成されるのか、いつ生成されるのかなどを把握していなければ混乱してしまう部分もあるため記事の執筆に至りました。

elm-spaのはじめ方

グローバル環境を汚さずに生成してみます。あまり気にしない方は、elm-spaをグローバルインストールしてしまっても良いと思います。

$ npm init // Enter連打です。
$ npm i elm-spa
$ npx elm-spa init

どんな形式でElmのコードを生成するかスタイルの選択があります。オーソドックスなhtml、レイアウト等をElmで抽象化しているelm-ui、CSSを型安全に書けるelm-css等があります。今回は、htmlを選択して解説をします。選択するとプロジェクト名を決めてディレクトリに移動して開発開始です。

兎にも角にも起動をしてみましょう。サーバが起動したら、localhost:8000でアクセスすることが可能です。中身については、あとで紹介することにします。

$ npm start

生成されたコードを見てみる

それではelm-spaが生成したコード達を見ていきましょう!生成されたコードには、初回生成したコードとサーバ起動時(npm start)に生成する2種類のコードがあるため分けて解説をしていきます。

初回生成コード

最初に生成されるコードはこちらになります。

publicは、見ての通りElmを起動するためのhtmlやjs, cssなどのアセットを置く場所となっております。portsの整備をしたり、scssや他のpackageを利用するときはここを書き換えていく必要があるため重要なディレクトリですね。

testsは空っぽですが、最近のElmはelm-testやelm-analyseなどの開発補助ツールがセットになっているので、テストコードを作ればそのままnpm testでテストが可能になります。

package.jsonの中身を見てみましょう。
基本的には、start, test(test:watch), buildを覚えておけば良さそうです。startでは、npm install, npm run build:dev, npm run devを実行していますが、つまりはパッケージのインストール, SPAのコードを自動生成, 開発サーバの起動を同時にしているようです。開発サーバにはelm-liveを利用していることがわかります。sassの利用やminify, 難読化等を導入する場合はこれらのコマンドをカスタマイズすると良いでしょう。

 "scripts": {
    "start": "npm install && npm run build:dev && npm run dev",
    "test": "elm-test",
    "test:watch": "elm-test --watch",
    "build": "run-s build:elm-spa build:elm",
    "build:dev": "run-s build:elm-spa build:dev:elm",
    "dev": "run-p dev:elm-spa dev:elm",
    "build:elm": "elm make src/Main.elm --optimize --output=public/dist/elm.compiled.js",
    "build:dev:elm": "elm make src/Main.elm --debug --output=public/dist/elm.compiled.js || true",
    "build:elm-spa": "elm-spa build .",
    "dev:elm": "elm-live src/Main.elm -u -d public -- --debug --output=public/dist/elm.compiled.js",
    "dev:elm-spa": "chokidar src/Pages -c \"elm-spa build .\""
  }

生成されたElmコード

周辺コードの説明があらかた終わったので、本番のElmコードを解説していきましょう。

たくさんコードがありますが、ものすごいざっくり言うと以下がSPAを支えているコードたちです。基本的には触れなくて良いコード群ですが、SPAの仕組みをカスタマイズしようとする場合は理解した上で書き換える必要があリマス。この記事では解説は割愛させていただきますので、興味がある方はコードを眺めてみてください。

  • Main.elm
  • Spa
    • Document.elm
    • Page.elm
    • Url.elm

それでは開発者がメインで触っていくべきコードがこちらです。

  • Shared.elm
  • Pages
    • NotFound.elm
    • Top.elm

このうち特殊な裏のMain.elmと言うべきものがShared.elmです。これはサイト上でどのページにも描画されるヘッダー部分ようなViewを持てます。それ以外にも起動時にJSから環境変数等の値を受け取ることが出来るFlagsやアプリケーションページ(後述します)が共有できるグローバルなModel等を持つことができます。基本的にSharedなので他にはグローバルなsubscriptionsなども持っています。

type alias Flags =
    ()


type alias Model =
    { url : Url
    , key : Key
    }


init : Flags -> Url -> Key -> ( Model, Cmd Msg )
init flags url key =
    ( Model url key
    , Cmd.none
    )


subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.none

npm startにより生成されるコード

実は今まで見てきたコードだけでは実行することはできません。なぜかと言うと、各ページを跨ぐためのルーティングやSharedと各ページを繋ぎ止めるためのコードがありません。なぜかと言うとPages以下に開発者が自由にコードを追加できますが、それらのルーティング等を自動生成する必要があるため、その生成タイミングを遅延させています。そのためnpm startを実行するとルーティング等の生成が始まります。

  • Generated
    • Pages.elm
    • Routes.elm

これらの生成されたコードがキチンと使われているかどうかは、Main.elmを覗くとわかります。

import Spa.Generated.Pages as Pages
import Spa.Generated.Route as Route exposing (Route)

type alias Model =
    { shared : Shared.Model
    , page : Pages.Model
    }
    
    
fromUrl : Url -> Route
fromUrl =
    Route.fromUrl >> Maybe.withDefault Route.NotFound

では、新しいルーティングを追加してみましょう。以下のように新しいページを作るとPages以下にファイルが生成されます。

 $ npx elm-spa add
✔ What kind of page? › application
✔ What's the module name? … MyPage

✔ Added a new application page at:
/Users/abab/Desktop/elm-spa-example/my-elm-spa/src/Pages/MyPage.elm

その後、npm startすると、ルーティングにページが追加されていることがわかります。パスが気に食わなければ変更することも可能なようです。

type Route
    = Top
    | MyPage
    | NotFound
    
    
routes : Parser (Route -> a) a
routes =
    Parser.oneOf
        [ Parser.map Top Parser.top
        , Parser.map MyPage (Parser.s "my-page")
        , Parser.map NotFound (Parser.s "not-found")
        ]

また、パラメータなどをルーティングに含めたい場合の半自動生成の方法もドキュメントに記述されています。ちょっとトリッキーなのでご注意を。

elm-spaにページを追加する

最後に、elm-spa add時にページ生成の種類を選択する場面が出てきます。これらの違いを載せておきます。

まずStaticは状態を持たずHtmlのみで構成されるページです。例えばヘルプページやAboutページ等が該当しそうです。

SandboxとElementの主な違いは一般的なElmのSandboxとElementと同じで、副作用を持つか持たないかの違いなのですが、elm-spaにおけるこの二つの共通点は、個別のページに閉じていると言う点です。他のElmプロジェクトで作ったソースコードをそのまま移植するなどが簡単に行えそうです。


そしてSPAのためのページ生成方法がApplicationになります。こちらを利用することで、Sharedのグローバルステートから値を読み取ったり(init, load)、永続化(save)を行うことができます。よくあるシチュエーションとしてはサインインページやログイン後のユーザ情報を利用した決済ページなど様々なページに利用されることが想定されます。具体的なデータの管理方法や繋ぎこみ部分を意識する必要がないのがelm-spaの最大の特徴となります。是非ご活用ください。

Discussion