前回の記事、SAFE Templateで生成したプロジェクト(クライアント側)でクライアント側のソースを俯瞰しましたが、記事の最後の方で書いた通り、SAFE Templateはとてもシンプルな構成になっています。

ルーティングの実装例くらいは入っていてもいいのではないか?というissueが挙がっていますが、ミニマルな状態をキープしたいようです。

さて、今回はルーティング機能を実装したいと思うのですが、SAFE Templateには必要なモジュールがありません。
必要なモジュールとは、Fable.Elmish.Browser になります。

これまでに作成したテンプレートにFable.Elmish.Browserを直接組み込んで行けば良いとは思うのですが、とりあえず動くサンプルが見たいと思って探していたら、ありました。
Fable.Elmish.Reactのテンプレートがマルチページのサンプルになっていました。

これまでに作成したテンプレートから一度離れて、新しいサンプルプロジェクトを作成し、そちらからSAFE Templateの方に組み込んで行きたいと思います。

Fable.Template.Elmish.React テンプレートの導入とプロジェクト作成

Fable.Elmish.ReactのテンプレートもSAFE Templateと同様、dotnet new コマンドでプロジェクトを作成できます。
まずは、テンプレートのインストールです。

dotnet new -i Fable.Template.Elmish.React

インストールが完了したらプロジェクトを作成します。適当なディレクトリを作成して、そこをカレントディレクトリにして以下のコマンドを実行します。

dotnet new fable-elmish-react -n Sample1

Sample1の部分はプロジェクト名となりますのでご自由にどうぞ。
実行するとカレントディレクトリにSample1というディレクトリが作成されます。

作成されたディレクトリをカレントディレクトリとしてVSCodeで開いてみましょう。

README.mdというMarkdownのファイルがあるので中を見てみると、Building and running the appという個所にビルド手順が書いてあります。

  1. npm install
  2. srcディレクトリに移動
  3. dotnet restore
  4. dotnet fable npm-start
  5. ブラウザで [http://localhost:8080/] にアクセス

実行すると、以下のような画面が表示されます。

作成されたテンプレートの画面

マルチページの実装

テンプレートの画面は、左側に機能別ページへ遷移するためのメニュー領域が有り、右側に選択した機能のコンテンツ表示領域が有ります。
メニューはHome, Counter sample, Aboutの3つが有り、クリックするとそれぞれのページが右側に表示されます。
また、ブラウザのアドレスバーに "http://localhost:8080/#counter" などと直接入力しても該当のコンテンツが表示されます。

とりあえず、ページをもう一つ追加することで探ってみようかなと思っていたら、もうほぼそのままの内容の記事を見つけてしまいました(英語ですが)。

Adding a new page to Fable Elmish, Part 1 - Viquoc Quan's Blog
Adding a new page to Fable Elmish, Part 2 - Viquoc Quan's Blog
Adding a new page to Fable Elmish, Part 3 - Viquoc Quan's Blog

Part 1ではテンプレートのソース概要、Part 2で実際にページを追加してみる、Part 3で応用としてじゃんけんゲームを行うページを追加しています。

私は決して英語が得意なほうではないので流し読みした程度の理解度ですが、随所に登場するソースコードはなんとなく読めるので、手元のテンプレートにじゃんけんゲームのページを追加してみました。もちろん文言は日本語に変えましたyo!

それほど詰まらず実装は出来たのですが、ページを一つ追加するのに、変更しなきゃいけない部分が点在 していると感じました。
また、アプリケーション全体のModelに各ページ毎のModelを保持しているのも気になります。

SAFE Template へ適用

ここで前回までに作成したSAFE Templateに適用しようと思ったのですが、なんと、Fableのバージョン2が正式リリースとなり、SAFE Templateもこれに合わせて変更されたので、新しく作成しなおしました。作成手順は変わっていないので以前の記事の通り、dotnet new -i SAFE.Templateでテンプレートをアップデートしてからdotnet new SAFE ... です。

Fable.Elmish.Browserの追加は以下のコマンドをプロジェクトルートディレクトリで実行します。

.paket\paket.exe add nuget Fable.Elmish.Browser --version "~> 2" --group Client

実行すると、paket.dependenciesというファイルの group Client以下にFable.Elmish.Browserが追加されます。
元のpaket.dependenciesは綺麗にインデントされているのですが、このコマンドで追加された行はインデントがありません。 どうなんだろ...?
ちなみに、以前のpaket.dependenciesはバージョン制約が記載されていませんでしたが、今回のFableアップデートで制限するようにしたようですね。

さらに、src\Client\paket.referencesに Fable.Elmish.Browserを追加します。ここ、自動化できるのか不明です。paketの使い方がまだよくわかっていないです...。
paketのリファレンスを見ると自動で追加されるように書いてある気がするんですけど。

group Client
    FSharp.Core
    Fable.Elmish
    Fable.Elmish.Browser
    Fable.Elmish.Debugger
    Fable.Elmish.React
    Fable.Elmish.HMR
    Fable.Core
    dotnet-fable
    Fulma
    Fable.Remoting.Client

ここまで出来たら、後はElmishテンプレートのソースから移植するのですが、ひとつひとつ細かく説明するととても長くなるので、最終形のソースはGitHubのリポジトリを参照していただくとして、要点だけを記載します。

  • ひとつのソースになっているClient.fsをElmishテンプレートに倣い、view関数(View.fs)、init関数とupdate関数(State.fs)、ModelとMessage(Types.fs)に分割
  • 分散していたページURLとURLパーサーをひとつのモジュールに集めた(Pages.fs)
  • pagesディレクトリ内はElmishテンプレートからほぼそのまま追加。viewだけ、Fulmaで書き直し(ElmishテンプレートはBootstrapなので)
  • テンプレートのsrc/Client/public/index.htmlとadmin.cssを若干修正(Webフォントの読み込みを止めた)

日本語入力の対応

Client.fsを分割した結果、Client.fsは純粋にエントリポイントとしてのコードのみにしたのですが、日本語環境での使用で難があるので修正しています。

module Client

open Elmish
open Elmish.Browser.Navigation
open Elmish.React
open App.View
open App.State

#if DEBUG
open Elmish.Debug
open Elmish.HMR
#endif

Program.mkProgram init update view
|> Program.toNavigable Pages.urlParser urlUpdate
#if DEBUG
|> Program.withConsoleTrace
|> Program.withHMR
#endif
// |> Program.withReact "elmish-app"    // 日本語入力が出来ない。以下を使用する[https://github.com/elmish/react/issues/12]
|> Program.withReactUnoptimized "elmish-app"
#if DEBUG
|> Program.withDebugger // Chromeに Redux DevTools extension[https://github.com/zalmoxisus/redux-devtools-extension]を導入すること https://github.com/elmish/templates/issues/36 
#endif
|> Program.run

詳しくはコード中のコメントに記載している通りなのですが、

// |> Program.withReact "elmish-app"    // 日本語入力が出来ない。以下を使用する[https://github.com/elmish/react/issues/12]
|> Program.withReactUnoptimized "elmish-app"

この部分はIME文化の我々にとっては必須なのではないかと思います。


ここまでのコードのメカニズムを説明すると、他の方の記事の日本語訳になってしまうと思うので、この後追加していくマスタ保守画面で改めて解説したいと思います。

SAFE.Template(というか、Fable周辺)はまだまだ進化していくようで、今回のように記事を書いてる途中で色々変更が入ったりして苦労してますが、気長にお付き合いいただければ幸いです。

次回からマスタ保守画面を追加してコード解説も少しずつ進めていきたいと思います。
Server側もDBアクセスが必要なのですが、Dapperを使って実装する予定です。