miniflare@next のコードを読む
現時点ではまだリリースされてなくてベータの状態。
現行のv1について読んでみたのはこちら。
ちなみに、Cloudflare系スタックの開発に使うCLIの`warngler`が、なんとv2になると同時にMiniflareを内蔵したCLIになった。そしてその`wrangler`もv2はまだリリースされてない。つまりそういうことやな・・!
Overview
v2になったとはいえ、正統進化といった感じなので、機能に大きな差異があるわけではない。
読み始めたときの最新のコミットハッシュは`97e5fa1570d8a2d5032130590cd661c32244a1b1`で、公開バージョンでいうと`2.0.0.-rc.3`だった。
コードベースがモノレポになって、より洗練され、より新しいNode.jsのWeb標準系のコードを使うようになったって感じ。
新機能もちょいちょい増えてて、JestのEnvironmentとしても使えるようになったりもしてる。
npm i -D jest jest-environment-miniflare@next
というわけで、コードをざっくり読み直す。
パッケージの全容はこちら。イメージしやすいよう分類別に並べ替えしておいた。
# 表向きのAPI ├── miniflare ├── cli-parser ├── http-server ├── scheduler # そのコア ├── core ├── watcher ├── storage-file ├── storage-memory ├── storage-redis ├── runner-vm # Workerのランタイム ├── cache ├── durable-objects ├── kv ├── sites ├── html-rewriter ├── web-sockets # その他 ├── shared ├── shared-test └── jest-environment-miniflare
というわけで、ざっくり上から眺めていく。
エントリーポイント
`packages/miniflare`がすべてのエントリーであり、npmに公開されてるやつ。
CLIとしてキックする場合は、
- miniflare/bootstrap
- miniflare/src/cli
- miniflare/src/index
- miniflare/src/cli
モジュールとして利用する場合は、
- miniflare/src/index
からのスタートになる。
miniflare: cli
引数をさばくところは、`packages/cli-parser`に分離されてる。
気になった引数は、`--mount`で、これは複数のWorkerを同時に立てて開発するためのもの。複数のWorkerから単一のKVを参照したりするために、あれこれするの大変そう。
core: index
- `class MiniflareCore extends TypedEventTarget {}`
- `EventEmitter`ではなく、`EventTarget`を使ってのイベント駆動
- そして型付にするための独自実装
- コードは`packages/shared`にある
- `#init()`: Workerの初期化(前編)
- `#reload()`: Workerの初期化(後編)
- 初回はスキップされる処理もあるが、実態はこっち
- `#init()`で用意したものを使って、コードを実行するグローバルスコープの用意など
- そして`scriptRunner.run(globalScope, script)`
- `addEventListener()`のWorkerの場合、その内容で待ち受け
- ESMのWorkerの場合は、ここで`fetch`と`scheduled`が返るので、それを待ち受け
- コードの変更を検知するWatcherも再構成
- `packages/watcher`
- `dispatchFetch()`
- `dispatchScheduled()`
- 同上
- このあたりのイベントに関するコードは、`standards/event`にある
- グローバルスコープである`ServiceWorkerGlobalScope`もここ
core: standards
watcher
- スクリプトのコード変更を検知する仕組み
- `chokidar`とか使わずに自作してる
storage-xxx
- KVSの実装で、データをどこに保存するかで3種類ある
- `MemoryStorage`
- オンメモリで保存
- `RedisStorage`
- `ioredis`とつなげてそこに保存
- `FileStorage`
- ローカルファイルに保存
- KVなどの実装のバックエンドとしてだけでなく、内部的なプラグインの設定を保持する用途にも使われる
その他のグローバルオブジェクト
- Cache
- `XxxStorage`を操作する層として実装してある
- KV
- `XxxStorage`を操作する層として実装してある
- `ArrayBuffer`や`Stream`で取り出すこともできるところまで
- Nodeの`stream/consumers|web`とかまで使い倒してる
- DO
- MultiRead / SingleWriteな性質を再現するMutexまで自作して再実装してある
- Sites
- `__STATIC_CONTENT`というKVを用意して再現
- HTMLRewriter
- `html-rewriter-wasm`を使ってる
- WebSocket
- `WebScocket`や`WebSocketPair`を実装してる
- WSのアップグレードに対応した`fetch()`を上書き公開してる
shared
- `compat.ts`
- Compatibility Dates/Flagsの管理
- ここにあるすべてが対応済というわけではなさそう
- https://developers.cloudflare.com/workers/platform/compatibility-dates
- `event.ts`
- `EventTarget`を拡張した`TypedEventTarget`がココにある
- `ServiceWorkerGlobalScope`だけが継承してる`ThrowingEventTarget`もココ
- リスナの実行が`try/catch`されてて、catch時に`stopImmediatePropagation()`する
- 型まわり
- `runner.ts` / `storage.ts` / `plugin.ts` / `wrangler.ts`
- `sync/mutex.ts`
- いわゆる`Mutex`の実装
- コアがスクリプトの変更検知で実行するコールバックの同期実行を保証するのに使ってる
- DOの部分では、`ReadWriteMutex`というのが別で実装されてる
- `sync/gate.ts`
- `InputGate` / `OutputGate`
- `AsyncLocalStorage`を使って非同期処理を直列にしてるっぽい・・?
- `WebSocket`は、ココにある`InputGatedEventTarget`を継承してる
感想
現行のv1系を読んでても思ったけど、ほんとこれ一人で書いてんのマジっすか・・って感じ。
もちろんリファクタする余地とかはあるけど、それでもこの設計とその構想を一人で練り上げてコードに落とし込むって、簡単にできることじゃない・・ほんとすごいわこの人・・。
一通りのコードを読んでみたものの、バグ対応とかOSS的なことができるかと聞かれると、それはまた別の話って感じ。
個人プロジェクトからCloudflareのプロジェクトになったけど、そのへんがこれからどうなっていくのか次第かなーと思ってる。(少なくとも以前よりIssueの反応は遅くなってるので、インターン忙しいんかなー、`wrangler2`のほうに駆り出されてるんかなーとか邪推してる)