- Authors

- Name
- Youngju Kim
- @fjvbn20031
プロローグ — なぜ今、また「デスクトップフレームワーク」の話なのか
「Electronは重い」という言葉は使い古されすぎてもはや陳腐だ。Slackを起動するとRAMが400MB消え、VS Codeは1GBに達し、Discordのインストーラーは90MBある。毎回、同じChromiumのコピーがユーザーのディスクとメモリを占拠する。
代替手段はずっと語られてきた。ネイティブで書く(人員が二倍)、Flutterデスクトップ(まだ不慣れ)、Qt(商用ライセンス)、そして — Tauri。
2024年10月、Tauri 2.0がGAになった。2026年現在、リリースから一年半が経過している。約束は強かった。10MBのバンドル、システムWebView、Rustコア、iOSとAndroid対応。紙の上では、Electronの席を奪うように見えた。
実際は? Slackは依然としてElectronだ。VS CodeもElectron。DiscordもElectron。大手アプリが移行しない理由がある。同時に、SilentKeys、Voxly、Watson.ai、Pakeといった新しいアプリは最初からTauriを選んでいる。新規プロジェクトと既存プロジェクトで判断が分かれている。
この記事はその分岐を整理する。アーキテクチャの本当の違い、測定可能なトレードオフ(バンドル、メモリ、起動時間)、システムWebViewが持ち込む影、セキュリティモデルの仕組み、モバイル対応の現実、そして「我々のチームに合うのか」を判断する基準。ElectronとTauriのどちらが万人にとっての正解、という記事ではない。
一行の要点: TauriはElectronを「軽くしたもの」ではなく、別のトレードオフを選んだ別の道具だ。そのトレードオフを知れば選択は容易になる。
1章 · アーキテクチャ — 何がどう違うのか
Tauriの差別化は一行で言える。Chromiumを同梱しない。代わりにOSのシステムWebViewを使う。
Electronアプリの構成
┌────────────────────────────────────────────────┐
│ あなたのアプリ │
│ ├─ メインプロセス: Node.js ランタイム │
│ ├─ レンダラープロセス: Chromium (V8 + Blink) │
│ └─ Electron バイナリ (約80〜120MB) │
│ │
│ → 同じOSに同じChromiumのコピーがN個存在する │
└────────────────────────────────────────────────┘
Tauriアプリの構成
┌────────────────────────────────────────────────┐
│ あなたのアプリ │
│ ├─ Rust コア (約3〜5MB) │
│ ├─ システムWebView呼び出し: │
│ │ macOS → WKWebView (Safari/WebKit) │
│ │ Windows → WebView2 (Edge/Chromium) │
│ │ Linux → WebKitGTK │
│ └─ Webアセット(HTML/JS/CSS) │
│ │
│ → OSがすでに持っているWebViewを借りる │
└────────────────────────────────────────────────┘
違いの帰結:
- バンドルサイズ: Electron 80〜200MB → Tauri 2〜40MB(多くは5〜15MB)
- メモリ: Electron 200〜400MB アイドル → Tauri 30〜80MB アイドル
- 起動時間: Electron 1〜3秒 → Tauri 0.2〜0.8秒
- レンダリングの一貫性: Electronは同一、TauriはOSごとに別エンジン(これが核心のトレードオフ)
Tauriのバックエンド言語はRust。バックエンドコード(ファイルシステム、ネットワーク、システムAPI呼び出し)をRustで書き、フロントエンド(React/Vue/Svelte/Solidなど)はそのままWeb技術で書く。両者はIPCでメッセージをやり取りする。Electronのmain/renderer構造と概念的には同じで、ただ「main」がNode.jsではなくRustになっている。
まとめ: Electronは「ブラウザとNodeを丸ごと持ち歩く」、TauriはOSが持っているWebViewを借りてバックエンドだけRustでコンパイルする。
2章 · バンドルサイズとメモリ — 測定できる違い
抽象的な比較はやめて数値で見る。2026年時点の公開ベンチマークと移行事例から集めた値だ。
バンドルサイズ(.dmg / .msi / .AppImage)
| アプリの種類 | Electron | Tauri | 比率 |
|---|---|---|---|
| 最小の「Hello World」 | 85MB | 2.5MB | 34倍 |
| 中規模(エディタ、チャット) | 120〜160MB | 8〜15MB | 約12倍 |
| 重量級(IDEクラス) | 180〜250MB | 25〜40MB | 約7倍 |
| インストーラ平均 | 80〜150MB | 3〜10MB | 約20倍 |
メモリ(アイドル時)
| アプリ | Electron | Tauri |
|---|---|---|
| 空のウィンドウ1つ | 180〜250MB | 25〜45MB |
| 小さなUI画面 | 220〜300MB | 35〜60MB |
| 中複雑度SPA | 300〜500MB | 60〜120MB |
起動時間(コールドスタート、M2 MacBook Pro)
| アプリ | Electron | Tauri |
|---|---|---|
| 最小アプリ | 0.8〜1.5秒 | 0.15〜0.35秒 |
| 中規模 | 1.5〜3秒 | 0.4〜0.9秒 |
この数値が意味するもの: バンドル・メモリ・起動時間は本当に違う。ある移行事例は「インストーラ95%縮小、メモリ70%削減」を報告している。測定可能な勝利だ。
ただし — これがすべてを決めるわけではない。次章の落とし穴を読んでほしい。
3章 · システムWebViewの影 — 「一度書けばどこでも動く」という嘘
Tauri最大の弱点は、最大の強みから生まれる。システムWebViewだから軽いが、システムWebViewだからOSごとにレンダリングが異なる。
同じReactコードを実行すると:
macOS → WKWebView (Safari) — Safariのバグや非対応をそのまま引き継ぐ
Windows → WebView2 (Edge/Chromium) — Chromiumなので最も互換的
Linux → WebKitGTK — 最も遅れている。最新CSS/JS APIの一部が未対応
これが何を意味するか:
backdrop-filter: macOS/Windowsでは問題ないが、WebKitGTKは部分対応のみ:has()セレクタ: 2026年もWebKitGTKのバージョンによってばらつくOffscreenCanvas: macOS/Windows はOK、Linuxはケースバイケース- WebRTC、MediaRecorder: プラットフォームごとにコーデックと挙動が違う
- フォントレンダリング: macOSのアンチエイリアスはWindows/Linuxと別物に見える
- ドラッグ&ドロップ、Clipboard API: 各WebViewの癖がそのまま表れる
Electronは定義上この問題を持たない。どこで動かしても同じChromiumなので同じピクセルが出る。高価だが一貫性を買っている。
Tauriで書く現実のコードはこうなる。
/* Linux WebKitGTK 用のフォールバックを想定する */
.frosted {
background: rgba(255, 255, 255, 0.85);
}
@supports (backdrop-filter: blur(10px)) {
.frosted {
background: rgba(255, 255, 255, 0.5);
backdrop-filter: blur(10px);
}
}
Linuxユーザーが少ないアプリなら無視してよい。だが開発者ツールを作るならLinuxユーザー比率が30〜40%になり得る。すると新しいCSS/JS機能のたびにフォールバックを書く必要が出てくる。「Chromiumで動くから終わり」は通用しない。
まとめ: Tauriは「一度書けばどこでも動く」ではなく「一度書いて3つのOSでテストする」。ElectronよりQAコストがかかる。
4章 · セキュリティモデル — allowlistからcapabilitiesへ
Tauri 2の大きな変更点の一つがセキュリティモデルだ。v1のallowlistは単純なオン/オフトグルだった — 「fs APIを有効にするか否か」。これが限界に達したので、v2はpermissions / scopes / capabilitiesの3層に再設計された。
3つの概念
- Permissions: Tauriコマンドのオン/オフ(例:
fs:allow-read-file) - Scopes: コマンドのパラメータ検証(例: 「このパスパターンのみ許可」)
- Capabilities: 権限とスコープをウィンドウ/WebViewに紐付けて付与
これらをJSONかTOMLで宣言する。src-tauri/capabilities/ディレクトリにファイルを置けば自動で有効になる。
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main-capability",
"description": "メインウィンドウに付与する権限の束",
"windows": ["main"],
"permissions": [
"core:default",
"fs:allow-read-text-file",
"fs:allow-write-text-file",
{
"identifier": "fs:scope",
"allow": [
{ "path": "$APPDATA/notes/*" },
{ "path": "$DOCUMENT/MyApp/**" }
],
"deny": [
{ "path": "$HOME/.ssh/*" }
]
},
"shell:allow-open"
]
}
この設定が意味するもの:
- メインウィンドウだけがこれらの権限を受け取る(他のウィンドウは別capability が必要)
- ファイルシステムの読み書きは有効 — ただし
$APPDATA/notes/と$DOCUMENT/MyApp/配下のみ $HOME/.ssh/*は明示的に拒否shell:openは許可(メール/URLを開くため)
Electronにはこのような宣言的モデルがない。Electronではセキュリティはコードパターンだ — contextIsolation: true、nodeIntegration: false、preloadスクリプト、IPCホワイトリストを手で書く必要がある。忘れればRCE脆弱性になる。
Tauriのモデルがより安全な理由:
- 宣言的: 権限がコードではなく設定なので監査が容易
- きめ細かい: ファイルシステム全体ではなく特定パスのみ
- デフォルト拒否: capability を明示しないIPCコマンドはブロックされる
- ウィンドウ別: メインウィンドウとaboutウィンドウの権限を分離
もちろんElectronも気を配って書けば同等になる。違いは — Tauriではそれがデフォルトであり、Electronでは追加作業であるという点。
まとめ: capabilitiesは「デフォルトで安全なIPC」を強制する。Electronで気を抜くとRCEになるパターンが、Tauriではcapabilityファイルを書かなければIPCが単に拒否される。
5章 · IPC契約 — コマンド一つを最初から最後まで見る
抽象的な話はやめて、実際のIPCコマンドを一つ最初から最後まで見よう。シナリオ: フロントエンドで「ノート保存」ボタンを押すと、バックエンド(Rust)がディスクに書き込む。
Rust側: コマンド定義
src-tauri/src/commands.rs:
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use tauri::AppHandle;
use tauri_plugin_fs::FsExt;
#[derive(Debug, Serialize, Deserialize)]
pub struct Note {
pub id: String,
pub title: String,
pub body: String,
}
#[derive(Debug, Serialize)]
pub struct SaveResult {
pub bytes_written: usize,
pub path: String,
}
#[tauri::command]
pub async fn save_note(
app: AppHandle,
note: Note,
) -> Result<SaveResult, String> {
// 1) 入力検証 — IPC境界は信頼できない
if note.id.is_empty() || note.id.contains('/') || note.id.contains('\\') {
return Err("invalid note id".into());
}
if note.body.len() > 10 * 1024 * 1024 {
return Err("note too large (>10MB)".into());
}
// 2) capability が許可するパス配下でのみ作業
let app_data = app
.path()
.app_data_dir()
.map_err(|e| format!("no app data dir: {e}"))?;
let mut path: PathBuf = app_data.join("notes");
std::fs::create_dir_all(&path).map_err(|e| e.to_string())?;
path.push(format!("{}.json", note.id));
// 3) シリアライズして書き込む
let body = serde_json::to_vec_pretty(¬e).map_err(|e| e.to_string())?;
let bytes = body.len();
std::fs::write(&path, body).map_err(|e| e.to_string())?;
Ok(SaveResult {
bytes_written: bytes,
path: path.display().to_string(),
})
}
src-tauri/src/lib.rs:
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_fs::init())
.invoke_handler(tauri::generate_handler![
commands::save_note,
])
.run(tauri::generate_context!())
.expect("tauri 起動失敗");
}
TypeScript側: 呼び出し
src/lib/notes.ts:
import { invoke } from '@tauri-apps/api/core'
export interface Note {
id: string
title: string
body: string
}
export interface SaveResult {
bytesWritten: number
path: string
}
export async function saveNote(note: Note): Promise<SaveResult> {
// invoke は Rust 側の #[tauri::command] 名と 1:1 でマッピングされる
// シリアライズは自動 (serde と JSON の間)
// ただし snake_case と camelCase の対応規則は確認が必要
const raw = await invoke<{ bytes_written: number; path: string }>('save_note', {
note,
})
return { bytesWritten: raw.bytes_written, path: raw.path }
}
Reactコンポーネント内で:
import { useState } from 'react'
import { saveNote } from './lib/notes'
export function NoteEditor() {
const [title, setTitle] = useState('')
const [body, setBody] = useState('')
const [status, setStatus] = useState<string | null>(null)
async function onSave() {
try {
const result = await saveNote({
id: crypto.randomUUID(),
title,
body,
})
setStatus(`保存完了: ${result.path} (${result.bytesWritten} bytes)`)
} catch (e) {
setStatus(`失敗: ${String(e)}`)
}
}
return (
<div>
<input value={title} onChange={(e) => setTitle(e.target.value)} />
<textarea value={body} onChange={(e) => setBody(e.target.value)} />
<button onClick={onSave}>保存</button>
{status && <p>{status}</p>}
</div>
)
}
なぜこれが安全か
- capability で明示的に許可 —
src-tauri/capabilities/main.jsonにsave_noteが含まれるウィンドウのみ呼び出し可能 - Rust側で入力検証 — id にスラッシュがあればディレクトリ脱出の試み、サイズ制限も明示
- パス限定 —
app_data_dir()配下のみで作業、ユーザーが任意のパスを注入できない - 型安全 — Rust側はコンパイラが、TS側はinvokeのジェネリクスが検証
Electronで同じものを書こうとすると、preload + contextBridge + IPCハンドラ + 明示的なホワイトリストを自分で組み立てる必要がある。コード量は同程度だが、忘れるとセキュリティホールになる。
まとめ: TauriのIPCはRustの型システムとcapabilityシステムが一緒に強制する。セキュリティがコードの隣に置かれている。
6章 · モバイル — Tauri 2の本当に大きな変化
Tauri 1はデスクトップのみだった。v2の発表で最大の見出しだったのがiOSとAndroid対応だ。2026年時点で、これがどこまで現実になっているかが重要になる。
何ができるか
- iOSとAndroidのビルドが安定(Tauri 2.0 GAから)
- 同じWebフロントエンドのコードをデスクトップと共有
- Rustバックエンドをモバイルでも呼び出し可能(ネイティブプラグインはSwift/Kotlin/Javaで記述可能)
- 基本プラグイン(ファイルシステム、通知、ダイアログなど)の多くがモバイルで動作
まだ粗い部分
- デスクトッププラグイン = モバイルプラグインではない。一部のプラグインはデスクトップ専用、一部はモバイル専用
- Tauriチーム自身が認めている — 「モバイルを第一級市民に」という約束は過大だった、コミュニティと共に作っていくとGA振り返りに書かれている
- iOSのサイドローディングの難しさ(Appleのポリシー)、Androidは比較的自由
- FlutterやReact Nativeほどモバイル向けに最適化されたUXを自動では提供しない — どのみちWebViewベースなので、モバイルネイティブの感触を出すには追加作業が必要
いつTauriモバイルを選ぶか
| シナリオ | 推奨 |
|---|---|
| すでにTauriデスクトップアプリがあり、同じコードをモバイルに拡張 | Tauriモバイルが良い |
| モバイル優先のアプリ、デスクトップは副次 | React Native / Flutter / ネイティブ |
| デスクトップとモバイルの両方が一級、コード共有が重要 | Tauriを真剣に検討 |
| モバイルネイティブのUXが本質(ジェスチャ、アニメーション) | ネイティブまたはFlutter |
まとめ: Tauriモバイルは「デスクトップアプリのモバイル同伴者」としては優秀、「モバイル優先アプリの基盤」としてはまだReact Native / Flutterほど熟成していない。
7章 · Electron · Wails · Neutralino · ネイティブ — 比較マトリクス
各ツールの居場所を整理する。
| 項目 | Tauri 2 | Electron | Wails | Neutralino | ネイティブ |
|---|---|---|---|---|---|
| バックエンド言語 | Rust | Node.js | Go | C/C++(コア) | Swift/Kotlin/C++/C# |
| レンダリング | システムWebView | Chromium 同梱 | システムWebView | システムWebView | ネイティブUI |
| バンドルサイズ | 5〜40MB | 80〜200MB | 10〜25MB | 2〜10MB | 1〜20MB |
| メモリアイドル | 30〜80MB | 200〜400MB | 50〜100MB | 30〜70MB | 20〜80MB |
| モバイル | iOS/Android | なし | なし | なし | 第一級 |
| レンダリング一貫性 | 低(OS依存) | 高 | 低 | 低 | 高 |
| 学習曲線 | 高(Rust) | 低(JS) | 中(Go) | 低 | 最高 |
| セキュリティモデル | capabilities(宣言的) | コードパターン | メソッドバインド | allowList | OS API |
| 成熟度 | 高(2024 GA) | 非常に高 | 中 | 中 | 最高 |
| エコシステム | 急成長 | 巨大 | 小 | 小 | OS依存 |
| 後援 | 財団(独立) | OpenJS | コミュニティ | コミュニティ | プラットフォーム提供者 |
各ツールの一行サマリ
- Tauri 2: 軽量で安全なデスクトップ・モバイルアプリ。Rustを学ぶ意志があるならベスト。
- Electron: 最も広いエコシステム、一貫したレンダリング。重量を受け入れる代わりに素早い開発。
- Wails: Goチームの自然な選択。Goのシンプルさをそのままデスクトップに持ってくる。ただし一部機能(マルチウィンドウなど)はまだ進行中。
- Neutralino: 超軽量。コンパイル不要でNodeに近いAPI。最も軽いが最も未成熟。
- ネイティブ: プラットフォームごとに一チーム。最高のUX、最も高価。
8章 · Rust学習曲線 — フロントエンドチームの本当のコスト
Tauriの最大の参入障壁はバンドルサイズでもシステムWebViewでもない。Rustだ。フロントエンド中心のチームにRustを導入するコストを正直に評価しよう。
よい知らせ
- Tauriは「フロントは普段通り + バックエンドだけRust」という構造だ。バックエンドコードが少ない単純なアプリなら、Rustコードは数個のIPCコマンドとmain.rs程度で済む
- Tauri CLIがボイラープレートの約90%を生成してくれる — Rustのビルドシステム(cargo)、クロスコンパイル、コード署名設定の大半が抽象化されている
- ChatGPT/Claudeなどのツールがコード生成を上手にこなす。単純なIPCコマンドはほぼAIが書いてくれる
- バックエンドが単純であれば、Rustの難しい部分(ライフタイム、深い非同期、unsafe)にほとんど触れない
よくない知らせ
- バックエンドが複雑になった瞬間 — DBコネクションプール、非同期処理、並行性 — Rustの学習曲線が急になる
- コンパイル時間が長い。初回ビルド5〜15分、増分ビルド5〜30秒。Electronは即時リロード
- エラーメッセージは親切だが量が多い。JSの一行エラーがRustでは30行のトレイト境界の説明になり得る
- チーム内でRustコードをレビューできる人が一人だけだと、その人がボトルネックになる
- 依存(crate)のセキュリティ監査。
cargo auditはあるが、JSのnpm周辺よりツールは少なく、一部のcrateはunsafeを多用する
現実的な判断基準
| チームの状況 | 推奨 |
|---|---|
| フロント専任、Rust経験なし、バックエンドが単純 | Tauriを試す価値あり |
| フロント専任、バックエンドが複雑(DB・並行性) | Electron + Node か、Tauri + サイドカー |
| Rust経験のあるメンバーが一人以上 | Tauri強く推奨 |
| システムプログラミング背景あり | Tauriが自然に合う |
| 6ヶ月以内に出荷、スケジュール厳しい | Electron — すべて検証済み |
興味深いパターン: 多くのチームがサイドカーパターンで折衷する。Tauriメインは軽く保ち、重いバックエンドロジック(例: Python機械学習、Nodeサービス)は別プロセスとして起動しTauriに管理させる。これでRust学習コストを最小化しつつ、Tauriのバンドル・メモリの利点は確保できる。
まとめ: フロントエンドチームにとってRustは本当のコストだ。バックエンド複雑度が低ければ耐えられるし、高ければ別のツールの方がよい。
9章 · 実際にTauri 2で作られたアプリ
2026年時点でTauri 2でリリースされた注目のアプリ。名前とカテゴリ、そしてなぜTauriを選んだかの推測。
| アプリ | カテゴリ | なぜTauriか(推測) |
|---|---|---|
| SilentKeys | プライバシー優先の音声書き起こし | オンデバイスML、小さなバンドル、システム統合 |
| Voxly | AI音声書き起こし | 高速起動、低メモリ、システムトレイ統合 |
| Watson.ai | 会議の録音・抽出 | バックグラウンド動作、システム権限のきめ細かい制御 |
| Pake | Webページをデスクトップアプリへ | 小さなバンドルが核心の価値(Electronより明確な勝ち) |
| Rivet | AIエージェントのビジュアルプログラミング | 重いIDE的UX、メモリ節約が重要 |
| XGetter | 動画・音声ダウンローダ | システム統合、小さなインストーラ |
| Kunobi / Kubeli | Kubernetesデスクトップクライアント | 開発者ツール、高速起動、MCPサーバー内蔵 |
共通パターンが見える:
- 新規プロジェクト — 既存のElectronアプリの移植ではなく、最初からTauriを選択
- 小〜中規模 — 巨大IDEではなく、単一目的が明確なアプリ
- システム統合が重要 — トレイ、バックグラウンド、グローバルショートカット、システム権限
- メモリに敏感 — バックグラウンドで動き続けるアプリ(書き起こし、会議録音など)
- 開発者やパワーユーザー対象 — ユーザーのLinux比率がある程度ある
逆に移行事例が少ない理由:
- 既存Electronアプリのコード資産が大きい — Rustでバックエンドを書き直すコスト
- ユーザーがすでに満足している — 壊れていないものを直すな
- Linuxユーザー比率が低ければシステムWebViewの差を気にしなくてよい
まとめ: Tauriは「新規プロジェクトで意識的に選択される」ケースが圧倒的だ。既存Electronアプリからの移行は強い動機がある場合のみ正当化される。
10章 · いつ何を選ぶか — 意思決定ツリー
スタート:
│
├─ モバイルが優先か?
│ ├─ はい → React Native / Flutter / ネイティブ
│ └─ いいえ → 次へ
│
├─ Linuxを真剣にサポートすべきか?
│ ├─ はい、Linuxユーザー30%以上 → システムWebViewの差を許容できるか?
│ │ ├─ はい → Tauri / Wails
│ │ └─ いいえ、ピクセル一貫性が必要 → Electron
│ └─ いいえ、少ない → 次へ
│
├─ チームにRust経験があるか?
│ ├─ はい、または学ぶ意志あり → Tauri強く推奨
│ ├─ Go経験あり → Wails検討
│ └─ JSだけ、学ぶ時間なし → Electron
│
├─ バンドルサイズ・メモリが本当に重要か?
│ ├─ はい(バックグラウンドアプリ、低スペック端末) → Tauri
│ └─ いいえ、ユーザーは気にしない → Electron が速い
│
├─ バックエンドロジックが複雑か?
│ ├─ はい、DB・並行性多い → Electron か、Tauri + サイドカー
│ └─ いいえ、主にUI → Tauri か Neutralino
│
└─ スケジュールが厳しいか?(3〜6ヶ月)
├─ はい → Electron(すべて検証済み)
└─ いいえ、1年以上 → Tauri学習コストが回収可能
簡単なルール
- 新規プロジェクト、単純なバックエンド、バンドル重要 → Tauri
- 既存Electronアプリ、ユーザー満足 → そのまま維持
- AAA級デスクトップUX、Linuxが第一級市民 → Electron
- デスクトップ + モバイルのコード共有が重要 → Tauri か Flutter
- Goチーム → Wails
- 1MBでも削りたい → Neutralino(ただし未成熟)
- OSごとに最高のUX、人員無限 → ネイティブ
11章 · アンチパターンと落とし穴
Tauriを選んで後悔するパターン。
「Electronのように書けばよい」
- 症状: Rustバックエンドを巨大な単一ファイルにして、すべてのIPCコマンドをそこに詰める
- 結果: コンパイル時間爆発、変更コスト増大、テスト不可
- 代わりに: Rustモジュールでドメインを分離(
commands/、services/、models/)、コマンドは薄く(検証 + サービス呼び出し)
「capabilityを全部オンに」
- 症状:
core:defaultに加えて、すべてのfs/shell/dialog権限をワイルドカードでオン - 結果: セキュリティモデルの核心価値を捨てる。Electronのリスクをそのまま受け取る
- 代わりに: 本当に必要なものだけ、スコープを狭く。ウィンドウ別にcapabilityを分離
「最初からRust非同期を深く」
- 症状: 単純なIPCを
tokio::spawnとチャンネル6個で実装 - 結果: 学習曲線爆発、デッドロック、コンパイルエラーの沼
- 代わりに: 最初は
async fnとTauriのデフォルトランタイム。非同期が本当に必要なときだけ深く
「システムWebViewの差を無視」
- 症状: macOSだけで開発、リリース直前にLinuxテスト
- 結果: WebKitGTKでUIの半分が壊れる
- 代わりに: 最初の週から3 OSのCIでビルド、主要画面はすべてのOSでスクリーンショット比較
「フロントもRustで書く」
- 症状: Leptos/Yew/Dioxusでフロントまでもすべてに
- 結果: Rust学習曲線が二倍、エコシステムはReact/Vueの百分の一
- 代わりに: フロントは普段のツール(React/Vue/Svelte)、バックエンドだけRust。Tauriの設計はそうなっている
「AIエージェントにTauriアプリを丸ごと作らせる」
- 症状: エージェントがcapability設定とRust IPCパターンを甘く作り、セキュリティホール
- 結果: 権限が広すぎる、入力検証が抜ける、サイドチャネルが開く
- 代わりに: capabilityとIPCコマンドは人がレビュー、単体テストはIPC境界の境界条件を明示的に強制
12章 · 移行 — ElectronからTauriへ移すなら
移行が正当化されるシナリオがあるなら、手順は次のようになる。
1. 計測: 現在のElectronアプリのバンドル、メモリ、起動時間を記録
2. バックエンド棚卸し: Node依存をすべて列挙 — どれがRust crateに置き換わるか?
3. フロント分離: UIはそのまま持っていけるか(ほとんどの場合可能)
4. IPCマッピング: 現在のipcMainハンドラを一つずつTauriコマンドにマップ
5. capability設計: 各IPCが何にアクセスするかを整理 → capabilitiesファイル
6. システム統合: トレイ/通知/ショートカットをTauriプラグインで再実装
7. クロスOSテスト: 初日から3 OSでビルド、視覚回帰テスト
8. 計測再確認: 約束された数値(メモリ/バンドル)が実際に出るか検証
9. ベータユーザー → 段階的リリース
移行すべきでないシグナル
- Node依存にRust代替がないものが多い(特定の機械学習ライブラリなど)
- ユーザーの70%がmacOS、システムWebViewの差はそもそも問題にならず、メモリも満足
- チームにRustが書ける人が0人、学ぶ6ヶ月もない
- アプリがユーザーにとって十分速く、バンドルサイズへの不満がない
移行が正当化されるシグナル
- バックグラウンドで24/7動くアプリで、メモリが実際にユーザーマシンに負担
- インストーラサイズがダウンロード転換率に影響(低スペック/低帯域市場)
- セキュリティ監査を通る必要があり、Electronのセキュリティパターンを整理する方が高くつく
- モバイル展開予定があり、デスクトップコードを再利用したい
エピローグ — チェックリスト、アンチパターン、次回予告
Tauri 2は「Electronより軽い何か」ではなく、別のトレードオフを選んだ別の道具だ。システムWebViewの影、Rust学習曲線、小さなエコシステムを受け入れて — バンドル・メモリ・セキュリティモデルの利点を得る。新規プロジェクト、単純なバックエンド、バンドルが重要な状況なら合理的な選択だ。既存Electronアプリは強い動機がなければ移さなくてよい。
Tauri選択チェックリスト
- 新規プロジェクトか、既存の移行か? — 新規なら参入コストが正当化される
- バックエンドの複雑度は? — 単純なほどTauriが自然
- モバイル計画があるか? — あればTauri 2のモバイル対応が魅力
- チームのRust経験は? — 0人なら学習期間をスケジュールに足す必要
- Linuxユーザー比率は? — 高ければシステムWebView QAコストを計画に含める
- バンドル・メモリが本当に重要か? — バックグラウンドアプリ、低スペック端末対象なら利点
- スケジュールの余裕は? — 厳しければElectron、1年以上ならTauri学習回収可能
- capability設計を早めに始めたか? — セキュリティモデルがコード構造を左右する
- サイドカーパターンが必要か? — 重いバックエンドは別プロセスで分離
- 3 OSのCIを最初の週に組んだか? — 最後の週で発見すると遅い
アンチパターン
| アンチパターン | なぜ悪いか | 代わりに |
|---|---|---|
| バンドルサイズだけでTauri選択 | Rust学習コストとQAコストを無視 | チームとスケジュールも評価 |
| Electronコードをそのまま1:1ポート | Rustのイディオムが違い不自然 | ドメインから再設計 |
| capabilityをワイルドカードでオン | セキュリティモデルの価値消滅 | 本当に必要な権限だけ、スコープ狭く |
| Linuxテストを最後に | リリース直前に発見 | 最初の週から3 OS CI |
| フロントもRustで(Leptosなど) | エコシステム百分の一 | フロントはReact/Vue、バックエンドだけRust |
| 巨大なcommands.rs一つ | コンパイル時間爆発 | ドメイン別モジュールに分割 |
| 最初からRust非同期を深く | 学習曲線の崖 | 同期から、必要なときだけ非同期 |
| システムWebViewの一貫性を仮定 | macOSだけ見てリリース | CSS・JS機能はフォールバック + 全OSの視覚回帰 |
| モバイルをReact Native代替と期待 | モバイル優先UXまだ不足 | デスクトップの伴侶として使う |
| ROIなしに移行強行 | コード資産無駄、ユーザー影響 | 測定可能な勝ちがあるときだけ |
次回予告
次回は**「Rust非同期の落とし穴 — async/await、tokio、Sendトレイト、そしてライフタイムが交わる場所」**。今回が「Tauriを選ぶか」の記事なら、次回は「Tauriバックエンドが複雑になり始めると何に出会うか」の記事だ。Rust非同期は強力だが落とし穴が多い — Send/Syncトレイト境界、ライフタイムと非同期の相互作用、チャンネル選択(mpsc/oneshot/broadcast)、selectパターン、そして非同期コンテキストから同期コードを呼ぶ方法まで。Tauriバックエンドでも、バックエンドマイクロサービスでも、Rust非同期を真剣に使うあらゆる場所に適用される。
参考 / References
- Tauri 2.0 Stable Release 発表
- Tauri 公式サイト
- Tauri アーキテクチャドキュメント
- Tauri セキュリティモデル
- Tauri Capabilities リファレンス
- Tauri Permissions ガイド
- Tauri モバイル Alpha 発表
- Tauri GitHub リポジトリ
- Awesome Tauri — アプリ・プラグイン索引
- Made with Tauri — 事例ショーケース
- Electron 公式サイト
- Wails 公式サイト
- Wails と Tauri 比較ディスカッション
- Neutralino.js 公式
- Tauri 1.3 — 初期allowlist解説
- Roadmap to Tauri 2.0
- Tauri vs Electron — 性能とトレードオフ(Hopp)
- Tauri 隔離パターン
- Evil Martians — Tauri サイドカーパターン