Tauriのアーキテクチャ:15クレートからなるRustモノレポを読み解く
前提知識
- ›Rustの基本知識(トレイト、ジェネリクス、モジュール)
- ›Cargoワークスペースとクレート依存関係への理解
- ›デスクトップWebviewフレームワークの概要把握
Tauriのアーキテクチャ:15クレートからなるRustモノレポを読み解く
Tauriは、オープンソースのRustプロジェクトの中でも特に野心的な一作です。WebフロントエンドとRustバックエンドを組み合わせ、デスクトップ・モバイルアプリを1つのコードベースから届けられるポリグロットなアプリケーションフレームワークです。ところが、リポジトリを初めてクローンした瞬間、15個のクレート、複数のTypeScriptパッケージ、コンパイル時からランタイムまで広がるビルドパイプラインが一気に目に飛び込んできます。この記事では、そのすべてをスムーズに把握するためのメンタルモデルを紹介します。
モノレポの全体像
リポジトリのトップレベルはシンプルな構造になっています。
| ディレクトリ | 役割 |
|---|---|
crates/ |
すべてのRustクレート — コアフレームワーク、ランタイム、CLI、バンドラー、マクロ、ヘルパー |
packages/ |
TypeScript/JavaScriptパッケージ — @tauri-apps/api JS APIとNAPIベースのCLIラッパー |
examples/ |
APIの全機能を網羅したサンプルアプリなど |
bench/ |
ベンチマーク用ハーネス |
ワークスペースルートの Cargo.toml がすべてをまとめ、15のメンバークレートに加えてテスト・サンプルプロジェクトを宣言しています。
graph TB
subgraph "Workspace Root"
CT[Cargo.toml]
end
subgraph "crates/"
tauri[tauri]
runtime[tauri-runtime]
wry_rt[tauri-runtime-wry]
macros[tauri-macros]
utils[tauri-utils]
build[tauri-build]
codegen[tauri-codegen]
plugin[tauri-plugin]
cli[tauri-cli]
bundler[tauri-bundler]
sign[tauri-macos-sign]
schema[tauri-schema-generator]
schema_w[tauri-schema-worker]
driver[tauri-driver]
end
subgraph "packages/"
api["@tauri-apps/api"]
cli_napi["@tauri-apps/cli (NAPI)"]
end
CT --> tauri
CT --> cli
CT --> api
ワークスペースでは resolver = "2" が設定されており、Rustの最小バージョンとして1.77.2が要求されます。リリースプロファイルはバイナリサイズの最小化を徹底的に追求しており(opt-level = "s"、LTO有効、コード生成単位を1つに統合)、これはTauriが掲げる「小さなバイナリ」という核心的な価値観を反映しています。
ヒント:
Cargo.toml末尾の[patch.crates-io]セクションでは、tauri、tauri-plugin、tauri-utilsをローカルパスに固定しています。つまりワークスペース内のすべてのクレートは、crates.ioで公開されたバージョンではなく、常にローカルのソースを参照します。依存バージョンを読む際は、この点を念頭に置いておきましょう。
コアクレート:tauri
tauri クレートはこのリポジトリの主役です。アプリ開発者が依存するのはこのクレートであり、必要なものはほぼすべてここから再エクスポートされています。crates/tauri/src/lib.rs のモジュール宣言を見ると、内部の構成が見えてきます。
| モジュール | 公開範囲 | 役割 |
|---|---|---|
app |
pub(crate) |
Builderパターン、App、AppHandle、RunEvent |
ipc |
pub |
IPCコマンド、チャンネル、authority |
plugin |
pub |
Pluginトレイトとビルダー |
webview |
pub |
WebviewおよびWebviewWindowの型 |
window |
pub |
ウィンドウ管理 |
event |
private | イベントシステム |
manager |
private | AppManager — 中央オーケストレーター |
protocol |
pub(crate) |
カスタムURIプロトコルハンドラー |
state |
private | TypeIdをキーとした状態管理 |
menu |
pub(デスクトップのみ) |
メニューAPI |
tray |
pub(デスクトップ + feature) |
システムトレイAPI |
このクレートは Manager、Listener、Emitter という3つの基盤トレイトを定義しており、これらが公開APIの骨格を形成しています。App、AppHandle、Window、Webview、WebviewWindow がこれらのトレイトを実装することで、状態へのアクセス、イベントの発行、リソース管理に一貫したインターフェースが提供されます。
classDiagram
class Manager {
+app_handle() AppHandle
+config() Config
+get_webview_window(label) Option~WebviewWindow~
+state~T~() State~T~
}
class Listener {
+listen(event, handler) EventId
+once(event, handler) EventId
+unlisten(id)
}
class Emitter {
+emit(event, payload) Result
+emit_to(target, event, payload) Result
}
class ManagerBase {
<<sealed>>
+manager() AppManager
+runtime() RuntimeOrDispatch
}
Manager --|> ManagerBase : depends on
Listener --|> ManagerBase : depends on
Emitter --|> ManagerBase : depends on
App ..|> Manager
AppHandle ..|> Manager
Window ..|> Manager
Webview ..|> Manager
WebviewWindow ..|> Manager
1059行目で定義されているsealed化された ManagerBase トレイトは巧みな設計です。内部の AppManager へのアクセスを必須としながら、pub(crate) モジュール内に置くことで外部コードからの実装を防いでいます。つまり Manager、Listener、Emitter は事実上sealedであり、利用することはできても、自分の型に実装することはできません。
ランタイム抽象化レイヤー
Tauriは「フレームワークがWebviewランタイムに求めるもの」と「WRY/TAOによる実際の実装」を明確に分離しています。この分離は2つのクレートにまたがります。
tauri-runtime は抽象トレイトを定義します。Runtime トレイトはdispatcher、handle、イベントループproxyに対応する関連型を規定し、ウィンドウ・Webviewの生成やモニター情報の取得などのメソッドを宣言しています。対となる RuntimeHandle トレイトは Send + Sync + Clone を満たすhandleを提供し、どのスレッドからでも安全に利用できます。
tauri-runtime-wry はWRY(Webviewレンダリング)とTAO(ウィンドウ管理)を用いた具体的な実装を提供します。両ライブラリを再エクスポートし、抽象トレイトへの橋渡しを行います。
flowchart TB
subgraph "Application Code"
App["Your App"]
end
subgraph "tauri crate"
Manager["Manager / Builder"]
end
subgraph "tauri-runtime"
RT["Runtime trait"]
RTH["RuntimeHandle trait"]
WD["WindowDispatch trait"]
WVD["WebviewDispatch trait"]
end
subgraph "tauri-runtime-wry"
Wry["Wry struct"]
end
subgraph "External"
WRY["WRY (webview)"]
TAO["TAO (windowing)"]
end
App --> Manager
Manager --> RT
RT -.->|"impl"| Wry
Wry --> WRY
Wry --> TAO
メインの tauri クレートは Wry という型エイリアスを定義し、tauri_runtime_wry::Wry<EventLoopMessage> をラップしています。#[default_runtime] proc macroとの組み合わせにより、wry featureが有効な場合はジェネリクスパラメータ R: Runtime が自動で補完されるため、エンドユーザーがこのパラメータを意識する場面はほとんどありません。
コンパイル時クレート:utils、codegen、macros、build
Tauriの処理の多くはコンパイル時に行われます。そのパイプラインを担う4つのクレートを見ていきましょう。
flowchart LR
CONFIG["tauri.conf.json"] --> UTILS["tauri-utils<br/>Config parsing, ACL types"]
UTILS --> CODEGEN["tauri-codegen<br/>Asset embedding, context generation"]
CODEGEN --> MACROS["tauri-macros<br/>#[command], generate_context!, generate_handler!"]
UTILS --> BUILD["tauri-build<br/>build.rs helpers, ACL resolution"]
BUILD --> CODEGEN
MACROS --> APP["Compiled application"]
BUILD --> APP
tauri-utils はこのパイプラインの土台です。tauri.conf.json のRust表現である Config 構造体、すべてのACL型(capabilities、permissions、scopes)、そして共有ユーティリティを定義しています。ランタイム層への依存を一切持たないため、ビルドスクリプトやproc macroから安全に利用できます。
tauri-codegen はconfigを読み込み、フロントエンドのアセットを探索・圧縮し、Context 構造体をトークンストリームとして生成します。get_config 関数は TAURI_CONFIG 環境変数によるオーバーライドにも対応しており、これによってCLIがマージ済みの設定をコンパイルステップへ受け渡せる仕組みになっています。
tauri-macros は開発者が直接使うproc macroを提供します。コマンド関数に付与する #[command] やdispatchテーブルを構築する generate_handler! が代表例です。コンパイル時のcontextを埋め込む generate_context! やジェネリクスのruntimeパラメータを自動補完する #[default_runtime] も含まれます。
tauri-build はユーザーの build.rs から呼び出すことを想定したクレートです。ACLパーミッションの解決、リソースのコピー、Windowsマニフェストの生成、そして必要に応じて tauri-codegen へのアセット埋め込み処理の委譲を担います。
ツールチェーンクレート:CLI、バンドラー、署名
開発者向けツールチェーンは3つのクレートで構成されています。
tauri-cli は cargo tauri コマンドの実装本体です。main.rs では cargo-tauri(Cargoサブコマンド)として呼ばれたのか直接実行されたのかを判定し、余分な tauri 引数を除去してから tauri_cli::run() に処理を委ねます。lib.rs のClapベースのコマンド構造には dev、build、bundle、init、add、android、ios、plugin、icon、signer など豊富なサブコマンドが並びます。
tauri-bundler はコンパイル済みバイナリを受け取り、各プラットフォーム向けのパッケージを生成します。macOS向けのDMGと .app バンドル、Windows向けのMSI/NSSインストーラー、Linux向けのDEB/RPM/AppImageなどに対応しています。
tauri-macos-sign はmacOSのコード署名と公証(notarization)を担当します。
CLIは packages/cli/ でNAPIモジュールとしてもラップされており、@tauri-apps/cli としてnpm経由で配布されます。JavaScriptフレームワークを使うユーザーの多くは、このnpmパッケージを通じてTauriを操作しています。
JS APIパッケージ
packages/api/ にある @tauri-apps/api パッケージは、フロントエンドコードがRustバックエンドと通信するためのTypeScriptインターフェースを提供します。コアモジュール(packages/api/src/core.ts)がエクスポートする invoke() 関数は、その中心的な存在です。
async function invoke<T>(
cmd: string,
args: InvokeArgs = {},
options?: InvokeOptions
): Promise<T> {
return window.__TAURI_INTERNALS__.invoke(cmd, args, options)
}
この関数は window.__TAURI_INTERNALS__ に処理を委ねており、このオブジェクトはTauriがすべてのWebviewに注入する初期化スクリプトによって設定されます。Channel クラス(77〜154行目)はRustからJavaScriptへのストリーミングデータ転送をメッセージの順序保証付きで実現しており、プラグインの進捗報告やリアルタイム更新で広く活用されるパターンです。
sequenceDiagram
participant Frontend as JS Frontend
participant Internals as __TAURI_INTERNALS__
participant Protocol as ipc:// Protocol
participant Rust as Rust Backend
Frontend->>Internals: invoke("greet", {name: "World"})
Internals->>Protocol: HTTP POST with headers
Protocol->>Rust: parse_invoke_request()
Rust->>Rust: ACL check + command dispatch
Rust-->>Protocol: Response
Protocol-->>Frontend: Promise resolves
クレートの依存関係グラフ
各クレートの実際の依存関係を示します。コンパイル時クレート(左側)とランタイムクレート(右側)がはっきりと分かれているのが見て取れます。
graph TD
tauri_utils["tauri-utils"]
tauri_runtime["tauri-runtime"]
tauri_runtime_wry["tauri-runtime-wry"]
tauri_codegen["tauri-codegen"]
tauri_macros["tauri-macros"]
tauri_build["tauri-build"]
tauri_plugin["tauri-plugin"]
tauri_main["tauri"]
tauri_cli["tauri-cli"]
tauri_bundler["tauri-bundler"]
tauri_utils --> tauri_runtime
tauri_utils --> tauri_codegen
tauri_utils --> tauri_build
tauri_utils --> tauri_plugin
tauri_utils --> tauri_cli
tauri_utils --> tauri_bundler
tauri_runtime --> tauri_runtime_wry
tauri_runtime --> tauri_main
tauri_runtime_wry --> tauri_main
tauri_codegen --> tauri_macros
tauri_codegen --> tauri_build
tauri_macros --> tauri_main
tauri_build -.->|"build.rs"| tauri_main
tauri_plugin -.->|"build.rs"| tauri_main
tauri_bundler --> tauri_cli
この構造はまさに層状のケーキです。最下層に tauri-utils(Tauri固有の依存なし)、その上に抽象インターフェースを定義する tauri-runtime、具体的な実装を提供する tauri-runtime-wry、そしてすべてを統合する tauri が積み重なります。コンパイル時クレート(codegen、macros、build)は並行したトラックで動作し、tauri-utils の型を参照しながらも、ランタイム層には一切依存しません。
ヒント: 特定の機能がどのクレートにあるかを調べるときは、次のルールから始めましょう。型や設定に関するものであれば
tauri-utils、ビルド時の処理であればtauri-buildまたはtauri-codegen、ランタイムの動作であればtauriクレートの内部モジュール、WebviewやWindowの生成に関わるものであればtauri-runtimeまたはtauri-runtime-wryを確認してみてください。
次の記事では、Tauriアプリケーションのライフサイクルを端から端まで追っていきます。コンパイル時にアセットを埋め込む generate_context! マクロを起点に、Builderパターンを経て、アプリを動かし続けるイベントループに至るまでの全体の流れを解説します。