elm-spa v5 -> v6 に移行ガイド
elm-spaは、ElmでSPAをするためのボイラプレートを生成してくれる、とても便利なツールです。v5からv6で変更点がいくつかあり、びっくりしてしまう部分があります。しかし、よくよく見ていくと、そこまで大きく内容が変わっているというよりも機能やモジュールがまとめられたり命名が変わっているだけだったりするので、マッピングがしっかりできていればそこまで驚くことはありません。この記事では移行の手順と大まかな変更点が伝えればなと思います。
この記事では、以下の順番で移行の手順をお伝えできればと思います。既に手元にv5のプロジェクトがある場合、いきなり書き換えるのではなく、新規プロジェクトを立ち上げて、v6の構造を把握したり、新しいPages
の仕組みを把握してから書き換えをした方がスムーズに行くように感じました。
- 設定
- elm-spaコマンド
- ソースコード
設定の変更点
elm-spa v6用の設定変更点を説明していきます。これらは、手で変更する必要はなく、elm-spa
を導入すれば、elm-spa init
コマンドで設定をしてくれます。特に新規でプロジェクトを立ち上げる場合は、そちらをご利用下さい。
package.json
elm-spaは自動生成をするためのコマンドライブラリとelmのライブラリの2つがあります。コマンドライブラリは、通常のnpmプロジェクトと同じ、package.json
にあるので、6系のライブラリにアップグレードしましょう。
"elm-spa": "5.0.4"
↓
"elm-spa": "6.0.4"
.gitignore
elm-spaはSPA用の自動生成ファイルをディレクトリにまとめて運用をします。v5では、src配下にElmのメインプロジェクトファイルとして生成されていましたが、プロジェクト直下の.elm-spa
ファイルに生成されることになりました。
src/Spa/Generated
↓
.elm-spa
elm.json
Elmプロジェクトを単に作った場合、src
ディレクトリだけがビルドの対象となります。しかし、内部ライブラリなどの用途で任意のディレクトリをビルド対象にすることができます。v6では、.elm-spa/defaults
, .elm-spa/generated
の2つのディレクトリが新たにビルドの対象となります。これは、先ほど.gitignore
に書いてあった、.elm-spaディレクトリの中身に当たります。こう書かれた場合、ビルド時には、すべてsrcディレクトリ配下にある状態だと思ってもらえれば大丈夫です。また、.elm-spa/defaults
下のファイルはsrcに移動することで上書きが可能です。(generatedも可能ですが、推奨はされていません)
また、v6からElmライブラリとしても、elm-spaが提供されるようになったので、必ずinstallするようにしましょう。
{
...
"source-directories": [
"src"
],
"dependencies": {
...
},
...
↓
{
...
"source-directories": [
"src",
".elm-spa/defaults",
".elm-spa/generated"
],
"dependencies": {
...
"ryannhg/elm-spa": "6.0.0"
},
elm-spaコマンドの変更点
v5では対話形式でルーティングファイルの種類を選択し、Pages直下にファイルを生成していましたが、v6ではコマンドを一度で打ち切る形に変更されました。しかし、ルーティングからモジュール名に脳内変換して入力をしていましたが、v6ではルーティングを直接書けば良いので、より直感的になりました。
npx elm-spa add
? What kind of page? › - Use arrow-keys. Return to submit.
static
sandbox
element
❯ application - Needs read-write access to Shared.Model
✔ What's the module name? … UserId_String.Posts.PostId_String
✔ Added a new application page at:
...[Project Path]/src/Pages/UserId_String/Posts/PostId_String.elm
↓
npx elm-spa add /:userId/posts/:postId element
New page created at:
[Project Path]/src/Pages/UserId_/Posts/PostId_.elm
ページの種類が、static
, sandbox
, element
は変わりませんが、application
は、advanced
となったのでご注意ください。また、ここで使われるテンプレートは、templates
ディレクトリ以下に用意されており、あらかじめ、どのようなコードが生成されるかわかるため、不安な方は事前にご確認ください。
tree .elm-spa/templates
.elm-spa/templates
├── advanced.elm
├── element.elm
├── sandbox.elm
└── static.elm
elm-spa add
コマンドでPagesファイルを生成した場合、Params等の自動生成モジュールはこの時点では作られないため、コンパイルエラーが発生します。npx elm-spa server
やnpx elm-spa build
などのコマンドを叩くことで生成はされますが、もっと手っ取り早く.elm-spa
ファイルを生成するには、npx elm-spa gen
コマンドを叩くと良いでしょう。
ソースコードの変更点
Pagesモジュールファイルの命名
これは、コマンドの変更点でお気づきかもしれませんが、URLの変数部分にUserId_String
のような形でStringがついていましたが、実用的にString一択のためか、Stringに統一された上で、変数名_
のように、型名は省略されるようになりました。
[Project Path]/src/Pages/UserId_String/Posts/PostId_String.elm
↓
[Project Path]/src/Pages/UserId_/Posts/PostId_.elm
自動生成やライブラリのimport
Spaモジュールが撤廃されて、elm.json
のsource-directories
に従って、importがシンプルになりました。また、Document -> View, Url -> Requestのように命名が変わり、中の機能が少しだけ変更になりました。差分については、ソースコードを見比べたり、記事一番下の関連記事などをご覧ください。
import Spa.Document exposing (Document)
import Spa.Page as Page exposing (Page)
import Spa.Url exposing (Url)
import Shared
↓
import View exposing (View)
import Page
import Request
import Shared
Document -> Viewの差分は以下のような形です。特に関数の中身には変更がありません。
view : Model -> Document Msg
view : Model -> View Msg
Request
Request
はUrlの代わりとなる型ですが、少しだけ複雑です。主にPagesファイル配下では、Request.With Params
と言う型で使われることが多いはずです。Request.With
は、以下のようなレコードのaliasです。
type alias With params =
ElmSpa.Request Route params
Paramsは、Pagesモジュールでは以下のようにimportされているはずです。
import Gen.Params.UserId_.Posts.PostId_ exposing (Params)
Paramsの中を見ると、以下のようなレコードです。ルーティングで定義した変数が取得できるようになっているわけですね。
type alias Params =
{ userId : String, postId : String }
改めて、ElmSpa.Requestの定義を見てみましょう。
Request.With Params`とは、HTTPリクエストに関わるものを一つにまとめた型であると言うことがわかりました。
{ url : Url
, key : Key
, route : route
, params : params
, query : Dict String String
}
例えば、現在/:userId/posts/:postId
と言うルーティングにいて、ブログ投稿を削除するための、/:userId/posts/:postId/delete
と言うページに遷移するようなCmd Msgをupdateで返すようなシチュエーションを考えてみましょう。すると、Request
を利用して、以下のように書くことができます。Gen.Routeなどは自動生成されたもので、補完が効くのでご安心ください。
update : Request.With Params -> Msg -> Model -> ( Model, Cmd Msg )
update req msg model =
...
GoDeletePost ->
( model,
Request.pushRoute
(Gen.Route.UserId___Posts___PostId___Delete
{ userId = params.userId, postId = params.postId }
)
req
)
Ports
v6からPagesモジュールはPorts Moduleとなることが禁止になりました。そのため、もしport関数を定義したい場合には、別モジュールで定義をしてそれをimportするような形にすることでこの問題を回避することができます。Pagesと対応したいため、私は以下のようPortsモジュールを作り、Pagesに対応させてimportすることにしました。
port module Pages.UserId_String.Posts.PostId_String
port hoge: ~~~
port foo: ~~~
↓
module Pages.UserId_.Posts.PostId_
import Ports.UserId_.Posts.PostId_ exposing (..)
...
module Ports.UserId_.Posts.PostId_ exposing (..)
port hoge:~~~
port foo:~~~
Shared State と ユーザ認証
こちらは、差分が多いため、一番下の関連記事をご覧ください。
ごく簡単に説明すると、application(advanced)のときのsave
, load
関数が消え、Effectモジュールとpages関数の引数にSharedとRequestが渡されるようになりました。
関連記事
elm-spa v6関連の記事を他にも書いているので、良ければご覧ください。
Discussion