2023-04-30

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

Fable の練習を始めた。

Fable にしたのはフロントエンドのエコシステムと統合するのに良さそうだったから。 いまの Clojure 製 Cryogen はフロントエンドとは切り離された感じで、一応 Bootstrap を使ってりはしてるが module 管理とかしてない。なのでフロントエンドの依存性の管理みたいのも一緒にしたいなーと考えていた。

今使ってる Clojure 製の Cryogen と同じ雰囲気を F# で再現するなら ionide/Fornax が正にそんな感じっぽかった。 でも折角なので HTML, JavaScript, CSS 共に F# へ統合した感じにしたい。 Fable ならそれができそうに思えた。

実際のところ Fable は F# を他言語へ変換するトランスパイラなので、静的サイトジェネレーターとして使うにはオーバスペックな感じはある。 けど、こんな機会でもないと一生触らないテクノロジなので、やるならこっちやろ!と考えた。

実現したいことは以下の通り。

  1. Markdown で書いたコンテンツを静的サイトに変換する(Cryogen と同じ使用感)
  2. RSS feed の XML を出す(Cryogen で出したのと同じやつ)

ここ 2, 3 週間ほど、子供の寝かしつけ中とかに片手間で Fable やその周辺ツール・モジュールのドキュメントとか諸々の repo を見ていた。けどあんまドキュメント充実しておらず、テンプレプロジェクトを生成して壊してを繰り返してようやく雰囲気がわかってきた。見た目は同じでも中身は違うわ、ってかんじ。

色々試行錯誤した成果として先述の 1 を最小限に実現できたので、一旦日記にまとめる。 krymtkts/blog-fable


実現手段はいくつか調査した。 まず Fable 作者の Fable で静的サイトを作った例1が既にある。 でも激古なためかわたしが理解不足なのか、全くもって動かすことができなかった。 また Bulma という CSS Framework の Fable wrapper である Fulma のテンプレ2もあったのだけど、これも同様に動かし方がわからなかった。

Fable の site で使われている MangelMaxime/Nacara を試してみたけど、これ RSS 以外をほぼ自分で組り込むところがなくて F# で書く楽しさなさそうったので却下。

ここまで来たら、自力で練習がてら Fable の無垢のテンプレに付け足ししていくしかあるまいなとなった。というか周辺ツールの不理解もあって 1 から自分でやらないと無理ゲーと化してた。

まず Fable · Start a new project で素朴なプロジェクトを作成しする。

次に fable-compiler/static-web-generator でやってることを移植していく。 ここではエントリポイントで Markdown などのコンテンツを読み込んでテンプレに埋め込み、 HTML にレンダリングした結果をファイル出力している。 なので、まずは素朴な手書きの HTML でいいからファイル出力できる状態にし、その後 React の wrapper を追加する形で進める。

RSS は、 Fable · .NET and F# compatibility を見るに .NET の機能はまあ使えないので、 Node.js で xml を出力するやつを移植することになるハズ。 なんかハードな気配してきた。


何度も作っては消しを繰り返して得た確かな手順は以下の通り。

Paket を使うと依存関係の管理が楽になる(project ファイルから外に出せるから)のだけど、今回は導入しなかった。関係するツールが多すぎて腹いっぱいになってる。

# 無垢の Fable project 作成する。
dotnet new install Fable.template
dotnet new fable --name blog-fable
cd blog-fable
git init
git commit --allow-empty -m 'Initial commit.'
git add .
git commit -m 'Add minimal Fable template.'

# dotnet tool を更新する。
dotnet tool update fable
dotnet tool update femto

# 動くことを確認する。
npm install
npm run start

# global.json の更新方法わからないので力づくで書き換えて更新する。
dotnet new globaljson --sdk-version 7.0.203 --roll-forward latestFeature --force
dotnet tool restore

build target が .Net Core 2.0 なので .Net 7 にする。 src\App.fsproj を編集する。

 <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>netstandard2.0</TargetFramework>
+ <TargetFramework>net7</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="App.fs" />

Fable も最新版の 4 にする。 これには Femto という tool を使うみたい。 Femto は packet と npm の仲介役のようなやつで、 Nuget から Fable のモジュールを持ってきたらついでに npm してくれるようなやつっぽい。 さっぱりわからんが、コマンド打ったら確かに更新された。

femto install Fable.Core .\src
femto install Fable.Browser.Dom .\src

↓ こうなった。

   <ItemGroup>
- <PackageReference Include="Fable.Browser.Dom" Version="2.2.0" />
- <PackageReference Include="Fable.Core" Version="3.2.3" />
+ <PackageReference Include="Fable.Browser.Dom" Version="2.14.0" />
+ <PackageReference Include="Fable.Core" Version="4.0.0" />
</ItemGroup>

npm run start npm run build あたりが動いてたら大丈夫だろう。 (dotnet tool restorepostinstall で走る記述になってる)

webpack も古かったのでとりあえず上げておいたが、 SSG したいだけなのでこれらは後ほど消した。

ついでに solution も作っとく。

dotnet new sln
dotnet sln add ./src

ここまでやってまだテンプレが最新化されただけ...

ここからが本番。 無味乾燥したテンプレに fable-compiler/static-web-generator の要素を足していく。

以下の Fable の package が使われており、そのうちいくつかは deprecated されてる。分割されている package を追うのは直に repo でコードを追うしかなく、なかなかハードな道のりだった。 そして調べた内容で書き換えをした。書き換えの内容は krymtkts/blog-fable を直に見るのが良い(書くのめんどい)。

これら新しい package を移植し多少のコードを変更することで、やっと Markdown(今は README.md のみ) → HTML とパースして表示できるようになった。


ひとまずここまで。

まだたった 1 ページを出力できただけで先は長いが、 Fable の雰囲気わかってきてできそうな感触を得ている。

やっぱ Node.js の変更周り全然わからんことだらけなので、そこは Nacara/Node.Extra.fs at master · MangelMaxime/Nacara のイケてる実装を参照するなどして組んでいけたら良さそう。

あとやっぱ F# は .NET へ重依存なので .NET を部分的にしか使わない Fable だとかなり書き味が違う。これに慣れるのはちょっとかかりそう。

続く。


  1. fable-compiler/static-web-generator: Simple Fable Node.js app to generate static pages

  2. Template - Fulma