第6章 実践:Tauri + Vite によるデスクトップアプリ開発
Web フロントエンド技術(HTML / CSS / JavaScript)と Rust を組み合わせることで、洗練された UI と圧倒的なパフォーマンス・安全性を両立したデスクトップアプリケーションを開発できます。
本章では、軽量デスクトップアプリフレームワーク「Tauri」と、超高速フロントエンドビルドツール「Vite」を用いて、システム情報を取得・表示するデスクトップ Web アプリケーションを一から作成します。
6.1 Tauri の特徴とアーキテクチャ
デスクトップアプリを Web 技術で構築する代表格として Electron がありますが、Tauri には Electron と比較して際立った優位性があります。
Electron と Tauri の違い
-
メモリとバイナリサイズ: Electron は Chromium ブラウザと Node.js ランタイムをまるごとアプリ内に同梱するため、配布サイズが 100MB を超え、メモリ消費量も大きくなります。対して Tauri は、OS 標準の Webview(Windows なら WebView2、macOS なら WebKit、Linux なら WebKitGTK)を利用し、バックエンドを Rust でネイティブコンパイルするため、配布サイズが数 MB 〜 十数 MB 程度と極めて軽量で省メモリです。
-
セキュリティと堅牢性: フロントエンドの JavaScript がローカルマシンに直接アクセスするのを厳格に制限し、Rust 側の安全なコマンド経由(IPC 通信)でのみシステム操作を許可する強固なセキュリティ設計となっています。
graph LR
subgraph Frontend["フロントエンド (Webview)"]
UI["UI (Vite + TypeScript)<br/>HTML / CSS / JS"]
INVOKE["invoke()"]
end
subgraph Backend["バックエンド (Rust)"]
CMD["#[tauri::command]<br/>Rust 関数"]
OS["OS 機能 / ハードウェア / ファイル"]
end
UI --> INVOKE
INVOKE -->|IPC 通信 (JSON)| CMD
CMD --> OS
6.2 開発環境のセットアップとプロジェクト作成
開発には、Rust ツールチェーン(rustc, cargo)に加えて、Node.js(npm / pnpm / yarn)が必要です。
公式のセットアップツール create-tauri-app を利用してプロジェクトの雛形を生成します。
npm create tauri-app@latest
対話形式のプロンプトでは以下を選択します。
- Project name:
sys-monitor - Choose which language to use:
TypeScript / JavaScript - Choose your package manager:
npm - Choose your UI template:
VanillaまたはReact(ここでは構造が分かりやすいVanillaを選択) - Choose your UI flavor:
TypeScript
ディレクトリ構成
プロジェクトが生成されると、以下のような構成になります。
sys-monitor/
├── index.html # フロントエンドの起点
├── package.json # フロントエンドの依存管理
├── src/ # フロントエンドの TypeScript/CSS コード
│ ├── main.ts
│ └── styles.css
├── src-tauri/ # Rust バックエンドのコードと設定
│ ├── Cargo.toml # Rust の依存管理
│ ├── tauri.conf.json # ウィンドウサイズや権限の設定
│ └── src/
│ ├── lib.rs # コマンド定義とロジック
│ └── main.rs # エントリーポイント
└── vite.config.ts # Vite のビルド設定
6.3 Rust バックエンドの実装(Tauri コマンドの定義)
まずは Rust 側で、PC のシステム情報(OS 名、CPU アーキテクチャ、メモリ情報など)を取得する関数と、テキストファイルを安全に保存するコマンドを定義します。
Cargo.toml の編集
src-tauri/Cargo.toml を開き、システム情報取得用クレート sysinfo を追加します。
[dependencies]
tauri = { version = "2.0", features = [] }
tauri-plugin-opener = "2.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sysinfo = "0.31"
コマンドの実装(src-tauri/src/lib.rs)
#[tauri::command] 属性を付与した関数は、フロントエンドから IPC(プロセス間通信)経由で非同期に呼び出せるようになります。
#![allow(unused)]
fn main() {
use serde::{Deserialize, Serialize};
use sysinfo::System;
#[derive(Serialize, Deserialize)]
pub struct SystemStats {
os_name: String,
total_memory_mb: u64,
used_memory_mb: u64,
cpu_count: usize,
}
// システム情報を取得して返す Tauri コマンド
#[tauri::command]
fn get_system_stats() -> SystemStats {
let mut sys = System::new_all();
sys.refresh_all();
let os_name = System::name().unwrap_or_else(|| "Unknown OS".to_string());
let total_memory_mb = sys.total_memory() / 1024 / 1024;
let used_memory_mb = sys.used_memory() / 1024 / 1024;
let cpu_count = sys.cpus().len();
SystemStats {
os_name,
total_memory_mb,
used_memory_mb,
cpu_count,
}
}
// ユーザー入力のテキストをローカルファイルに書き出すコマンド
#[tauri::command]
fn save_log_file(content: String) -> Result<String, String> {
use std::fs;
let file_path = "app_log.txt";
fs::write(file_path, content).map_err(|e| e.to_string())?;
Ok(format!("{} に正常に保存しました", file_path))
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![
get_system_stats,
save_log_file
])
.run(tauri::generate_context!())
.expect("Tauri アプリケーションの実行中にエラーが発生しました");
}
}
tauri::generate_handler![get_system_stats, save_log_file] に関数名を登録することで、フロントエンドからの呼び出しが可能になります。
6.4 フロントエンドの実装(Vite + TypeScript)
次に、Web フロントエンド側を実装します。
HTML の作成(index.html)
index.html にシンプルなダッシュボード画面を定義します。
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>System Monitor</title>
<link rel="stylesheet" href="/src/styles.css" />
</head>
<body>
<div class="container">
<h1>🦀 System Monitor (Tauri + Vite)</h1>
<div class="card">
<h2>システム稼働状況</h2>
<div id="stats-display">読み込み中...</div>
<button id="refresh-btn">状態を更新</button>
</div>
<div class="card">
<h2>ログメモ保存</h2>
<textarea id="log-input" placeholder="保存したいメモを入力..."></textarea>
<button id="save-btn">ファイルへ保存</button>
<p id="save-status"></p>
</div>
</div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
スタイリング(src/styles.css)
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background-color: #f4f6f8;
color: #333;
margin: 0;
padding: 20px;
}
.container {
max-width: 600px;
margin: 0 auto;
}
h1 {
font-size: 1.5rem;
color: #1f2937;
text-align: center;
}
.card {
background: white;
border-radius: 8px;
padding: 16px 20px;
margin-bottom: 16px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
button {
background-color: #2563eb;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
}
button:hover {
background-color: #1d4ed8;
}
textarea {
width: 100%;
height: 80px;
margin-top: 8px;
margin-bottom: 8px;
box-sizing: border-box;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
フロントエンドロジック(src/main.ts)
Tauri 公式の @tauri-apps/api/core パッケージに含まれる invoke 関数を使用して、Rust コマンドを呼び出します。
import { invoke } from "@tauri-apps/api/core";
interface SystemStats {
os_name: String;
total_memory_mb: number;
used_memory_mb: number;
cpu_count: number;
}
const statsDisplay = document.getElementById("stats-display") as HTMLDivElement;
const refreshBtn = document.getElementById("refresh-btn") as HTMLButtonElement;
const logInput = document.getElementById("log-input") as HTMLTextAreaElement;
const saveBtn = document.getElementById("save-btn") as HTMLButtonElement;
const saveStatus = document.getElementById("save-status") as HTMLParagraphElement;
// Rust コマンドを呼び出してシステム情報を取得
async function fetchStats() {
try {
statsDisplay.innerText = "情報取得中...";
const stats = await invoke<SystemStats>("get_system_stats");
statsDisplay.innerHTML = `
<ul>
<li><strong>OS:</strong> ${stats.os_name}</li>
<li><strong>CPU コア数:</strong> ${stats.cpu_count} コア</li>
<li><strong>メモリ使用量:</strong> ${stats.used_memory_mb} MB / ${stats.total_memory_mb} MB</li>
</ul>
`;
} catch (error) {
statsDisplay.innerText = `エラーが発生しました: ${error}`;
}
}
// ログ保存コマンドを呼び出し
async function saveLog() {
const content = logInput.value;
if (!content) return;
try {
const result = await invoke<string>("save_log_file", { content });
saveStatus.innerText = result;
saveStatus.style.color = "green";
} catch (error) {
saveStatus.innerText = `保存失敗: ${error}`;
saveStatus.style.color = "red";
}
}
refreshBtn.addEventListener("click", fetchStats);
saveBtn.addEventListener("click", saveLog);
// 初回読み込み実行
fetchStats();
6.5 アプリの動作確認と配布パッケージのビルド
開発用サーバーでの実行
プロジェクトのルートディレクトリで以下のコマンドを実行します。
npm run tauri dev
このコマンドを実行すると、Vite の高速なホットモジュールリプレース(HMR)が起動した上で、ローカルデスクトップウィンドウが立ち上がります。フロントエンドコードを変更すると、再ビルドを待つことなく即座に画面が自動更新されます。
本番用インストーラーのビルド
配布用アプリケーションをパッケージングする際は、以下のコマンドを実行します。
npm run tauri build
コンパイルが完了すると、src-tauri/target/release/bundle/ 配下に実行ファイルおよび OS に合わせたインストーラー(Windows では .msi や .exe、macOS では .dmg や .app、Linux では .deb や AppImage)が自動生成されます。
わずか数十 MB の軽量な単一インストーラーとして配布でき、ユーザーは追加の Node.js などをインストールすることなく即座に利用できます。