Moonbit で React アプリを書く
自分の中で今 Moonbit が熱いです。Moonbit を普段使いしたいですよね。
というわけで、js backend で FFI を駆使して React でSPAを書けるとこまで頑張ってみました。
React を書いたことがある人なら、以下のコードを見れば理解できると思います。
///|
enum CounterAction {
Increment
}
///|
pub fn counter(_ : EmptyProps) -> @react.Element {
let (count, dispatch) = use_reducer(
(state : Int, action : CounterAction) => match action {
CounterAction::Increment => state + 1
},
0,
)
let on_click = use_callback(
_ => {
dispatch(CounterAction::Increment)
log("Counter clicked: \{count + 1}")
},
[count],
)
h("button", on_click~, ["Count: \{count}"])
}
できるだけ React プログラマとして自然に感じられるAPIを目指しました。
@testing-library/react でテストも書けます
///|
test "increment by click" {
let { render, fire_event, screen, .. } = ReactTestingLibrary::init()
@react.init_react()
render(component(counter, EmptyProps::default()))
fire_event.click(screen.get_by_text("Count: 0"))
let target = screen.get_by_text("Count: 1")
assert_eq(target.textContent(), "Count: 1")
}
構成は、 moon (--target js), vite, npm, tailwind です。
セットアップ等の詳細は以下のリポジトリを見てください。clone すればそのまま使えるはずです。
$ pnpm install
# develop
$ moon build # or moon build --target js --watch
$ pnpm vite
# build
$ pnpm vite build
実装の詳細: mizchi/js.mbt
moonbit は js backend があるんですが、React とのバインディングを書くにあたって、そもそも DOM はおろか EcmaScript のビルトインオブジェクトへのバインディングがありません。
これを実装するために、js binding をひたすら書いていました。
一つのライブラリとしていますが、中身は自分が必要とするものの詰め合わせになっています。
-
mizchi/js: js のプリミティブ型へのバインディング -
mizchi/js/async: Promise と Moonbit の非同期のバインディング -
mizchi/js/dom: DOM API へのバインディング -
mizchi/js/node/*: Node.js APIへのバインディング -
mizchi/js/npm/*: react / react-router 等へのバインディング
まだエコシステムが不安定なので、ライブラリを分割するのは避けて、一つのパッケージで管理しています。
これらは仕様から生成した網羅的なものではなく、自分が必要なもの、自分が moonbit を書くにあたって、使いたいものを優先して実装したものです。
気が向いたら、 next/remix/hono のバインディングを書きます。
余談: Why Moonbit?
(前提として、自分は React SPA や Node.js SSR のチューニングをする仕事が多い、という文脈があります。最近は Node.js ライブラリを書いていることが多いです)
TypeScript を10年ほど書いてきて、その進化や現実のユースケースへの適用に感心する一方、JSの言語由来の本質的な不安定性に不安を感じることが増えました。自分で書くときはいいとして、AIを通してコードを生成する際、悪い品質のコードを学習をしたと思しきコードが頻出します。
TypeScript が流行る2017年より前、AltJS マニアとして様々な言語を書き比べて結局 TypeScript に落ち着いたのですが、10年近く同じ言語を書き続けて自分の成長が感じられなくなってきたのもあります。
要はTSに飽きています。
Moonbit にはTypeScriptにない刺激と、実際に現実のユースケースに対応できるポテンシャルがあります。
Moonbit のよさや、JS バインディングの書き方のパターンについては、後で別の記事で書きます。
Discussion