Tauri v2でデスクトップアプリ開発 — Electron代替の決定版


Tauriとは

Tauriは、Web技術(HTML/CSS/JavaScript)とRustを組み合わせてデスクトップアプリを作るフレームワークです。Electronの代替として急速に注目を集めています。

Electronとの比較

項目TauriElectron
バイナリサイズ3-5 MB120-150 MB
メモリ使用量50-100 MB200-400 MB
起動時間<1秒2-3秒
バックエンド言語RustNode.js
セキュリティ高(デフォルトで厳格)中(設定次第)
プラットフォームWindows, macOS, LinuxWindows, macOS, Linux

Tauri v2の新機能(2026年)

  • モバイル対応: Android/iOSアプリのビルドサポート
  • プラグインシステムの改善: 公式プラグインが充実
  • パフォーマンス向上: 起動時間が30%短縮
  • マルチウィンドウ: 複数ウィンドウの管理が容易に
  • 深層リンク: カスタムURLスキーム対応

セットアップ

前提条件

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

# プラットフォーム別の依存関係
# macOS
xcode-select --install

# Windows(PowerShell管理者権限)
# Microsoft Visual Studio C++ Build Toolsをインストール

# Linux (Ubuntu/Debian)
sudo apt update
sudo apt install libwebkit2gtk-4.1-dev \
  build-essential \
  curl \
  wget \
  file \
  libssl-dev \
  libgtk-3-dev \
  libayatana-appindicator3-dev \
  librsvg2-dev

プロジェクト作成

# Tauri CLIインストール
cargo install tauri-cli

# プロジェクト作成
npm create tauri-app@latest

# 対話式で以下を選択
# - App name: my-tauri-app
# - Frontend: React / Vue / Svelte / Vanilla
# - TypeScript: Yes

または既存のフロントエンドプロジェクトに追加:

cd my-existing-app
npm install -D @tauri-apps/cli
npx tauri init

プロジェクト構造

my-tauri-app/
├── src/               # フロントエンド(React/Vue/Svelteなど)
├── src-tauri/         # Rustバックエンド
│   ├── src/
│   │   └── main.rs    # エントリーポイント
│   ├── Cargo.toml     # Rust依存関係
│   ├── tauri.conf.json # Tauri設定
│   └── icons/         # アプリアイコン
└── package.json

基本的な実装

Rustバックエンド

// src-tauri/src/main.rs
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

// Tauriコマンド定義
#[tauri::command]
fn greet(name: &str) -> String {
    format!("Hello, {}! You've been greeted from Rust!", name)
}

#[tauri::command]
fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[tauri::command]
async fn fetch_data() -> Result<String, String> {
    // 非同期処理
    tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
    Ok("Data from backend".to_string())
}

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

フロントエンド(React + TypeScript)

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

function App() {
  const [greetMsg, setGreetMsg] = useState("");
  const [name, setName] = useState("");

  async function greet() {
    const message = await invoke<string>("greet", { name });
    setGreetMsg(message);
  }

  async function calculate() {
    const result = await invoke<number>("add", { a: 10, b: 20 });
    console.log("Result:", result); // 30
  }

  return (
    <div>
      <h1>Welcome to Tauri!</h1>
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Enter your name"
      />
      <button onClick={greet}>Greet</button>
      <p>{greetMsg}</p>

      <button onClick={calculate}>Calculate</button>
    </div>
  );
}

export default App;

開発とビルド

# 開発モード(ホットリロード有効)
npm run tauri dev

# プロダクションビルド
npm run tauri build

# ビルド成果物
# macOS: src-tauri/target/release/bundle/dmg/
# Windows: src-tauri/target/release/bundle/msi/
# Linux: src-tauri/target/release/bundle/appimage/

IPC通信(高度な例)

ファイル操作

use tauri::command;
use std::fs;

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

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

#[command]
fn list_files(dir: String) -> Result<Vec<String>, String> {
    let entries = fs::read_dir(&dir)
        .map_err(|e| e.to_string())?
        .filter_map(|entry| entry.ok())
        .map(|entry| entry.file_name().to_string_lossy().to_string())
        .collect();
    Ok(entries)
}
import { invoke } from "@tauri-apps/api/tauri";

// ファイル読み込み
const content = await invoke<string>("read_file", {
  path: "/path/to/file.txt"
});

// ファイル書き込み
await invoke("write_file", {
  path: "/path/to/output.txt",
  content: "Hello, Tauri!"
});

// ディレクトリ一覧
const files = await invoke<string[]>("list_files", {
  dir: "/path/to/directory"
});

ステート管理

use tauri::State;
use std::sync::Mutex;

struct AppState {
    counter: Mutex<i32>,
}

#[command]
fn increment(state: State<AppState>) -> i32 {
    let mut counter = state.counter.lock().unwrap();
    *counter += 1;
    *counter
}

#[command]
fn get_counter(state: State<AppState>) -> i32 {
    *state.counter.lock().unwrap()
}

fn main() {
    tauri::Builder::default()
        .manage(AppState {
            counter: Mutex::new(0),
        })
        .invoke_handler(tauri::generate_handler![increment, get_counter])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

イベント通信(双方向)

use tauri::{Manager, Window};

#[command]
async fn start_task(window: Window) {
    for i in 0..100 {
        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;

        // フロントエンドにイベント送信
        window.emit("progress", i).unwrap();
    }
    window.emit("complete", ()).unwrap();
}
import { listen } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api/tauri";

// イベントリスナー登録
const unlisten1 = await listen<number>("progress", (event) => {
  console.log("Progress:", event.payload);
  setProgress(event.payload);
});

const unlisten2 = await listen("complete", () => {
  console.log("Task completed!");
});

// タスク開始
await invoke("start_task");

// クリーンアップ
unlisten1();
unlisten2();

公式プラグイン

ファイルシステム

npm install @tauri-apps/plugin-fs
import { readTextFile, writeTextFile, BaseDirectory } from "@tauri-apps/plugin-fs";

// アプリデータディレクトリに保存
await writeTextFile("config.json", JSON.stringify(config), {
  dir: BaseDirectory.AppData,
});

// 読み込み
const content = await readTextFile("config.json", {
  dir: BaseDirectory.AppData,
});

ダイアログ

npm install @tauri-apps/plugin-dialog
import { open, save, message } from "@tauri-apps/plugin-dialog";

// ファイル選択ダイアログ
const selected = await open({
  multiple: false,
  filters: [
    { name: "Image", extensions: ["png", "jpg", "jpeg"] },
    { name: "Document", extensions: ["pdf", "doc", "docx"] },
  ],
});

// 保存ダイアログ
const savePath = await save({
  defaultPath: "output.txt",
});

// メッセージボックス
await message("Operation completed successfully!", {
  title: "Success",
  type: "info"
});

通知

npm install @tauri-apps/plugin-notification
import { sendNotification } from "@tauri-apps/plugin-notification";

await sendNotification({
  title: "Update Available",
  body: "A new version is ready to install.",
});

HTTPクライアント

npm install @tauri-apps/plugin-http
import { fetch } from "@tauri-apps/plugin-http";

const response = await fetch("https://api.example.com/data", {
  method: "GET",
  headers: {
    "Content-Type": "application/json",
  },
});

const data = await response.json();

マルチウィンドウ

use tauri::{Manager, WindowBuilder};

#[command]
fn open_settings_window(app: tauri::AppHandle) {
    let window = WindowBuilder::new(
        &app,
        "settings",
        tauri::WindowUrl::App("settings.html".into()),
    )
    .title("Settings")
    .inner_size(600.0, 400.0)
    .build()
    .unwrap();
}
import { WebviewWindow } from "@tauri-apps/api/window";

// 新しいウィンドウを開く
const webview = new WebviewWindow("settings", {
  url: "settings.html",
  title: "Settings",
  width: 600,
  height: 400,
});

webview.once("tauri://created", () => {
  console.log("Window created");
});

webview.once("tauri://error", (e) => {
  console.error("Error creating window:", e);
});

設定とカスタマイズ

// src-tauri/tauri.conf.json
{
  "build": {
    "beforeDevCommand": "npm run dev",
    "beforeBuildCommand": "npm run build",
    "devPath": "http://localhost:5173",
    "distDir": "../dist"
  },
  "package": {
    "productName": "My Tauri App",
    "version": "1.0.0"
  },
  "tauri": {
    "allowlist": {
      "all": false,
      "fs": {
        "all": false,
        "readFile": true,
        "writeFile": true,
        "scope": ["$APPDATA/*"]
      },
      "dialog": {
        "all": true
      },
      "http": {
        "all": true,
        "request": true,
        "scope": ["https://api.example.com/*"]
      }
    },
    "windows": [
      {
        "title": "My Tauri App",
        "width": 1200,
        "height": 800,
        "resizable": true,
        "fullscreen": false
      }
    ],
    "security": {
      "csp": "default-src 'self'; img-src 'self' data: https:"
    }
  }
}

アップデーター

npm install @tauri-apps/plugin-updater
use tauri_plugin_updater::UpdaterExt;

#[command]
async fn check_for_updates(app: tauri::AppHandle) -> Result<bool, String> {
    let update = app.updater().check().await
        .map_err(|e| e.to_string())?;

    if let Some(update) = update {
        let mut downloaded = 0;

        update
            .download_and_install(|chunk_length, content_length| {
                downloaded += chunk_length;
                println!("Downloaded {}/{}", downloaded, content_length.unwrap_or(0));
            })
            .await
            .map_err(|e| e.to_string())?;

        Ok(true)
    } else {
        Ok(false)
    }
}

ビルドとリリース

GitHub Actionsで自動ビルド

# .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
      - uses: actions/setup-node@v4
      - uses: dtolnay/rust-toolchain@stable
      - name: Install dependencies (Ubuntu)
        if: matrix.platform == 'ubuntu-latest'
        run: |
          sudo apt-get update
          sudo apt-get install -y libwebkit2gtk-4.1-dev
      - run: npm install
      - uses: tauri-apps/tauri-action@v0
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tagName: ${{ github.ref_name }}
          releaseName: "App v__VERSION__"
          releaseBody: "See the assets to download this version."
          releaseDraft: true
          prerelease: false

まとめ

Tauri v2は、軽量・高速・安全なデスクトップアプリ開発の新しいスタンダードです。

Tauriを選ぶべき理由:

  • バイナリサイズが小さい(Electronの1/30)
  • メモリ使用量が少ない
  • Rustの安全性とパフォーマンス
  • モダンなWeb技術を活用
  • クロスプラットフォーム対応

次のデスクトップアプリプロジェクトでは、ぜひTauriを検討してください。

公式サイト: https://tauri.app/