---
title: Go から MoonBit へ
description: このブログを MoonBit で作り直し、Cloudflare Workers の blog を TinyGo の wasm から MoonBit の wasm-gc に置き換えた話。
prev: 2026-10-17-workers-wasm.md
next: 2026-10-19-moonbit-memory.md
---
このブログは、もともと Go の標準ライブラリだけで作り、Workers では TinyGo の wasm として動かしていた。
同じサイトを MoonBit で作り直し、今は `blog` という Worker で MoonBit の wasm が動いている。
今読んでいるこのページも、MoonBit が組み立てた HTML だ。

## 何を移したか

Go 版のパッケージを、ほぼ一対一で [`moonbit/`](../moonbit/README.md) に移した。

| Go | MoonBit |
| --- | --- |
| `pkg/el`、`pkg/picoel` | `moonbit/el`、`moonbit/picoel` |
| `pkg/tinymarkdown` | `moonbit/tinymarkdown` |
| `pkg/tinyfrontmatter` | `moonbit/tinyfrontmatter` |
| `go:embed` | `moon.pkg` の `dev_build` で生成するソース |
| `examples/blog/internal/site` | `moonbit/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 |
| TinyGo | 2117.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 版をもう一度デプロイすればいい。

詳しくは [調査メモ](../docs/sketch/ja/moonbit-workers-wasm.md) にまとめてある。
