ブログ記事

Tauri 2.0実践ガイド2025 - Rust製デスクトップアプリ開発の新時代

Tauri 2.0はRustベースの革新的なクロスプラットフォームフレームワーク。Electronの1/10のサイズで90%高速、iOS/Android対応でモバイルも制覇。WebViewを活用した軽量アーキテクチャと最高レベルのセキュリティで、次世代アプリ開発を実現する完全ガイド。

Web開発
Tauri Rust デスクトップアプリ クロスプラットフォーム モバイル
Tauri 2.0実践ガイド2025 - Rust製デスクトップアプリ開発の新時代のヒーロー画像

2024 年 10 月にリリースされた Tauri 2.0 は、デスクトップアプリ開発に革命をもたらしました。Electronの重さに悩む開発者にとって、Tauri は軽量・高速・セキュアな理想的な代替となっています。さらに iOS/Android サポートにより、真のクロスプラットフォーム開発が可能になりました。

この記事で学べること

  • Tauri 2.0 の新機能とモバイルプラットフォーム対応
  • Rust + フロントエンドでの実践的な開発手法
  • Electronとの詳細な比較とマイグレーション戦略
  • セキュリティ重視のアーキテクチャ設計
  • 実践的なデプロイメントとパフォーマンス最適化

Tauri 2.0の革新的な進化

Tauri 2.0 は単なるアップデートではなく、アプリケーション開発の新しいパラダイムを提示しています。

Tauri 2.0 アーキテクチャ

チャートを読み込み中...

主要な新機能

Tauri 2.0 正式リリース

モバイルサポート、新権限システム導入

プラグインエコシステム拡充

公式プラグイン30種類以上

Servo統合実験開始

Rust製ブラウザエンジンとの連携

現在

エンタープライズ採用急増中

クイックスタート

環境構築

# Rust のインストール
winget install Rustlang.Rust

# 必要な依存関係
npm install -g @tauri-apps/cli

# Visual Studio Build Tools も必要
# https://visualstudio.microsoft.com/visual-cpp-build-tools/
# Rust のインストール
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Xcode Command Line Tools
xcode-select --install

# Tauri CLI
npm install -g @tauri-apps/cli
# 依存関係のインストール
sudo apt update
sudo apt install libwebkit2gtk-4.0-dev \
    build-essential \
    curl \
    wget \
    file \
    libssl-dev \
    libgtk-3-dev \
    libayatana-appindicator3-dev \
    librsvg2-dev

# Rust のインストール
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

プロジェクト作成

# インタラクティブなプロジェクト作成
npm create tauri-app@latest my-app

# テンプレート選択
? Choose which language to use for your frontend
 TypeScript / JavaScript  (pnpm, yarn, npm, bun)
    Rust                    (cargo)

? Choose your package manager
 pnpm

? Choose your UI template
 React    (TypeScript)
    Vue      (TypeScript)
    Svelte   (TypeScript)
    Solid    (TypeScript)
    Angular  (TypeScript)
    Vanilla  (TypeScript)

実践的な開発パターン

フロントエンドとRustの連携

// src/App.tsx
import { invoke } from '@tauri-apps/api/tauri';
import { useState } from 'react';

function App() {
  const [greeting, setGreeting] = useState('');
  const [name, setName] = useState('');

  async function greet() {
    // Rustの関数を呼び出し
    const response = await invoke<string>('greet', { 
      name 
    });
    setGreeting(response);
  }

  return (
    <div>
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="名前を入力..."
      />
      <button onClick={greet}>挨拶</button>
      <p>{greeting}</p>
    </div>
  );
}
// src-tauri/src/main.rs
use tauri::Manager;

#[tauri::command]
fn greet(name: &str) -> String {
    format!("こんにちは、{}さん!Tauriからの挨拶です。", name)
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
フロントエンド (TypeScript)
// src/App.tsx
import { invoke } from '@tauri-apps/api/tauri';
import { useState } from 'react';

function App() {
  const [greeting, setGreeting] = useState('');
  const [name, setName] = useState('');

  async function greet() {
    // Rustの関数を呼び出し
    const response = await invoke<string>('greet', { 
      name 
    });
    setGreeting(response);
  }

  return (
    <div>
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="名前を入力..."
      />
      <button onClick={greet}>挨拶</button>
      <p>{greeting}</p>
    </div>
  );
}
バックエンド (Rust)
// src-tauri/src/main.rs
use tauri::Manager;

#[tauri::command]
fn greet(name: &str) -> String {
    format!("こんにちは、{}さん!Tauriからの挨拶です。", name)
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

ファイルシステム操作

use std::fs;
use tauri::Manager;

#[tauri::command]
async fn read_file(path: String) -> Result<String, String> {
    fs::read_to_string(&path)
        .map_err(|e| e.to_string())
}

#[tauri::command]
async fn write_file(path: String, contents: String) -> Result<(), String> {
    fs::write(&path, contents)
        .map_err(|e| e.to_string())
}

#[tauri::command]
async fn list_directory(path: String) -> Result<Vec<String>, String> {
    let entries = fs::read_dir(&path)
        .map_err(|e| e.to_string())?;
    
    let mut files = Vec::new();
    for entry in entries {
        if let Ok(entry) = entry {
            if let Some(name) = entry.file_name().to_str() {
                files.push(name.to_string());
            }
        }
    }
    
    Ok(files)
}

新しい権限システム

Tauri 2.0 では、より細かい権限制御が可能になりました:

// tauri.conf.json
{
  "tauri": {
    "security": {
      "capabilities": [
        {
          "identifier": "main",
          "description": "メインウィンドウの権限",
          "windows": ["main"],
          "permissions": [
            "fs:read",
            "fs:write",
            {
              "identifier": "fs:scope",
              "allow": ["$APPDATA/*", "$DOCUMENT/*"],
              "deny": ["$HOME/.ssh/*"]
            }
          ]
        }
      ]
    }
  }
}

モバイル開発の実装

iOS対応

# iOS開発環境のセットアップ
npm run tauri ios init

# 開発用ビルド
npm run tauri ios dev

# リリースビルド
npm run tauri ios build

Swift連携の例:

// ios/Sources/MyAppPlugin.swift
import Tauri
import UIKit

class MyAppPlugin: Plugin {
    @objc public func showNativeAlert(_ call: Invoke) {
        let title = call.getString("title") ?? "Alert"
        let message = call.getString("message") ?? ""
        
        DispatchQueue.main.async {
            let alert = UIAlertController(
                title: title,
                message: message,
                preferredStyle: .alert
            )
            alert.addAction(UIAlertAction(
                title: "OK",
                style: .default
            ))
            
            self.presentViewController(alert)
            call.resolve(["success": true])
        }
    }
}

Android対応

// android/app/src/main/java/com/myapp/MyAppPlugin.kt
package com.myapp

import app.tauri.annotation.Command
import app.tauri.annotation.TauriPlugin
import app.tauri.plugin.Plugin
import app.tauri.plugin.Invoke

@TauriPlugin
class MyAppPlugin(private val activity: Activity): Plugin(activity) {
    @Command
    fun vibrate(invoke: Invoke) {
        val duration = invoke.getLong("duration") ?: 100L
        val vibrator = activity.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            vibrator.vibrate(
                VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE)
            )
        } else {
            @Suppress("DEPRECATION")
            vibrator.vibrate(duration)
        }
        
        invoke.resolve(mapOf("success" to true))
    }
}

Electronとの徹底比較

Tauri 2.0 vs Electron パフォーマンス比較(2025年版)
項目 Tauri 2.0 Electron 差異
バンドルサイズ 3-10MB 50MB以上 80-95%削減
メモリ使用量 〜50MB 〜300MB 83%削減
起動時間 〜0.5秒 〜2秒 75%高速
セキュリティ Rust製・サンドボックス Chromiumベース 優位
開発言語 Rust + Web技術 JavaScript/Node.js 学習曲線あり
プラットフォーム デスクトップ + モバイル デスクトップのみ Tauri優位
WebView OS標準 Chromium同梱 軽量

マイグレーション戦略

// Electronからの移行例

// Before (Electron)
const { app, BrowserWindow, ipcMain } = require('electron');

ipcMain.handle('read-file', async (event, path) => {
  return fs.readFileSync(path, 'utf-8');
});

// After (Tauri)
// main.rs
#[tauri::command]
async fn read_file(path: String) -> Result<String, String> {
  std::fs::read_to_string(path)
    .map_err(|e| e.to_string())
}

// フロントエンド
import { invoke } from '@tauri-apps/api/tauri';
const content = await invoke('read_file', { path });

高度な機能実装

システムトレイ

use tauri::{
    CustomMenuItem, Manager, SystemTray, SystemTrayEvent,
    SystemTrayMenu, SystemTrayMenuItem,
};

fn main() {
    let quit = CustomMenuItem::new("quit".to_string(), "終了");
    let hide = CustomMenuItem::new("hide".to_string(), "隠す");
    let tray_menu = SystemTrayMenu::new()
        .add_item(hide)
        .add_native_item(SystemTrayMenuItem::Separator)
        .add_item(quit);
    
    let system_tray = SystemTray::new()
        .with_menu(tray_menu);
    
    tauri::Builder::default()
        .system_tray(system_tray)
        .on_system_tray_event(|app, event| match event {
            SystemTrayEvent::LeftClick {
                position: _,
                size: _,
                ..
            } => {
                let window = app.get_window("main").unwrap();
                window.show().unwrap();
            }
            SystemTrayEvent::MenuItemClick { id, .. } => {
                match id.as_str() {
                    "quit" => {
                        std::process::exit(0);
                    }
                    "hide" => {
                        let window = app.get_window("main").unwrap();
                        window.hide().unwrap();
                    }
                    _ => {}
                }
            }
            _ => {}
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

プラグインシステム

// カスタムプラグインの作成
use tauri::{
    plugin::{Builder, TauriPlugin},
    Manager, Runtime,
};

pub fn init<R: Runtime>() -> TauriPlugin<R> {
    Builder::new("my-plugin")
        .invoke_handler(tauri::generate_handler![
            my_custom_command
        ])
        .setup(|app| {
            // プラグインの初期化処理
            println!("My Plugin initialized!");
            Ok(())
        })
        .build()
}

#[tauri::command]
async fn my_custom_command() -> String {
    "Custom plugin response".to_string()
}

// main.rs での使用
fn main() {
    tauri::Builder::default()
        .plugin(my_plugin::init())
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

パフォーマンス最適化

バンドルサイズの最小化

# Cargo.toml
[profile.release]
opt-level = "z"     # サイズ最適化
lto = true          # Link Time Optimization
codegen-units = 1   # 単一コード生成ユニット
strip = true        # シンボルを削除
panic = "abort"     # パニック時の巻き戻しを無効化

[dependencies]
# 必要な機能のみを選択
tauri = { version = "2.0", features = ["dialog", "fs", "notification"] }
バンドルサイズ削減率 92 %

起動時間の最適化

// 遅延読み込みの実装
// App.tsx
import { lazy, Suspense } from 'react';

const HeavyComponent = lazy(() => import('./components/HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <HeavyComponent />
    </Suspense>
  );
}

セキュリティベストプラクティス

セキュリティ重要事項

  • CSP(Content Security Policy)を必ず設定
  • 外部スクリプトの実行を制限
  • IPC 通信では必ず入力を検証
  • 機密データは Rust側で暗号化
// tauri.conf.json
{
  "tauri": {
    "security": {
      "csp": {
        "default-src": "'self'",
        "script-src": "'self' 'unsafe-inline'",
        "style-src": "'self' 'unsafe-inline'",
        "img-src": "'self' data: https:",
        "connect-src": "'self' https://api.example.com"
      },
      "dangerousDisableAssetCspModification": false,
      "freezePrototype": true
    }
  }
}

デプロイメントと配布

自動アップデート機能

use tauri::updater::builder::UpdateBuilder;

#[tauri::command]
async fn check_update(app_handle: tauri::AppHandle) -> Result<bool, String> {
    let update = UpdateBuilder::new()
        .current_version(app_handle.package_info().version.clone())
        .url("https://api.example.com/update/manifest.json")
        .build()
        .map_err(|e| e.to_string())?;
    
    if let Some(update) = update {
        update.download_and_install().await
            .map_err(|e| e.to_string())?;
        Ok(true)
    } else {
        Ok(false)
    }
}

CI/CDパイプライン

# .github/workflows/release.yml
name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    strategy:
      matrix:
        platform: [macos-latest, ubuntu-latest, windows-latest]
    runs-on: ${{ matrix.platform }}
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          
      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable
        
      - name: Install dependencies (Ubuntu)
        if: matrix.platform == 'ubuntu-latest'
        run: |
          sudo apt-get update
          sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev
          
      - name: Build Tauri App
        run: |
          npm install
          npm run tauri build
          
      - name: Upload artifacts
        uses: actions/upload-artifact@v3
        with:
          name: ${{ matrix.platform }}
          path: src-tauri/target/release/bundle/

まとめ

Tauri 2.0 は、2025 年のクロスプラットフォーム開発における最適解の 1 つです。

フレームワーク選択ガイド
選択基準 Tauriが最適 Electronが最適
パフォーマンス重視
小さいバンドルサイズ
モバイル対応必須
高度なセキュリティ
Node.jsエコシステム
Chromium固有機能
既存Electronアプリ

Tauri 2.0 は、軽量・高速・セキュアなアプリケーション開発を可能にし、特にリソース制約のある環境やモバイル展開を考慮する場合に最適な選択肢となっています。

この記事は役に立ちましたか?

Daily Hackでは、開発者の皆様に役立つ情報を毎日発信しています。