デスクトップアプリ比較:Tauri vs Electron vs Wails でクロスプラットフォームアプリを構築する
オープンソースラボ編集部 ・ 2026年6月14日
デスクトップアプリ比較:Tauri vs Electron vs Wails でクロスプラットフォームアプリを構築する
WebフロントエンドエンジニアがWindows・macOS・Linuxで動作するデスクトップアプリを開発できるクロスプラットフォームフレームワークが充実しています。Tauri(Rust製・超軽量)・Electron(Node.js製・最大シェア)・Wails(Go製・軽量)の3つが2026年のクロスプラットフォームデスクトップアプリのデファクトスタンダードです。
デスクトップアプリフレームワークを選ぶ理由
- 既存WebスキルでデスクトップUI: React/Vue/SvelteのコードをそのままデスクトップGUIに流用
- ネイティブAPI: ファイルシステム・システムトレイ・通知・Webview外のOS機能にアクセス
- オフライン動作: ブラウザ拡張やSaaS不要のスタンドアロンアプリとして配布
- パフォーマンス: Chromeを丸ごと同梱するElectronに対してTauri/WailsはシステムのWebviewを使いバイナリが小さい
主要フレームワークの概要
Tauri
2021年公開、Rust製のOSSです。GitHubスター86k+。Rust + WebviewでElectronの1/10のバイナリサイズを実現するフレームワークで、フロントエンドは任意のWeb技術(React・Vue・Svelte)を使用します。Tauri v2ではAndroid・iOSのモバイル対応も追加されました。
# Tauri v2 セットアップ(Vite + React + TypeScript)
npm create tauri-app@latest my-app -- --template react-ts --manager npm
cd my-app
npm install
npm run tauri dev
// src-tauri/src/lib.rs: Rustバックエンドのコマンド定義
use tauri::{AppHandle, Manager};
use std::path::PathBuf;
// フロントエンドからコマンドを受け取る
#[tauri::command]
async fn read_file(path: String) -> Result<String, String> {
std::fs::read_to_string(&path)
.map_err(|e| format!("ファイル読み込みエラー: {}", e))
}
#[tauri::command]
async fn write_file(path: String, content: String) -> Result<(), String> {
std::fs::write(&path, content)
.map_err(|e| format!("ファイル書き込みエラー: {}", e))
}
#[tauri::command]
async fn get_app_data_dir(app: AppHandle) -> Result<String, String> {
let data_dir = app.path().app_data_dir()
.map_err(|e| format!("パス取得エラー: {}", e))?;
Ok(data_dir.to_string_lossy().to_string())
}
// システムトレイアイコン
#[tauri::command]
async fn show_notification(title: String, body: String, app: AppHandle) {
let _ = app.notification()
.title(&title)
.body(&body)
.show();
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_notification::init())
.plugin(tauri_plugin_dialog::init())
.invoke_handler(tauri::generate_handler![
read_file,
write_file,
get_app_data_dir,
show_notification,
])
.run(tauri::generate_context!())
.expect("Tauriアプリの起動に失敗");
}
// src/App.tsx: React フロントエンドからTauriコマンドを呼び出す
import { invoke } from '@tauri-apps/api/core'
import { open, save } from '@tauri-apps/plugin-dialog'
import { useState } from 'react'
export default function App() {
const [content, setContent] = useState('')
const [filePath, setFilePath] = useState('')
const openFile = async () => {
const selected = await open({
multiple: false,
filters: [{ name: 'テキスト', extensions: ['txt', 'md'] }],
})
if (selected) {
const text = await invoke<string>('read_file', { path: selected })
setContent(text)
setFilePath(selected as string)
}
}
const saveFile = async () => {
const path = await save({ defaultPath: filePath || 'document.txt' })
if (path) {
await invoke('write_file', { path, content })
await invoke('show_notification', { title: '保存完了', body: `${path}に保存しました` })
}
}
return (
<div style={{ padding: 20 }}>
<button onClick={openFile}>ファイルを開く</button>
<textarea value={content} onChange={e => setContent(e.target.value)} rows={20} style={{ width: '100%' }} />
<button onClick={saveFile}>保存</button>
</div>
)
}
Electron
2013年公開(GitHub製)、TypeScript/JavaScript製のOSSです。GitHubスター115k+。VS Code・Slack・Discordが使う実績最大のデスクトップフレームワークで、Node.jsのAPIがフル利用できます。Chromiumを同梱するためバイナリが200MB前後と大きいですが、エコシステムと実績が圧倒的です。
// main.ts: Electronメインプロセス(Node.js)
import { app, BrowserWindow, ipcMain, dialog, Notification, Tray, Menu, nativeImage } from 'electron'
import path from 'path'
import fs from 'fs'
let mainWindow: BrowserWindow | null
let tray: Tray | null
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
contextIsolation: true,
},
})
// 開発時: Vite devサーバー、本番: dist/index.html
if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:5173')
} else {
mainWindow.loadFile(path.join(__dirname, '../dist/index.html'))
}
}
// IPC: レンダラープロセスからのコマンドを受信
ipcMain.handle('read-file', async (_event, filePath: string) => {
return fs.readFileSync(filePath, 'utf-8')
})
ipcMain.handle('open-dialog', async () => {
const result = await dialog.showOpenDialog(mainWindow!, {
filters: [{ name: 'テキスト', extensions: ['txt', 'md'] }],
})
return result.filePaths[0] || null
})
ipcMain.handle('show-notification', (_event, title: string, body: string) => {
new Notification({ title, body }).show()
})
app.whenReady().then(() => {
createWindow()
// システムトレイ
const icon = nativeImage.createFromPath(path.join(__dirname, 'icon.png'))
tray = new Tray(icon)
tray.setContextMenu(Menu.buildFromTemplate([
{ label: 'ウィンドウを表示', click: () => mainWindow?.show() },
{ label: '終了', role: 'quit' },
]))
})
Wails
2020年公開、Go製のOSSです。GitHubスター26k+。GoバックエンドとWebフロントエンドを組み合わせた軽量フレームワークで、Tauriより小さいバイナリ(約4MB)とGoのシンプルさが魅力です。
// main.go: Wailsバックエンド
package main
import (
"context"
"fmt"
"os"
"embed"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
//go:embed all:frontend/dist
var assets embed.FS
type App struct {
ctx context.Context
}
func (a *App) startup(ctx context.Context) { a.ctx = ctx }
func (a *App) ReadFile(path string) (string, error) {
data, err := os.ReadFile(path)
if err != nil {
return "", fmt.Errorf("ファイル読み込みエラー: %w", err)
}
return string(data), nil
}
func (a *App) OpenFileDialog() (string, error) {
return runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{
Filters: []runtime.FileFilter{{DisplayName: "Text Files", Pattern: "*.txt;*.md"}},
})
}
func main() {
app := &App{}
wails.Run(&options.App{
Title: "My Wails App",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{Assets: assets},
OnStartup: app.startup,
Bind: []interface{}{app},
})
}
機能比較表
| 比較項目 | Tauri | Electron | Wails |
|---|---|---|---|
| バイナリサイズ | ✅ ~6MB | ❌ ~200MB | ✅ ~4MB |
| バックエンド言語 | Rust | Node.js | Go |
| モバイル対応 | ✅(v2) | ❌ | ❌ |
| エコシステム | 成長中 | ✅ 最大 | 中 |
| GitHub Stars | 86k+ | 115k+ | 26k+ |
デスクトップアプリフレームワークはDevOpsカテゴリ/categories/devopsのCI/CD(GitHub Actions)でWindows・macOS・Linux向けバイナリをビルドして自動リリースするパイプラインと組み合わせて配布を自動化します。LLM Toolsカテゴリ/categories/llm-toolsのOllama・Whisperとローカルで統合してオフラインAIアシスタントデスクトップアプリを構築するパターンが注目されています。
FAQ
Q. TauriとElectronのバイナリサイズはなぜ違うのですか?
A. Electronは独自のChromiumランタイムをバンドルするのに対し、TauriはOSのシステムWebViewを使用するからです。Electronのバンドル内訳: Chromiumレンダリングエンジン(約150MB)+ Node.jsランタイム(約30MB)+ アプリコード(数MB)=合計200MB前後。Tauriのバンドル内訳: Rustバイナリ(約3〜4MB)+ アプリコード(数MB)=合計6MB前後。システムWebViewの違い: Windows→WebView2(Edge/Chromiumベース)、macOS→WKWebView(Safari/WebKitベース)、Linux→WebKitGTK。注意: システムWebViewはOSのバージョンに依存するためレイアウトの微差が生じる可能性があります。Electronはどの環境でも同一のChromiumで動作するため一貫性が高いです。
Q. ElectronアプリをmacOSでノータリゼーション(公証)するには?
A. Apple Developer Programへの加入($99/年)とelectron-builderのコード署名設定が必要です。手順: ①Apple Developer Programに登録②Xcodeでコード署名証明書(Developer ID Application)を発行③electron-builder.config.jsにmac: {identity: 'Developer ID Application: Your Name (XXXXXXXXXX)', notarize: {teamId: 'XXXXXXXXXX'}}を設定④npm run buildでビルド時に自動署名・ノータリゼーション・ステープリングが実行。CI(GitHub Actions): apple_id・apple_app_specific_password・apple_team_idをGitHub Secretsに設定してactions/macos-12ランナーで実行。Tauriのnotarize: tauri.conf.jsonのbundle.macOS.signing_identityとbundle.macOS.notarizeで同様に設定できます。
Q. Tauriアプリで自動アップデートを実装するには?
A. **tauri-plugin-updater(v2)またはElectronはelectron-updater**で実装します。Tauriアップデーター: ①npm install @tauri-apps/plugin-updater②tauri.conf.jsonのplugins.updater.endpointsにアップデートチェックURLを設定③Rustコード: tauri_plugin_updater::UpdaterExt::update_check()でアップデートを確認してインストール④GitHub Releasesをエンドポイントとして使用(update.jsonをリリースに添付)。Electronアップデーター: electron-updaterパッケージ+GitHub Releasesまたはカスタムサーバー→autoUpdater.checkForUpdatesAndNotify()で起動時に自動チェック・通知・インストール。
Q. WailsとTauriのどちらを選ぶべきですか?
A. Goに慣れているチーム・シンプルさ重視ならWails、Rust性能・モバイル将来対応・活発なコミュニティならTauriが向いています。Tauri優位: ①GitHubスター86k(Wailsの3倍)で知見・プラグインが豊富②v2でAndroid/iOS対応(Wailsは未対応)③Rustによるメモリ安全性保証。Wails優位: ①Goの学習コストが低くRustより習得しやすい②バイナリサイズが最小(4MB前後)③Goのnet/httpパッケージやgoroutineが活かせる。どちらもElectronより軽量でWeb技術でUIを構築できますが、エコシステムと採用実績ではTauriが優勢です。
まとめ
| ユースケース | 推奨ツール |
|---|---|
| 軽量・モバイル対応・Rust性能 | Tauri |
| VS Code級・Node.js・最大エコシステム | Electron |
| Go・最小バイナリ・シンプル | Wails |