Tauri で OS ネイティブ API にアクセスする方法

最近の Web 技術の進歩により、デスクトップアプリ開発の選択肢が大幅に広がっています。その中でも特に注目されているのが Tauri です。Tauri は軽量でセキュアなデスクトップアプリケーション開発フレームワークとして、多くの開発者から支持を集めています。
本記事では、Tauri でアクセスできる OS ネイティブ API を機能別に分類し、各 API の使い方と実装例を詳しく解説いたします。Web 技術の知識を活かしながら、本格的なデスクトップアプリケーションを作成したい方にとって、実践的な内容となっております。
背景
Tauri のアーキテクチャ
Tauri は現代的なデスクトップアプリ開発において、革新的なアプローチを採用しています。従来の Electron とは異なり、軽量性とセキュリティを重視した設計となっております。
以下の図は、Tauri アプリケーションの基本的なアーキテクチャを示しています。
mermaidclassDiagram
class TauriApp {
+Frontend: React/Vue/Svelte
+Backend: Rust
+Bridge: Tauri Commands
}
class OSAPIs {
+FileSystem
+Notifications
+Clipboard
+Dialog
+Window
+Shell
}
TauriApp --> OSAPIs : access via Commands
この図からわかるように、Tauri アプリケーションはフロントエンド(Web 技術)とバックエンド(Rust)が明確に分離されており、Tauri Commands がその橋渡しを担っています。
Frontend - Backend 分離アーキテクチャ
Tauri の最大の特徴は、フロントエンドとバックエンドの完全な分離です。フロントエンド側では慣れ親しんだ React、Vue、Svelte などのフレームワークを使用できます。一方、バックエンド側は Rust で記述され、高いパフォーマンスとメモリ安全性を提供します。
この分離により、開発者は各層の特性を最大限に活用できるのです。Web 技術の柔軟性と Rust の堅牢性を同時に享受できるのは、他のフレームワークにはない大きなメリットといえるでしょう。
セキュリティモデル
Tauri は「ゼロトラスト」の原則に基づいたセキュリティモデルを採用しています。デフォルトでは、すべての API アクセスが無効になっており、開発者が明示的に許可したもののみが使用可能です。
セキュリティ機能の詳細について、以下の図で説明いたします。
mermaidflowchart TD
A[フロントエンド] -->|Command呼び出し| B[セキュリティレイヤー]
B -->|許可チェック| C{allowlist確認}
C -->|許可済み| D[Rust Command実行]
C -->|未許可| E[エラー返却]
D -->|結果| F[OSネイティブAPI]
F -->|レスポンス| A
E -->|エラー| A
この仕組みにより、意図しない API アクセスを防ぎ、アプリケーションの安全性を確保しています。
許可システム
許可システムはtauri.conf.json
ファイルで管理されます。各 API ごとに細かく設定でき、必要最小限の権限のみを付与することが可能です。これにより、セキュリティリスクを最小限に抑えながら、必要な機能を実装できます。
課題
従来の課題
Web 技術を使ったデスクトップアプリ開発には、いくつかの根本的な課題が存在していました。これらの課題を理解することで、Tauri がもたらす価値をより深く理解できるでしょう。
Web 技術だけでは実現できない機能
ブラウザベースの Web アプリケーションは、セキュリティ上の理由から多くの制限があります。ファイルシステムへの直接アクセス、システム通知の送信、クリップボードの操作など、デスクトップアプリケーションとして当然必要な機能が利用できませんでした。
従来の課題を整理すると以下のような構造になります。
mermaidstateDiagram-v2
[*] --> WebApp
WebApp --> 制限事項
制限事項 --> ファイルアクセス不可
制限事項 --> システム通知制限
制限事項 --> ハードウェア情報取得不可
制限事項 --> 外部プロセス実行不可
制限事項 --> セキュリティ制約
制限事項 --> パフォーマンス問題
これらの制限により、リッチなデスクトップ体験を提供することが困難でした。
セキュリティ制約
Web 技術の安全性は、同時に制約でもありました。Cross-Origin Resource Sharing(CORS)、Content Security Policy(CSP)などの仕組みにより、柔軟なアプリケーション開発が阻害されるケースが多々ありました。
パフォーマンスの問題
Electron などの既存ソリューションでは、Chromium エンジン全体をバンドルするため、アプリケーションサイズが大きくなり、メモリ使用量も多くなる傾向がありました。これは特に軽量なアプリケーションにとって深刻な問題でした。
Tauri でのアプローチと Mermaid 図解
Tauri はこれらの課題に対して、革新的なアプローチで解決策を提供しています。
Rust 層でのネイティブ API 呼び出し
Tauri の中核となるのは、Rust 層でのネイティブ API 呼び出し機能です。Rust の安全性とパフォーマンスを活かしながら、OS の機能に直接アクセスできます。
以下の図は、Tauri における API 呼び出しのフローを示しています。
mermaidsequenceDiagram
participant F as Frontend
participant TC as Tauri Command
participant R as Rust Backend
participant OS as OS API
F->>TC: invoke('file_read', {path})
TC->>R: Command実行
R->>OS: ファイル読み込み
OS->>R: ファイルデータ
R->>TC: 結果返却
TC->>F: Promise resolve
このフローにより、Web 技術の制約を超えた機能実装が可能になります。
JavaScript/TypeScript 層との連携
フロントエンド側では、Tauri API を通じて簡単にバックエンド機能を呼び出せます。TypeScript による型安全性も確保されており、開発効率が大幅に向上します。
解決策
Tauri Commands の基本
Tauri Commands は、フロントエンドとバックエンドを繋ぐ重要な仕組みです。この仕組みを理解することで、効率的なアプリケーション開発が可能になります。
Command 関数の定義
Rust 側で Command 関数を定義する基本的な方法をご紹介します。以下のコードは、シンプルなテキストファイル読み込み機能の実装例です。
rustuse tauri::command;
use std::fs;
#[command]
async fn read_text_file(path: String) -> Result<String, String> {
match fs::read_to_string(&path) {
Ok(content) => Ok(content),
Err(e) => Err(format!("ファイル読み込みエラー: {}", e))
}
}
この Command 関数では、ファイルパスを受け取り、テキストファイルの内容を返します。エラーハンドリングも適切に実装されており、安全な処理が保証されています。
次に、Command 関数を Tauri アプリケーションに登録する方法をご説明します。
rustfn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![read_text_file])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
JavaScript 側からの呼び出し
フロントエンド側では、invoke
関数を使用して Command 関数を呼び出します。以下は TypeScript での実装例です。
typescriptimport { invoke } from '@tauri-apps/api/tauri';
async function readFile(filePath: string): Promise<string> {
try {
const content = await invoke<string>('read_text_file', {
path: filePath,
});
return content;
} catch (error) {
console.error('ファイル読み込みに失敗しました:', error);
throw error;
}
}
この実装により、フロントエンド側から簡単にファイル操作が行えます。
データの受け渡し
複雑なデータ構造の受け渡しも可能です。以下は、ファイル情報を取得する Command の例です。
rustuse serde::{Deserialize, Serialize};
use std::fs::metadata;
#[derive(Serialize, Deserialize)]
struct FileInfo {
name: String,
size: u64,
is_directory: bool,
modified: u64,
}
#[command]
async fn get_file_info(path: String) -> Result<FileInfo, String> {
match metadata(&path) {
Ok(meta) => {
let modified = meta.modified()
.unwrap()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
Ok(FileInfo {
name: path.clone(),
size: meta.len(),
is_directory: meta.is_dir(),
modified,
})
}
Err(e) => Err(format!("ファイル情報取得エラー: {}", e))
}
}
設定とセットアップ
tauri.conf.json の設定
Tauri アプリケーションの動作は、tauri.conf.json
ファイルで詳細に制御できます。以下は基本的な設定例です。
json{
"build": {
"beforeDevCommand": "yarn dev",
"beforeBuildCommand": "yarn build",
"devPath": "http://localhost:3000",
"distDir": "../dist"
},
"package": {
"productName": "My Tauri App",
"version": "0.1.0"
},
"tauri": {
"allowlist": {
"all": false,
"fs": {
"all": false,
"readFile": true,
"writeFile": true,
"createDir": true
}
}
}
}
許可設定(allowlist)
セキュリティの観点から、使用する API を明示的に許可する必要があります。以下は主要な API 許可設定の例です。
json{
"tauri": {
"allowlist": {
"fs": {
"readFile": true,
"writeFile": true,
"readDir": true
},
"notification": {
"all": true
},
"clipboard": {
"all": true
},
"dialog": {
"open": true,
"save": true
}
}
}
}
ビルド設定
本番環境での最適化設定も重要です。以下はパフォーマンスを重視した設定例です。
json{
"tauri": {
"bundle": {
"active": true,
"targets": "all",
"identifier": "com.example.myapp",
"icon": ["icons/32x32.png", "icons/128x128.png"],
"resources": [],
"externalBin": [],
"copyright": "",
"category": "DeveloperTool",
"shortDescription": "",
"longDescription": ""
}
}
}
具体例
ファイル操作 API
ファイル操作は、デスクトップアプリケーションにとって最も基本的で重要な機能の一つです。Tauri では、安全で効率的なファイル操作 API を提供しています。
ファイル読み書き
テキストファイルの基本的な読み書き操作から始めましょう。以下は、設定ファイルを管理する Command の実装例です。
rustuse tauri::{command, AppHandle};
use std::fs;
use std::path::PathBuf;
#[command]
async fn read_config_file(app: AppHandle) -> Result<String, String> {
let app_dir = app.path_resolver()
.app_config_dir()
.ok_or("設定ディレクトリの取得に失敗しました")?;
let config_path = app_dir.join("config.json");
match fs::read_to_string(&config_path) {
Ok(content) => Ok(content),
Err(_) => {
// デフォルト設定を作成
let default_config = r#"{"theme": "light", "language": "ja"}"#;
fs::create_dir_all(&app_dir).map_err(|e| e.to_string())?;
fs::write(&config_path, default_config).map_err(|e| e.to_string())?;
Ok(default_config.to_string())
}
}
}
ファイル書き込み機能も実装してみましょう。
rust#[command]
async fn save_config_file(app: AppHandle, content: String) -> Result<(), String> {
let app_dir = app.path_resolver()
.app_config_dir()
.ok_or("設定ディレクトリの取得に失敗しました")?;
let config_path = app_dir.join("config.json");
fs::create_dir_all(&app_dir).map_err(|e| e.to_string())?;
fs::write(&config_path, content).map_err(|e| e.to_string())?;
Ok(())
}
フロントエンド側での使用例をご紹介します。
typescriptimport { invoke } from '@tauri-apps/api/tauri';
interface AppConfig {
theme: 'light' | 'dark';
language: string;
}
class ConfigManager {
async loadConfig(): Promise<AppConfig> {
try {
const configStr = await invoke<string>(
'read_config_file'
);
return JSON.parse(configStr);
} catch (error) {
console.error('設定読み込みエラー:', error);
return { theme: 'light', language: 'ja' };
}
}
async saveConfig(config: AppConfig): Promise<void> {
try {
const configStr = JSON.stringify(config, null, 2);
await invoke('save_config_file', {
content: configStr,
});
} catch (error) {
console.error('設定保存エラー:', error);
throw error;
}
}
}
ディレクトリ操作
ディレクトリの作成、削除、一覧取得などの操作も重要です。以下は、プロジェクトディレクトリを管理する Command の例です。
rustuse serde::{Deserialize, Serialize};
use std::fs::{self, DirEntry};
use std::path::Path;
#[derive(Serialize, Deserialize)]
struct FileEntry {
name: String,
path: String,
is_directory: bool,
size: u64,
}
#[command]
async fn list_directory(path: String) -> Result<Vec<FileEntry>, String> {
let dir_path = Path::new(&path);
if !dir_path.exists() {
return Err("指定されたディレクトリが存在しません".to_string());
}
if !dir_path.is_dir() {
return Err("指定されたパスはディレクトリではありません".to_string());
}
let entries = fs::read_dir(dir_path)
.map_err(|e| format!("ディレクトリ読み込みエラー: {}", e))?;
let mut result = Vec::new();
for entry in entries {
let entry = entry.map_err(|e| format!("エントリ読み込みエラー: {}", e))?;
let metadata = entry.metadata()
.map_err(|e| format!("メタデータ取得エラー: {}", e))?;
result.push(FileEntry {
name: entry.file_name().to_string_lossy().to_string(),
path: entry.path().to_string_lossy().to_string(),
is_directory: metadata.is_dir(),
size: metadata.len(),
});
}
Ok(result)
}
ファイル選択ダイアログ
ユーザーインターフェースとしてファイル選択ダイアログも重要です。Tauri では標準的なダイアログ API を提供しています。
rustuse tauri::api::dialog::FileDialogBuilder;
#[command]
async fn select_file(app: AppHandle) -> Result<Option<String>, String> {
let result = FileDialogBuilder::new()
.add_filter("テキストファイル", &["txt", "md"])
.add_filter("すべてのファイル", &["*"])
.pick_file();
Ok(result.map(|path| path.to_string_lossy().to_string()))
}
フロントエンド側での実装は以下のようになります。
typescriptasync function openFile(): Promise<string | null> {
try {
const filePath = await invoke<string | null>(
'select_file'
);
if (filePath) {
console.log('選択されたファイル:', filePath);
return filePath;
}
return null;
} catch (error) {
console.error('ファイル選択エラー:', error);
return null;
}
}
システム通知
デスクトップアプリケーションにおいて、システム通知は重要なユーザー体験の要素です。Tauri では、OS ネイティブの通知システムを活用できます。
通知の表示
基本的な通知表示機能を実装してみましょう。
rustuse tauri::{command, AppHandle, Manager};
use tauri::api::notification::Notification;
#[command]
async fn show_notification(
app: AppHandle,
title: String,
body: String
) -> Result<(), String> {
Notification::new(&app.config().tauri.bundle.identifier)
.title(&title)
.body(&body)
.show()
.map_err(|e| format!("通知表示エラー: {}", e))?;
Ok(())
}
より詳細な通知設定も可能です。以下は、進捗通知の実装例です。
rust#[derive(Serialize, Deserialize)]
struct ProgressNotification {
title: String,
progress: u8, // 0-100
total_items: u32,
completed_items: u32,
}
#[command]
async fn show_progress_notification(
app: AppHandle,
notification: ProgressNotification
) -> Result<(), String> {
let body = format!(
"進捗: {}% ({}/{})",
notification.progress,
notification.completed_items,
notification.total_items
);
Notification::new(&app.config().tauri.bundle.identifier)
.title(¬ification.title)
.body(&body)
.show()
.map_err(|e| format!("進捗通知エラー: {}", e))?;
Ok(())
}
カスタムアイコン
通知にカスタムアイコンを設定することで、アプリケーションの識別性を高められます。
rust#[command]
async fn show_custom_notification(
app: AppHandle,
title: String,
body: String,
icon_path: Option<String>
) -> Result<(), String> {
let mut notification = Notification::new(&app.config().tauri.bundle.identifier)
.title(&title)
.body(&body);
if let Some(icon) = icon_path {
notification = notification.icon(&icon);
}
notification.show()
.map_err(|e| format!("カスタム通知エラー: {}", e))?;
Ok(())
}
アクション付き通知
より高度な機能として、アクション付きの通知も実装できます。
typescriptimport { invoke } from '@tauri-apps/api/tauri';
class NotificationManager {
async showTaskCompleted(taskName: string): Promise<void> {
try {
await invoke('show_notification', {
title: 'タスク完了',
body: `${taskName}が正常に完了しました`,
});
} catch (error) {
console.error('通知表示エラー:', error);
}
}
async showProgressUpdate(
taskName: string,
completed: number,
total: number
): Promise<void> {
const progress = Math.round((completed / total) * 100);
try {
await invoke('show_progress_notification', {
title: taskName,
progress,
totalItems: total,
completedItems: completed,
});
} catch (error) {
console.error('進捗通知エラー:', error);
}
}
}
クリップボード操作
クリップボード操作は、ユーザビリティを向上させる重要な機能です。テキストデータの読み書きから、より複雑なデータ形式まで対応できます。
テキストの読み書き
基本的なテキストクリップボード操作を実装しましょう。
rustuse tauri::api::clipboard::Clipboard;
#[command]
async fn read_clipboard() -> Result<String, String> {
let mut clipboard = Clipboard::new()
.map_err(|e| format!("クリップボード初期化エラー: {}", e))?;
clipboard.read_text()
.map_err(|e| format!("クリップボード読み込みエラー: {}", e))
}
#[command]
async fn write_clipboard(text: String) -> Result<(), String> {
let mut clipboard = Clipboard::new()
.map_err(|e| format!("クリップボード初期化エラー: {}", e))?;
clipboard.write_text(&text)
.map_err(|e| format!("クリップボード書き込みエラー: {}", e))
}
フロントエンド側での使用例です。
typescriptclass ClipboardManager {
async copyToClipboard(text: string): Promise<boolean> {
try {
await invoke('write_clipboard', { text });
return true;
} catch (error) {
console.error('クリップボードコピーエラー:', error);
return false;
}
}
async pasteFromClipboard(): Promise<string | null> {
try {
const text = await invoke<string>('read_clipboard');
return text;
} catch (error) {
console.error('クリップボード貼り付けエラー:', error);
return null;
}
}
async copyCodeSnippet(
code: string,
language: string
): Promise<void> {
const formattedCode = `\`\`\`${language}\n${code}\n\`\`\``;
const success = await this.copyToClipboard(
formattedCode
);
if (success) {
// 通知で成功を知らせる
await invoke('show_notification', {
title: 'コピー完了',
body: `${language}コードをクリップボードにコピーしました`,
});
}
}
}
画像データの扱い
より高度な機能として、画像データのクリップボード操作も実装できます。
rustuse base64::{Engine as _, engine::general_purpose};
use std::fs;
#[command]
async fn copy_image_to_clipboard(image_path: String) -> Result<(), String> {
let image_data = fs::read(&image_path)
.map_err(|e| format!("画像読み込みエラー: {}", e))?;
let mut clipboard = Clipboard::new()
.map_err(|e| format!("クリップボード初期化エラー: {}", e))?;
// 画像をBase64エンコードしてテキストとして格納
let encoded = general_purpose::STANDARD.encode(&image_data);
let image_text = format!("data:image/png;base64,{}", encoded);
clipboard.write_text(&image_text)
.map_err(|e| format!("画像クリップボード書き込みエラー: {}", e))
}
まとめ
Tauri でのネイティブアクセスの利点
本記事では、Tauri で OS ネイティブ API にアクセスする様々な方法をご紹介いたしました。Tauri を使用することで得られる主な利点をまとめてみましょう。
軽量性と高パフォーマンス
Tauri アプリケーションは、従来の Electron アプリと比較して大幅に軽量です。システムの WebView を活用することで、アプリケーションサイズを最小限に抑えながら、高いパフォーマンスを実現しています。これにより、ユーザーにとってストレスの少ない快適な体験を提供できます。
強固なセキュリティ
ゼロトラストの原則に基づいた設計により、必要最小限の権限のみを付与することが可能です。これにより、セキュリティリスクを大幅に削減し、安全なアプリケーションを構築できます。
開発効率の向上
Web 技術の知識を活かしながら、ネイティブアプリケーションの機能を実現できるため、学習コストを抑えながら高機能なアプリケーションを開発できます。TypeScript による型安全性も確保されており、開発効率が大幅に向上します。
開発時の注意点
Tauri を使用した開発において、特に注意すべきポイントをご説明いたします。
許可設定の適切な管理
tauri.conf.json
での allowlist 設定は、セキュリティの要です。必要以上の権限を付与しないよう、定期的に設定を見直すことが重要です。機能追加時には、最小限の権限のみを追加するよう心がけましょう。
エラーハンドリングの重要性
ネイティブ API を使用する際は、必ず適切なエラーハンドリングを実装してください。ファイルアクセス権限、ネットワーク接続状況など、様々な要因でエラーが発生する可能性があります。
プラットフォーム固有の考慮事項
Windows、macOS、Linux それぞれで動作が異なる場合があります。クロスプラットフォーム対応を行う際は、各プラットフォームでの十分なテストが必要です。
次のステップ
Tauri での OS ネイティブ API アクセスをマスターした後は、以下のような発展的な内容に挑戦してみてください。
プラグイン開発
独自の Tauri プラグインを作成することで、より高度な機能を実装できます。コミュニティプラグインの活用も効果的です。
パフォーマンス最適化
大量のデータ処理や、リアルタイム性が求められる機能では、Rust 側での最適化が重要になります。
配布とデプロイメント
アプリケーションの配布方法、自動更新機能の実装など、実際のプロダクションで必要となる機能の学習をおすすめします。
Tauri は今後も活発に開発が続けられており、新機能の追加や改善が期待されています。公式ドキュメントやコミュニティの動向を定期的にチェックし、最新の情報をキャッチアップすることが、より良いアプリケーション開発につながるでしょう。
関連リンク
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来