2023-06-18

Fable でブログを再構築する pt.7

krymtkts/blog-fable Fable でブログを再構築している。

当初開発サーバに使ってた tapio/live-server は npm の vulnerability 報告がずっと出てた。 開発用なので構わないといえばそうだがやっぱいい気分ではないので、自力で開発サーバを立てるように変えていく(現在作業中)。 #20

F# Weekly の Sergey Tihon 氏がちょうどよい F# script を書いてるのを見つけたので、それをベースにする。 Web サーバに F# 製の SuaveIO/suave を使うようになる。

f# - Suave in watch mode (during development) - Stack Overflow

これに従うと、変更イベントで WebSocket のメッセージをページに送って、ページはメッセージを受け取ったらリロードする、という流れで live reload をすることになる。

  1. ファイルの変更検知して変更イベントを発火する
  2. 変更イベントで WebSocket のメッセージ送信する
  3. ページに差し込んだ JavaScript で WebSocket のメッセージを受け取ったら reload する

この開発サーバ用 F# スクリプトを開発するにあたり、 F# Interactive 特有の挙動とかあまりわかってなかったので、以下を参照に勉強した。 F# Interactive (dotnet) Reference | Microsoft Learn

今回ファイルの変更を監視するのと、 dotnet コマンド実行するのに FAKE のモジュールがいくつか必要だったので NuGet から取得する形にした。

FAKE はいつか勉強せなあかんなと思っていたが、タイミング良く使う機会が来た。その良さもなんとなく感じており、なんでも F# で書きたくなる気持ちがわかってくる。

先述の WebSocket でやり取りする JavaScript は、デプロイするブツには不要だ。 なので今回開発モードをこしらえて、その場合のみ WebSocket 用の JavaScript をロードする script タグを差し込むようにした。 開発モード自体は SSG 実行時のオプションで渡す形にしている。設定ファイルなしでスクリプトだけで完結する世界観にしたいので。

この開発用 JavaScript も、そのうち Fable.Browser.WebSocket で書いてトランスパイルしたものを使うようにしたい。

一通り作ってみて、いい感じに動いてるっぽい。 ただし強烈に遅いのと、 Suave で 404 ページを表示する方法がわからないので、今後の課題かな。差分ビルドとかできるとかっこよい。

以下に今回のハマりどころを記載しておく。

ハマりどころ

Suave 、 Fable 共にそうだけどドキュメントに書いてないことがチラホラあるので、分からなくてコード読む必要があった。備忘のためいくつかメモを記録しておく。

Suave

Suave で以下のエラーが出たときは、権限じゃなくてサーバの 設定が足りない。

System.UnauthorizedAccessException: Access to the path 'C:\Program Files\dotnet\sdk\7.0.302\FSharp\_temporary_compressed_files' is denied.

これを解消するには権限を与えるのではなく、 Sauve がデフォルトで使ってる圧縮ファイルのパスを config に指定しないといけない(デフォでなんかやってくれよめんどくせえ)。 suave/Web.fs at 8efe4b32ea0dc52f36c10c8d8fec8191c6ae901c · SuaveIO/suave

compressedFilesFolder = Some(home)

Fake.DotNet

Fake.DotNet の使い方は SAFE-Stack のテンプレ が参考になる。 SAFE-template/Build.fs at db892ace1ecd1589a9a03a484e388a97f3b71718 · SAFE-Stack/SAFE-template

commandargs どう分けるか謎だが、あんまきにしなくていいっぽい。 現に ↓ のように cmd に引数が混ざっていても動くという。

let cmd = "fable src"
let args = "--runScript dev" // NOTE: run script with development mode.
let result = DotNet.exec (fun x -> { x with DotNetCliPath = "dotnet" }) cmd args

Fable

Fable に --runScript をつけて呼び出したときに引数を与えられるのかについては、ドキュメントの記載は見つけられなかったがソースコードを見る限り与えることができる。 Fable/src/Fable.Cli/Entry.fs at ac4d44997f69d5b1b7109730ccb45e354a4ec368 · fable-compiler/Fable

この形式なので、以下のようにコマンドを打てばよい。

dotnet fable src --runScript dev

--runScript で実行するスクリプトの方では、 Node.js の引数の取り方を使えば良い。つまり process.argv を使う。

Fable.Node を使ってたら process.argv が簡単に使える。 でも process は F# の予約語で警告が出るので、気持ち悪いからそのうち自前で binding するように直すかも。