elm-spa V6を試す
挙動を確かめるためにまっさらな状態からプロジェクトを始める
$ mkdir elm-spa-example
$ cd elm-spa-example
$ npm init
// エンター連打
$ npm i -D elm-spa@latest
こんな感じの状態
cat package.json
{
"name": "elm-spa-example",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"elm-spa": "^6.0.4"
}
}
プロジェクトを始めるときは、以下のコマンドを叩く
$ npx elm-spa new
// yes
主に以下が生えた
- public/index.html
- src/Pages/Home_.elm
- elm.json
index.htmtlは、Elmのファイルがコンパイルされたt結果の、elm.jsっとElm.Main.init
を読み込んでる
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<script src="/dist/elm.js"></script>
<script> Elm.Main.init() </script>
</body>
</html>
src/Pages/Home_.elm は、一旦割愛
elm.json, elm-spaがライブラリとして入った 他、.elm-spaがsource-directoriesに入ったが、そのファイルは現時点では出来ていない
{
"type": "application",
"source-directories": [
"src",
".elm-spa/defaults",
".elm-spa/generated"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/html": "1.0.0",
"elm/json": "1.1.3",
"elm/url": "1.0.0",
"ryannhg/elm-spa": "6.0.0"
},
"indirect": {
"elm/time": "1.0.0",
"elm/virtual-dom": "1.0.2"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}
以下のコマンドでサーバを立ち上げると
$ npx elm-spa server
// http://localhost:1234/
.elm-spaディレクトリが出来上がる。プリロードなコアライブラリと言ったところ
ちなみに、elm-spa gen
でも出来上がる
./elm-spa/defaults/Main.elm
の中を見てみる
main関数は、当たり前だけど、SPAの普段の中身と一緒。
Flagsは、defaults/Shared.elm
に切り出されている。
main : Program Shared.Flags Model Msg
main =
Browser.application
{ init = init
, update = update
, view = view
, subscriptions = subscriptions
, onUrlChange = ChangedUrl
, onUrlRequest = ClickedLink
}
Modelは、Pages.Model ./elm-spa/generated/Gen/Pages.elm
さらには、 ./elm-spa/generated/Gen/Model.elm
に切り出されている
type alias Model =
{ url : Url
, key : Key
, shared : Shared.Model
, page : Pages.Model
}
生成されたファイルで、各ページごとのParamsがカスタムタイプで定義されている
type Model
= Redirecting_
| Home_ Gen.Params.Home_.Params
| NotFound Gen.Params.NotFound.Params
initはSharedと各ページごとにinitを呼び出してマージしている。
v6からの新しい概念で各ページのCmdはeffectというもので抽象化されて、MainではじめてCmdに変換される。
init : Shared.Flags -> Url -> Key -> ( Model, Cmd Msg )
init flags url key =
let
( shared, sharedCmd ) =
Shared.init (Request.create () url key) flags
( page, effect ) =
Pages.init (Route.fromUrl url) shared url key
in
( Model url key shared page
, Cmd.batch
[ Cmd.map Shared sharedCmd
, Effect.toCmd ( Shared, Page ) effect
]
)
Msgはいつものセット + Shared + PageごとのMsg
type Msg
= ChangedUrl Url
| ClickedLink Browser.UrlRequest
| Shared Shared.Msg
| Page Pages.Msg
updateもいつものセット + Shared + Pageごとのupdate呼び出し。effectからCmdに変換さている
...
Shared sharedMsg ->
let
( shared, sharedCmd ) =
Shared.update (Request.create () model.url model.key) sharedMsg model.shared
( page, effect ) =
Pages.init (Route.fromUrl model.url) shared model.url model.key
in
if page == Gen.Model.Redirecting_ then
( { model | shared = shared, page = page }
, Cmd.batch
[ Cmd.map Shared sharedCmd
, Effect.toCmd ( Shared, Page ) effect
]
)
else
( { model | shared = shared }
, Cmd.map Shared sharedCmd
)
Page pageMsg ->
let
( page, effect ) =
Pages.update pageMsg model.page model.shared model.url model.key
in
( { model | page = page }
, Effect.toCmd ( Shared, Page ) effect
)
viewはBrowser.Documentを抽象化した型で扱われていて、各ページのViewをView.toBrowserDocumentで最後型合わせしている感じ
view : Model -> Browser.Document Msg
view model =
Pages.view model.page model.shared model.url model.key
|> View.map Page
|> View.toBrowserDocument
type alias View msg =
{ title : String
, body : List (Html msg)
}
subscriptionも特に変なところなし。
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ Pages.subscriptions model.page model.shared model.url model.key |> Sub.map Page
, Shared.subscr