はじめに
個人的に、Tauriは2024年以降のデスクトップアプリ開発で最有力候補だと思っています。
GitHubで約10万スター、Rust製のクロスプラットフォームフレームワーク。「Electronの軽量版」という触れ込みで登場したわけですが、今やElectronの代替どころか、それ以上の存在感を放っています。
正直なところ、最初は「またElectronクローンか」と思っていたんですよ。Electronで十分じゃない?と。でも実際に触ってみたら、「アプリサイズが600KB」という事実だけで、かなり印象が変わりました。Electronで100MBとか普通にあったのに、600KBですよ。30代になって思うのは、ユーザーにとって軽いアプリは正義だということ。Electronで「重い」「メモリ食いすぎ」と言われた経験がある人には刺さる話だと思います。
Tauriとは
TauriはRustで書かれたデスクトップアプリケーションフレームワークで、Webの技術(HTML/CSS/JavaScript)でUIを作りつつ、バックエンドはRustで書けるという設計になっています。
特徴的なのは、OSのネイティブWebViewを使う点。Electronはchromiumを丸ごとバンドルするので重くなるわけですが、TauriはOSに元々入っているWebViewを使う。だからアプリサイズが劇的に小さくなる。
2.0からはモバイル(iOS/Android)にも対応。単一コードベースでデスクトップとモバイル両方のアプリが作れるようになりました。
特徴・メリット
1. 圧倒的な軽量さ
これ、意外とインパクトが大きいんですよ。
- Electronアプリ: 100MB〜200MB
- Tauriアプリ: 600KB〜3MB
10倍以上の差。ダウンロード時間、ディスク容量、起動速度、全部に効いてくる。コスパ的に、この軽さは正義です。
2. メモリ消費量の少なさ
Electronは1つのアプリでChromiumプロセスが動くので、メモリを300MB〜500MB食うことがザラ。
Tauriは50MB〜100MB程度で済む。複数のアプリを同時に立ち上げても、PCが重くならない。
3. セキュリティファースト
Tauriチームは「Maximum Security」を最優先事項として掲げています。
- デフォルトでAPIアクセスが制限される
- 許可リストで必要な機能だけを有効化
- CSP(Content Security Policy)が標準で厳格
「全部許可」がデフォルトのElectronとは思想が逆。
4. Rustの恩恵
バックエンドがRustなので:
- メモリ安全性が保証される
- 実行速度が速い
- システムリソースへのアクセスが効率的
個人的には、このRustで書けるというのが地味に嬉しい。TypeScriptだけで完結させたい人には向かないかもしれませんが。
5. クロスプラットフォーム
Tauri 2.0で対応しているプラットフォーム:
- Windows
- macOS
- Linux
- iOS
- Android
1つのコードベースで全部カバーできる。これがQOL上がるポイント。
インストール方法
前提条件
まずRustが必要です。入っていない場合:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
OSごとに追加の依存関係もあります(公式ドキュメント参照)。
プロジェクトの作成
一番簡単なのはcreate-tauri-appを使う方法:
npm create tauri-app@latest
対話形式でプロジェクト名、フロントエンドのフレームワーク(React、Vue、Svelte、Solidなど)を選べます。
既存プロジェクトへの追加
既にReactやVueのプロジェクトがある場合:
npm install -D @tauri-apps/cli@latest
npx tauri init
これでTauriの設定ファイルが追加される。既存のWebアプリをそのままデスクトップアプリ化できます。
基本的な使い方
プロジェクト構造
my-tauri-app/
├── src/ # フロントエンド(React, Vue等)
├── src-tauri/ # Rust(バックエンド)
│ ├── src/
│ │ └── main.rs
│ ├── Cargo.toml
│ └── tauri.conf.json
├── package.json
└── ...
フロントエンドとバックエンドが明確に分離されている。
開発サーバーの起動
npm run tauri dev
ホットリロードが効くので、フロントエンドの変更はすぐ反映される。
Rustコマンドの定義
src-tauri/src/main.rsでRust側の関数を定義:
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
フロントエンドからの呼び出し
import { invoke } from '@tauri-apps/api/core';
async function greet() {
const message = await invoke('greet', { name: 'World' });
console.log(message); // "Hello, World! You've been greeted from Rust!"
}
TypeScriptからRustの関数を呼べる。この橋渡しがTauriの核心部分。
ファイルシステムへのアクセス
import { readTextFile, writeTextFile } from '@tauri-apps/plugin-fs';
// ファイル読み込み
const content = await readTextFile('path/to/file.txt');
// ファイル書き込み
await writeTextFile('path/to/file.txt', 'Hello, Tauri!');
Webアプリでは普通できないファイル操作が、Tauriなら簡単にできる。
ウィンドウ操作
import { getCurrentWindow } from '@tauri-apps/api/window';
const appWindow = getCurrentWindow();
// ウィンドウを最小化
await appWindow.minimize();
// フルスクリーン切り替え
await appWindow.setFullscreen(true);
// ウィンドウを閉じる
await appWindow.close();
システムトレイ
use tauri::{
menu::{Menu, MenuItem},
tray::TrayIconBuilder,
};
fn main() {
tauri::Builder::default()
.setup(|app| {
let quit = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
let menu = Menu::with_items(app, &[&quit])?;
TrayIconBuilder::new()
.icon(app.default_window_icon().unwrap().clone())
.menu(&menu)
.build(app)?;
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
実践的なユースケース
シンプルなメモアプリ
// フロントエンド: React + Tauri
import { useState, useEffect } from 'react';
import { readTextFile, writeTextFile, BaseDirectory } from '@tauri-apps/plugin-fs';
function MemoApp() {
const [content, setContent] = useState('');
const [saved, setSaved] = useState(true);
useEffect(() => {
// 起動時にファイルを読み込む
const loadMemo = async () => {
try {
const text = await readTextFile('memo.txt', {
baseDir: BaseDirectory.AppData,
});
setContent(text);
} catch {
// ファイルがなければ空で開始
}
};
loadMemo();
}, []);
const handleSave = async () => {
await writeTextFile('memo.txt', content, {
baseDir: BaseDirectory.AppData,
});
setSaved(true);
};
return (
<div>
<textarea
value={content}
onChange={(e) => {
setContent(e.target.value);
setSaved(false);
}}
/>
<button onClick={handleSave} disabled={saved}>
{saved ? '保存済み' : '保存'}
</button>
</div>
);
}
Rust側で重い処理を実行
use std::fs;
use walkdir::WalkDir;
#[tauri::command]
async fn count_files(path: String) -> Result<usize, String> {
let count = WalkDir::new(&path)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.file_type().is_file())
.count();
Ok(count)
}
// フロントエンドから呼び出し
const fileCount = await invoke('count_files', { path: '/home/user/documents' });
console.log(`ファイル数: ${fileCount}`);
ファイル走査みたいな重い処理はRust側でやる。JavaScriptでやるより圧倒的に速い。
自動アップデート機能
Tauriには自動アップデート機能が組み込まれています:
// tauri.conf.json
{
"plugins": {
"updater": {
"endpoints": [
"https://your-server.com/releases/{{target}}/{{arch}}/{{current_version}}"
]
}
}
}
import { check } from '@tauri-apps/plugin-updater';
import { relaunch } from '@tauri-apps/plugin-process';
async function checkForUpdates() {
const update = await check();
if (update) {
await update.downloadAndInstall();
await relaunch();
}
}
ビルドと配布
プロダクションビルド
npm run tauri build
各OS向けのインストーラーが生成される:
- Windows:
.msi,.exe - macOS:
.dmg,.app - Linux:
.deb,.rpm,.AppImage
バンドルサイズの確認
ビルド後、src-tauri/target/release/bundle/以下に成果物が入る。
試しにシンプルなアプリをビルドしてみると、本当に数MBで収まっていて感動する。
Electronからの移行
正直なところ、移行する価値は十分あると思います。特に以下のケース:
-
アプリサイズが気になる
- ユーザーからの「重い」というフィードバック対策
-
メモリ消費を減らしたい
- 低スペックPCでも快適に動かしたい場合
-
セキュリティを強化したい
- デフォルトでセキュアな設計
移行のポイント:
- フロントエンド(React、Vueなど)はほぼそのまま使える
electronのAPIを@tauri-apps/apiに置き換える- Node.js依存の処理はRustで書き直すか、サイドカーとして別プロセスで動かす
Node.jsのエコシステムをフルに使いたい場合はElectronの方が楽な場面もある。そこは使い分けですね。
まとめ
Tauriを導入して感じた変化:
- アプリサイズ: 100MB → 3MB(97%削減)
- メモリ消費: 300MB → 50MB(83%削減)
- 起動速度: 体感で2〜3倍速い
- セキュリティ: デフォルトで堅牢
- 学習コスト: フロントエンドはそのまま、Rustは必要な分だけ
正直なところ、新規でデスクトップアプリを作るなら、Electronを選ぶ理由はかなり限られると思います。Node.jsのライブラリが必須とか、Rustを書きたくないとか、そういう場合以外はTauri一択ですね。
特に「軽いアプリを作りたい」「ユーザー体験を重視したい」「Rustに興味がある」という人には最適。Electronで消耗していた時間を取り戻せますよ。
まだ試していない人は、まずnpm create tauri-app@latestで小さなプロジェクトを作ってみてください。ビルド後のアプリサイズを見た瞬間、「なんでもっと早く使わなかったんだ」と思うはずです。