Go から MoonBit へ #

このブログは、もともと Go の標準ライブラリだけで作り、Workers では TinyGo の wasm として動かしていた。 同じサイトを MoonBit で作り直し、今は blog という Worker で MoonBit の wasm が動いている。 今読んでいるこのページも、MoonBit が組み立てた HTML だ。

何を移したか #

Go 版のパッケージを、ほぼ一対一で moonbit/ に移した。

GoMoonBit
pkg/el、pkg/picoelmoonbit/el、moonbit/picoel
pkg/tinymarkdownmoonbit/tinymarkdown
pkg/tinyfrontmattermoonbit/tinyfrontmatter
go:embedmoon.pkg の dev_build で生成するソース
examples/blog/internal/sitemoonbit/site
  • Go のテストのケースは、同じ構文木、同じ出力になることを moon test で確かめている。
  • MoonBit には go:embed がないので、記事や文書を (ファイル名, 本文) の組として MoonBit のソースに書き出して埋め込む。生成したソースはコミットしない。
  • 移植のあいだは、Go 版と MoonBit 版に同じパスを取りにいき、ステータス、Content-Type、本文を比べる CLI(make compare)を物差しにした。切り替える前に、記事、文書、それぞれの .md 版、リダイレクト、404 まで、30 パスがすべて一致した。

サイズと起動時間 #

ビルドアップロード(gzip 後)起動時間
MoonBit(wasm-gc)237.41 KiB(78.50 KiB)2 ms
TinyGo2117.89 KiB(757.41 KiB)8 ms

アップロードは TinyGo 版のおよそ 9 分の 1 になった。 このうち少なくない部分は、埋め込んだ記事や文書の本文だ。 Go のランタイムを抱えない分、言語の側の重さはほとんど残っていない。

JS ではなく wasm で #

MoonBit は JS にもビルドできる。 Workers は JS が本来の住人なので、そちらのほうがずっと素直に動くはずだ。 それでも wasm にしたのは、ロマンだ。

その代わり、少し工夫が要った。

  • MoonBit の wasm-gc は、use-js-builtin-string を使うと JS の文字列をそのまま受け渡せる。ただしこれはコンパイル時のオプションが前提で、Workers では import した wasm にそのオプションを渡せない。
  • そこで、文字列を UTF-16 のコード単位ごとに 1 つずつ受け渡す小さな ABI にした。ステータスとヘッダーも同じ ABI で返す。
  • 1 文字ごとに JS と行き来するので遅そうに見えるが、このサイトでいちばん大きいページ(約 54 KB)でも、ローカルの wrangler dev での往復は / より 0.5 ms 遅いだけだった。

Go 版はどうなるか #

Go 版は消していない。 go run ./examples/blog はローカルで記事を書くときのサーバーで、MoonBit 版と比べるときの基準でもある。 記事もまだ Go 版の posts/ に置いてあり、MoonBit 版はそこから埋め込んでいる。 Workers の blog を Go 版に戻したければ、Go 版をもう一度デプロイすればいい。

詳しくは 調査メモ にまとめてある。