Cloudflare Workers SDK を読み解く:アーキテクチャとコードベース全体像
前提知識
- ›モノレポの基本的な概念を理解していること
- ›pnpm / npm workspaces の基礎知識があること
- ›Turborepo などのビルドオーケストレーターを触ったことがあること
Cloudflare Workers SDK を読み解く:アーキテクチャとコードベース全体像
workers-sdk モノレポは、Cloudflare のデベロッパーツール群の中核をなすリポジトリです。Wrangler(CLI)、Miniflare(ローカル Workers ランタイムシミュレーター)、Vite plugin、プロジェクトスキャフォールディングツールの C3、そして多数のサポートパッケージが含まれています。これらは pnpm workspaces と Turborepo で管理されたひとつのリポジトリに収まっています。wrangler dev や npx create-cloudflare を実行したことがあるなら、あなたはすでにこのコードを動かしていたことになります。
この記事はそのための「地図」です。コマンドパース、dev サーバーのオーケストレーション、バンドルパイプラインといったテーマを後の記事で掘り下げる前に、まずは各パーツがどう組み合わさっているか、どのパッケージが何に依存しているか、そしてチームがなぜあえて一風変わったパッケージング設計を選んだのかを把握しておきましょう。
モノレポのレイアウトとパッケージ数
リポジトリは目的ごとに明確に分けられた、3 つのトップレベルディレクトリで構成されています。
| ディレクトリ | 用途 | 概数 |
|---|---|---|
packages/ |
公開 npm パッケージおよび内部ライブラリ | 約 30 パッケージ |
fixtures/ |
統合テスト用プロジェクトとサンプルアプリ | 約 77 fixtures |
tools/ |
内部ビルドユーティリティとスクリプト | ワークスペースルート 1 つ |
ワークスペースの設定は pnpm-workspace.yaml で定義されており、Vite plugin の playground が独立したワークスペースルートとして含まれています。
packages:
- "packages/*"
- "packages/vite-plugin-cloudflare/playground/*"
- "packages/vite-plugin-cloudflare/playground"
- "fixtures/*"
- "tools"
packages/ ディレクトリには wrangler、miniflare、vite-plugin-cloudflare、create-cloudflare といった主役パッケージが並んでいます。それだけでなく、workers-shared(Cloudflare のエッジで動くアセットワーカー)、workers-utils(共有設定パース)、cli(インタラクティブ CLI フレームワーク)などの内部パッケージも含まれています。fixtures はエンドツーエンドテスト用の Worker プロジェクト群で、それぞれ独自の package.json を持ち、場合によっては turbo.json のオーバーライドも備えています。
graph TD
ROOT["workers-sdk root"]
ROOT --> PKG["packages/ (~30)"]
ROOT --> FIX["fixtures/ (~77)"]
ROOT --> TOOLS["tools/"]
PKG --> WRANGLER["wrangler"]
PKG --> MF["miniflare"]
PKG --> VITE["vite-plugin-cloudflare"]
PKG --> C3["create-cloudflare"]
PKG --> UTILS["workers-utils"]
PKG --> SHARED["workers-shared"]
PKG --> CLI["cli"]
コアパッケージの依存グラフ
各パッケージは明確な階層構造を持つ有向依存グラフを形成しています。このグラフを理解することは非常に重要です。ビルド順序を決定し、変更の影響範囲を左右し、特定のアーキテクチャ上の境界がなぜ存在するかを説明してくれるからです。
グラフの最下層に位置するのが workerd — Cloudflare のオープンソース Workers ランタイムで、ネイティブバイナリとして配布されています。Miniflare はその workerd を Node.js フレンドリーな API でラップし、ライフサイクルを管理します。Wrangler はローカル開発のために Miniflare に依存し、Vite plugin も Miniflare に依存しますが、まったく異なる統合経路をたどります(第 6 回の記事で詳しく取り上げます)。
flowchart BT
WORKERD["workerd (native binary)"] --> MF["miniflare"]
MF --> WRANGLER["wrangler"]
MF --> VITE["vite-plugin-cloudflare"]
UTILS["workers-utils"] --> WRANGLER
UTILS --> VITE
UTILS --> C3["create-cloudflare"]
WRANGLER --> VITEST["vitest-pool-workers"]
これらの依存関係は各パッケージの package.json で確認できます。Wrangler のランタイム依存は packages/wrangler/package.json#L67-L76 に記載されており、miniflare が workspace:* として指定されています。Miniflare の依存は packages/miniflare/package.json#L50-L57 で確認でき、workerd が特定の互換性日付バージョンに固定されています。
この階層化は単なる整理整頓ではありません。関心の分離を強制するための設計です。Miniflare は CLI 引数のパースを一切知りません。Wrangler は Cap'n Proto による設定シリアライズを知りません。workers-utils パッケージが設定パースを担い、Wrangler と Vite plugin の両方がそれを使うことで、wrangler.toml の解釈がどちらのツールでも一致することを保証しています。
workers-utils 共有パッケージ
@cloudflare/workers-utils パッケージは、設定のパースと検証における唯一の信頼できる情報源です。Wrangler と Vite plugin の両方がここからインポートしているため、wrangler.toml・wrangler.json・wrangler.jsonc のいずれも、どのツールが読み込んでも同じように解釈されます。
設定のエントリポイント packages/workers-utils/src/config/index.ts は、拡張子でファイル形式を判別する configFormat() 関数と、すべての設定データの形を定義した正規化済みの Config 型・RawConfig 型をエクスポートしています。
flowchart LR
TOML["wrangler.toml"] --> PARSE["workers-utils config parser"]
JSON["wrangler.json"] --> PARSE
JSONC["wrangler.jsonc"] --> PARSE
PARSE --> CONFIG["Normalized Config"]
CONFIG --> WRANGLER["Wrangler readConfig()"]
CONFIG --> VITE["Vite plugin"]
この設計により、新しい設定フィールドを追加する際の変更箇所は常に一か所で済みます。また、廃止予定フィールドへの警告、未知のキーの検出、環境継承に関するバリデーション診断も、すべてのコンシューマーで一貫して機能します。
ヒント: コードベースを読んでいて
@cloudflare/workers-utilsからの config 関連インポートを見かけたら、それは共有レイヤーです。../configからのインポートであれば、.envの読み込みやアップデートチェックなど CLI 固有の動作を追加した Wrangler のラッパーを見ています。
依存パッケージのバンドル戦略
ここで workers-sdk は一般的な npm パッケージングから大きく外れた選択をしています。Wrangler の package.json を見ると、ランタイム依存がたった 8 件 しかないのに対し、devDependencies は 90 件以上 あります。これは偶然ではありません。
packages/wrangler/package.json#L67-L76 に記載されている 8 つのランタイム依存は次のとおりです。
| 依存パッケージ | ランタイム依存である理由 |
|---|---|
miniflare |
ワークスペース依存。独自のネイティブ依存を持つ |
workerd |
ネイティブバイナリ — バンドル不可 |
esbuild |
ネイティブバイナリ — バンドル不可 |
blake3-wasm |
ランタイムで解決が必要な WASM モジュール |
unenv |
ポリフィルパスのために require.resolve が必要 |
@cloudflare/unenv-preset |
unenv のコンパニオンパッケージ |
@cloudflare/kv-asset-handler |
ワークスペース依存 |
path-to-regexp |
ランタイム依存 |
それ以外の chalk、yargs、undici、chokidar、ws、prompts など多数のパッケージは devDependencies として管理され、ビルド時に Wrangler の出力へバンドルされます。ビルドには tsup(esbuild のラッパー)を使い、自己完結したバンドルを生成します。
なぜこの設計なのでしょうか。エンドユーザーへの依存チェーン汚染を防ぐためです。npm install wrangler を実行したとき、ユーザーが受け取るのは Wrangler のコードと、node_modules に独立したエントリとして必ず存在しなければならないパッケージ(ネイティブバイナリ、WASM、require.resolve が必要なパッケージ)だけです。90 以上のパッケージの間接的な依存ツリーを引き継ぐことがなく、バージョン競合やサプライチェーンリスクを最小化できます。
flowchart LR
DEV["~90 devDependencies"] -->|"bundled by tsup"| DIST["wrangler-dist/cli.js"]
RT["8 runtime dependencies"] -->|"installed normally"| NM["node_modules/"]
DIST --> USER["End user"]
NM --> USER
pnpm Catalog によるバージョン固定
モノレポ全体では、重要な依存パッケージのバージョンを統一するために pnpm の catalog: プロトコルを活用しています。pnpm-workspace.yaml#L18-L48 で定義されたこの仕組みは、一元管理されたバージョンレジストリとして機能します。
主な固定バージョンは次のとおりです。
| パッケージ | バージョン | 用途 |
|---|---|---|
workerd |
1.20260317.1 |
Workers ランタイムバイナリ — 全パッケージで一致必須 |
esbuild |
0.27.3 |
バンドラーバージョンの統一 |
vitest |
4.1.0 |
テストランナーのバージョン |
vite |
^8.0.0 |
Vite フレームワークのバージョン |
typescript |
~5.8.3 |
コンパイラのバージョン |
undici |
7.24.4 |
HTTP クライアント。undici-types も合わせて固定 |
package.json で "workerd": "catalog:default" と書くと、pnpm は catalog に宣言されたバージョンに解決します。モノレポ内の 2 つのパッケージが異なる workerd バージョンを実行して互換性の問題が起きる、という悪夢を防ぐための仕組みです。
ヒント: catalog には、
@cloudflare/vitest-pool-workersがなぜworkspace:*ではなく catalog バージョンを使うのかを説明するコメントも記載されています。vitest-pool-workers に 含まれる パッケージが、それ自身で テストされる 必要もあるため、循環依存を避けるためにこの方式が採用されています。
Turborepo のタスクグラフ
Turborepo はモノレポ全体のビルド、テスト、型チェックをオーケストレーションします。turbo.json にタスクの依存グラフが定義されています。
flowchart TD
BUILD["build"] -->|"^build (topological)"| BUILD
TEST["test"] -->|"depends on"| BUILD
TESTCI["test:ci"] -->|"depends on"| BUILD
TESTE2E["test:e2e"] -->|"depends on"| BUILD
CHECKTYPE["check:type"] -->|"depends on"| BUILD
DEV["dev"] -.->|"persistent, no cache"| DEV
dependsOn に書かれた "^build" という構文は、Turborepo のトポロジカル依存マーカーです。「パッケージ X をビルドする前に、X が依存するすべてのパッケージを先にビルドする」という意味です。これにより、Wrangler のビルド時には Miniflare と workers-utils が必ず先にビルドされていることが保証されます。
テストタスクが依存するのは ^build ではなく build です。自分自身のパッケージさえビルドされていればよく、依存パッケージの再ビルドは不要(すでにビルド済みと見なす)だからです。dev タスクは persistent: true かつ cache: false に設定されており、長時間実行される watch プロセスとして適切な設定です。
globalPassThroughEnv には、キャッシュキーに影響を与えずに Turborepo が素通しすべき環境変数が列挙されています。CI トークン、Docker 設定、WRANGLER_LOG や CLOUDFLARE_API_TOKEN といった Wrangler 固有の環境変数などが含まれます。
次回予告
全体像が把握できたところで、いよいよ最初のサブシステムに踏み込んでいきましょう。次回は、Wrangler がシェルのエントリポイントから起動して宣言的なコマンド登録システムに至るまでの流れを追います。このシステムは yargs の上に構築されたカスタムレイヤーで、TypeScript のジェネリクスを活用してランタイムオーバーヘッドゼロの型安全性を実現しています。