Tauriの基礎 - プロジェクト構造
概要
Tauriは、RustバックエンドとWebフロントエンドを組み合わせてデスクトップアプリケーションを開発するためのフレームワークである。
フロントエンドにはReact、Vue、Svelte等の任意のフレームワークを使用でき、バックエンドはRustで記述する。
Tauriプロジェクトは、フロントエンド用のディレクトリとRustバックエンド用の src-tauri/ ディレクトリで構成される。
この2層構造により、Web技術の柔軟性とRustのパフォーマンスを両立できる。
フロントエンドとバックエンドの通信には、Tauriが提供する >invoke API を使用する。
フロントエンドからRustのコマンドを呼び出し、その結果を受け取ることができる。
Tauri v2では、セキュリティ強化のために capabilities/ ディレクトリで権限管理を行う。
アプリケーションが必要とする機能のみを明示的に許可することにより、セキュリティリスクを最小限に抑える。
主な特徴は以下の通りである。
- Rustバックエンドによる高速で安全な処理
- Webフロントエンドによる柔軟なUI開発
- クロスプラットフォーム対応 (Windows、MacOS、Linux)
- 小さなバイナリサイズと低いメモリ消費
- Tauri v2による強化された権限管理システム
ディレクトリ構成
Tauriプロジェクトの標準的なディレクトリ構成を以下に示す。
.
├── package.json
├── index.html
├── vite.config.ts
├── node_modules/
├── dist/
├── src/
│ ├── main.tsx
│ ├── App.tsx
│ └── components/
└── src-tauri/
├── Cargo.toml
├── build.rs
├── tauri.conf.json
├── src/
│ ├── main.rs
│ ├── lib.rs
│ └── commands.rs
├── icons/
└── capabilities/
└── default.json
ディレクトリの役割
- src/
- フロントエンドのソースコードを配置するディレクトリ
- src-tauri/
- Rustバックエンドのプロジェクトディレクトリ (Cargoパッケージとして管理)
- src-tauri/capabilities/
- Tauri v2の権限設定ファイルを配置するディレクトリ
- src-tauri/icons/
- アプリケーションアイコンを配置するディレクトリ
自動生成ディレクトリ
- node_modules/
- npmパッケージのインストール先 (
npm installコマンドで自動生成)
- npmパッケージのインストール先 (
- dist/
- フロントエンドのビルド成果物 (
npm run buildコマンドで生成)
- フロントエンドのビルド成果物 (
- src-tauri/target/
- Rustのビルド成果物
- (デバッグ: target/debug/、リリース: target/release/)
主要ファイルの役割 (フロントエンド)
main.tsx
main.tsx は、Reactアプリケーションのエントリーポイントである。
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode><App /></React.StrictMode>,
);
App.tsx
App.tsx は、アプリケーションのメインコンポーネントである。
invoke 関数を使用して、Rustバックエンドのコマンドを呼び出す。
import { useState } from "react";
import { invoke } from "@tauri-apps/api/core";
function App() {
const [greeting, setGreeting] = useState("");
async function greet(name: string) {
try {
const result = await invoke<string>("greet", { name });
setGreeting(result);
} catch (err) { console.error("Error:", err); }
}
return (
<div>
<button onClick={() => greet("World")}>Greet</button>
<p>{greeting}</p>
</div>
);
}
export default App;
invoke関数
invoke 関数は、フロントエンドからRustバックエンドのコマンドを呼び出すためのAPIである。
@tauri-apps/api/core からインポートして使用する。
- 第1引数: Rust側で定義したコマンド名 (文字列)
- 第2引数: コマンドに渡す引数 (オブジェクト形式、省略可能)
- 戻り値: Rustコマンドの実行結果を含むPromise
エラーハンドリング例:
async function callRustCommand() {
try {
const result = await invoke<string>("get_version");
console.log("Version:", result);
} catch (error) { console.error("Command failed:", error); }
}
index.html
index.html は、アプリケーションのHTMLエントリーポイントである。
<!DOCTYPE html>
<html lang="ja">
<head><meta charset="UTF-8" /><title>Tauri App</title></head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
vite.config.ts
vite.config.ts は、Viteの設定ファイルである。
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
clearScreen: false,
server: { port: 1420, strictPort: true },
});
主要ファイルの役割 (バックエンド)
main.rs
main.rs は、Rustアプリケーションのエントリーポイントである。
実際の処理は lib.rs に委譲する。
#![cfg_attr(all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows")]
fn main() { app_lib::run() }
Windows環境では、リリースビルド時にコンソールウィンドウを表示しないように設定している。
lib.rs
lib.rs は、Tauriアプリケーションのメインロジックを記述するファイルである。
#![cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet, get_version])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
#[tauri::command]
fn greet(name: &str) -> String { format!("Hello, {}!", name) }
コマンド定義
Tauriでフロントエンドから呼び出し可能なコマンドを定義するには、#[tauri::command] アトリビュートを使用する。
基本的なコマンドと戻り値
#[tauri::command]
fn simple_command() { println!("Command called"); }
#[tauri::command]
fn greet(name: &str) -> String { format!("Hello, {}!", name) }
非同期コマンド (async)
#[tauri::command]
async fn fetch_data(url: String) -> Result<String, String> {
let response = reqwest::get(&url).await.map_err(|e| e.to_string())?;
response.text().await.map_err(|e| e.to_string())
}
引数の命名規則 (rename_all)
Rustのsnake_caseとJavaScriptのcamelCaseの違いを吸収する。
#[tauri::command]
fn process_data(#[serde(rename = "userName")] user_name: String) -> String {
format!("User: {}", user_name)
}
エラーハンドリング (Result型)
#[tauri::command]
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 { return Err("Division by zero".to_string()); }
Ok(a / b)
}
build.rs
build.rs は、ビルド時に実行されるスクリプトである。
fn main() { tauri_build::build() }
別モジュールへのコマンド定義
コマンドが増えてくると、lib.rs が肥大化する。
コマンドを別のモジュールに分割して管理する方法を説明する。
commands.rsの作成
// src-tauri/src/commands.rs
#[tauri::command]
pub fn greet(name: &str) -> String { format!("Hello, {}!", name) }
#[tauri::command]
pub fn get_version() -> String { env!("CARGO_PKG_VERSION").to_string() }
lib.rsでのインポート
// src-tauri/src/lib.rs
mod commands;
#![cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![commands::greet, commands::get_version])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
状態管理
Tauriでは、アプリケーション全体で共有される状態を管理できる。
tauri::State を使用して、複数のコマンド間でデータを共有する。
use std::sync::Mutex;
use tauri::State;
struct AppState { counter: Mutex<i32> }
#[tauri::command]
fn get_counter(state: State<AppState>) -> i32 {
*state.counter.lock().unwrap()
}
#[tauri::command]
fn increment_counter(state: State<AppState>) -> i32 {
let mut counter = state.counter.lock().unwrap();
*counter += 1;
*counter
}
pub fn run() {
tauri::Builder::default()
.manage(AppState { counter: Mutex::new(0) })
.invoke_handler(tauri::generate_handler![get_counter, increment_counter])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
設定ファイル
Cargo.toml
Cargo.toml ファイルは、Rustプロジェクトの設定ファイルである。
[package]
name = "my-tauri-app"
version = "0.1.0"
edition = "2021"
[build-dependencies]
tauri-build = { version = "2.0.0" }
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "2.0.0", features = [] }
[profile.release]
panic = "abort"
lto = true
opt-level = "s"
strip = true
| 設定項目 | 説明 |
|---|---|
[package] |
パッケージの基本情報 |
[build-dependencies] |
ビルド時に必要な依存クレート |
[dependencies] |
実行時に必要な依存クレート |
[profile.release] |
リリースビルドの最適化設定 |
package.json
package.json ファイルは、Node.jsプロジェクトの設定ファイルである。
{
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"tauri": "tauri"
},
"dependencies": {
"@tauri-apps/api": "^2.0.0",
"react": "^18.2.0"
},
"devDependencies": {
"@tauri-apps/cli": "^2.0.0",
"typescript": "^5.0.0",
"vite": "^5.0.0"
}
}
tauri.conf.json
tauri.conf.json ファイルは、Tauriアプリケーションの設定ファイルである。
{
"productName": "My Tauri App",
"version": "0.1.0",
"identifier": "com.example.myapp",
"build": {
"beforeDevCommand": "npm run dev",
"devUrl": "http://localhost:1420",
"frontendDist": "../dist"
},
"app": { "windows": [{ "title": "My Tauri App", "width": 800, "height": 600 }] }
}
capabilities/ディレクトリ
capabilities/ ディレクトリは、Tauri v2で導入された権限管理システムの設定ファイルを配置する。
Tauri v2権限システム
Tauri v2では、セキュリティ強化のためにcapabilities (権限) システムが導入された。
アプリケーションが必要とする機能のみを明示的に許可することにより、セキュリティリスクを最小限に抑える。
- プリンシパル (主体)
- 権限を付与する対象 (ウィンドウ、Webビュー等)
- 権限 (Permission)
- 特定の機能へのアクセス許可
- ケイパビリティ (Capability)
- プリンシパルと権限の組み合わせ
設定例
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main-capability",
"windows": ["main"],
"permissions": [
"core:path:default",
"core:event:default",
"core:window:default",
"core:app:default",
"shell:allow-open"
]
}
| 権限 | 説明 |
|---|---|
| core:path:default | ファイルパス操作の基本権限 |
| core:event:default | イベントシステムの基本権限 |
| core:window:default | ウィンドウ操作の基本権限 |
| core:app:default | アプリケーション情報へのアクセス権限 |
| shell:allow-open | 外部プログラムの実行許可 |
| dialog:default | ファイルダイアログの基本権限 |
| fs:default | ファイルシステムの基本権限 |
プラットフォーム固有のcapability
{
"identifier": "platform-capability",
"windows": ["main"],
"permissions": [
{ "identifier": "shell:allow-open", "platforms": ["macOS", "windows"] }
]
}
icons/ディレクトリ
icons/ ディレクトリには、アプリケーションのアイコンファイルを配置する。
| ファイル名 | 用途 | 対応プラットフォーム |
|---|---|---|
| 32x32.png | 小サイズアイコン | 全プラットフォーム |
| 128x128.png | 中サイズアイコン | 全プラットフォーム |
| icon.icns | MacOS用アイコン | MacOS |
| icon.ico | Windows用アイコン | Windows |
アイコン生成コマンド
Tauri CLIを使用して、ソース画像から各プラットフォーム用のアイコンを一括生成できる。
npm run tauri icon /path/to/source-icon.png
実用例
フロントエンドからRustコマンドを呼び出す例を示す。
コンポーネントからのRustコマンド呼び出し
import { useState, useEffect } from "react";
import { invoke } from "@tauri-apps/api/core";
interface FileInfo { name: string; size: number; }
function FileList() {
const [files, setFiles] = useState<FileInfo[]>([]);
const [loading, setLoading] = useState(false);
async function loadFiles(directory: string) {
setLoading(true);
try {
const result = await invoke<FileInfo[]>("list_files", { directory });
setFiles(result);
} finally { setLoading(false); }
}
useEffect(() => { loadFiles("."); }, []);
return (
<div>
<h2>File List</h2>
{loading && <p>Loading...</p>}
<ul>
{files.map((file) => (
<li key={file.name}>{file.name} ({file.size} bytes)</li>
))}
</ul>
</div>
);
}
export default FileList;
状態管理の例
Rust側でアプリケーション状態を管理する例を示す。
use std::sync::Mutex;
use tauri::State;
#[derive(Clone, serde::Serialize)]
pub struct Todo { id: u32, text: String, completed: bool }
pub struct TodoStore { todos: Mutex<Vec<Todo>> }
#[tauri::command]
pub fn get_todos(store: State<TodoStore>) -> Vec<Todo> {
store.todos.lock().unwrap().clone()
}
#[tauri::command]
pub fn add_todo(text: String, store: State<TodoStore>) -> Todo {
let mut todos = store.todos.lock().unwrap();
let id = todos.len() as u32 + 1;
let todo = Todo { id, text, completed: false };
todos.push(todo.clone());
todo
}