1. やったこと
Tauri(Rust + React)でWindowsアプリを作成する為
Tauriの基本的な書き方を学びました
2. 技術選定
2-1. やりたいこと
以下を実施するツールを作ろうとしています
・今回作成するツールはWindows PCで利用したい
・ローカルストレージにある多数の画像ファイルをユーザーが眺めながら選択したい
・選んだ画像にコメントを記入していきたい
上記を踏まえて以下のような条件で選定します
・ローカルストレージのファイルを多数扱うのでネイティブアプリが良さそう
・どうせ覚えるならクロスプラットフォームなフレームワークが良い
・画像を選択したり色々するのだとリッチなGUIを作りたくなりそう
・別途ランタイムのインストールが必要になったりしないものが良い
2-2. 選択肢
以下を候補に考えました
・C++(WinAPI)
・.NET MAUI
・PyQt/PySide
・Electron
・Fyne
・Flutter
・ReactNative
・Tauri
2-3. 選定結果と選定理由
最終的にTauriを選定しました
Electron, Flutter, React-Nativeも候補に残してましたが
・Rustを書いてみたい
・Reactで書けてTailwindが使えるのが嬉しい
・ビルドがexeまたはmsiになって配布が容易
というのが決め手でTauriにしました
3. 開発環境を構築する
3-1. Node.jsをインストールする
フロントエンドをviteで扱う為にnodeが要ります
今回はWindows環境で開発するので公式のインストーラーを使います
https://nodejs.org/ja/download
3-2. Rustをインストールする
Rustもインストーラがあります
https://www.rust-lang.org/ja/tools/install
3-3. Visual Studioをインストールする
公式ページからインストーラーを入手します
https://visualstudio.microsoft.com/ja/free-developer-offers/

以上で準備完了です
4. アプリ作成手順
実際に作っていきましょう
現代的なフレームワークなので動くものが簡単に作成できます
4-1. プロジェクトの作成
プロジェクト作成
プロジェクトを作成します
npm create tauri-app
上記を実行すると、プロジェクト名をどうするか、フロントエンドを何で書くか(React, Vueなど)、バンドラを何にするか(npm, yarn, bunなど)などを聞かれます。今回はtauri-app, React, npmでやろうと思います
開発モードで起動
ReactやVueのように、開発モードで起動することが出来ます
作成された初期プロジェクトがそのままで動くようになっていて、以下コマンドで起動してGUIビューが表示されます
cd tauri-app
npm install
npm run tauri dev
しかもこの状態でホットリロードに対応しているので、フロントエンド部分を書き換えると表示に即時反映されます。開発エクスペリエンス良すぎて驚きます

実行ファイルにビルドする
以下でexeファイルがビルドされます
npm run tauri build
/tauri-app/src-tauri/target/release/tauri-app.exe として実行ファイルが作成されて、同梱ファイルが必要ない場合はこれだけで動きます。同梱がある場合は/tauri-app/src-tauri/target/release/bundle/内にインストーラーがビルドされるので、これを配布することができます
4-2. ディレクトリ構造
プロジェクトディレクトリは以下のようになります

フロントエンド
/src/以下にフロントエンドのファイルが作成されます
最初にmain.tsxが呼ばれます
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
main.tsxで呼ばれたApp.tsxで表示されている内容が書かれています
import { useState } from "react";
import reactLogo from "./assets/react.svg";
import { invoke } from "@tauri-apps/api/core";
import "./App.css";
function App() {
const [greetMsg, setGreetMsg] = useState("");
const [name, setName] = useState("");
async function greet() {
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
setGreetMsg(await invoke("greet", { name }));
}
return (
<main className="container">
<h1>Welcome to Tauri + React</h1>
<div className="row">
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" className="logo vite" alt="Vite logo" />
</a>
<a href="https://tauri.app" target="_blank">
<img src="/tauri.svg" className="logo tauri" alt="Tauri logo" />
</a>
<a href="https://reactjs.org" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<p>Click on the Tauri, Vite, and React logos to learn more.</p>
<form
className="row"
onSubmit={(e) => {
e.preventDefault();
greet();
}}
>
<input
id="greet-input"
onChange={(e) => setName(e.currentTarget.value)}
placeholder="Enter a name..."
/>
<button type="submit">Greet</button>
</form>
<p>{greetMsg}</p>
</main>
);
}
export default App;
ほぼReactそのままの書き方になっていて
Reactを知っていればすんなり読めると思います
一般的なReactと異なるのは3行目の
import { invoke } from "@tauri-apps/api/core";
の部分で、この1行でtauriバックエンドの関数を呼び出せるようになっています
バックエンド
/src-tauri/src/にバックエンドのコードがあります
main.rsでは2つの処理が書かれていて
2行目でリリースビルド時にコンソールウィンドウを表示しないように指示しており
4行目でlib.rsのrun()を実行します
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
tauri_app_lib::run()
}
/src-tauri/src/lib.rsでは、main.rsから呼ばれてTauriの基本機能部分をまかなうrun()と、フロントエンドからinvokeで呼ばれるバックエンド関数が定義されます
関数をinvokeから使えるようにするには、#[tauri::command]が付けてあって、かつrun()で.invoke_handler(tauri::generate_handler![greet])のように登録されている必要があり、明示的に指示する設計になっています
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
4-3. 簡単な変更
バックエンドに関数を追加
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
#[tauri::command]
fn greet_jp(name: &str) -> String {
format!("こんにちは、{}さん!Rustからご挨拶します!", name)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![greet, greet_jp])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
フロントエンドを書き換える
invokeしている関数をgreet_jpに変更します
async function greet() {
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
setGreetMsg(await invoke("greet_jp", { name }));
}

4-4. 画面の縦横幅を変更する
デフォルトの画面幅を設定する
/src-tauri/tauri.conf.jsonで画面サイズを変更できます
"app": {
"windows": [
{
"title": "tauri-app",
"width": 800,
"height": 300
}
動的に変更する
appWindow.setSizeで動的に変更できます
import { appWindow } from "@tauri-apps/api/window";
function ResizeButton() {
return (
<button
onClick={() =>
appWindow.setSize({ width: 1024, height: 768 })
}
>
サイズを1024x768に変更
</button>
);
}
4-5. 画面遷移する
Reactと同様にreact-router-domで画面遷移することができます
この場合、コンポーネントが置き換わるのでstateはクリアされます
routingを設定する
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import App1 from "./App1";
import App2 from "./App2";
import "./App.css";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/" element={<App1 />} />
<Route path="/app2" element={<App2 />} />
</Routes>
</BrowserRouter>
</React.StrictMode>,
);
navigateを設定する
import { useNavigate } from "react-router-dom";
import "./App.css";
export default function App() {
const navigate = useNavigate();
function goToApp2() {
navigate("/app2");
}
return (
<main className="container">
<h1>App1</h1>
<p>Click to another view.</p>
<button onClick={goToApp2}>App2へ移動</button>
</main>
);
}
import { useNavigate } from "react-router-dom";
import "./App.css";
export default function App() {
const navigate = useNavigate();
function goToApp1() {
navigate("/");
}
return (
<main className="container">
<h1>App2</h1>
<p>Click to another view.</p>
<button onClick={goToApp1}>App1へ移動</button>
</main>
);
}


できました
4-6. Tailwindを使う
Tailwindも使えます
Viteでレンダリングするのでその辺で上手くやってくれるようです
以下の手順が上手くいきました
https://tailwindcss.com/docs/installation/using-vite
npm install tailwindcss @tailwindcss/vite
@import "tailwindcss";
import "./App.css";
export default function App() {
return (
<main className="container">
<h1 className="text-red-500 text-5xl">Welcome to Tauri + React</h1>
</main>
);
}

5. まとめ
全体にかなり好感触です
・とりあえず動くところまでがすごく簡単
・Tauriプロジェクトの設計がキレイで分かりやすい
・開発モードのホットリロードが快適
・やっぱりReactで書けるのは嬉しい
・TypeScriptで書けるのも嬉しい
・Tailwindも使えちゃう
・Win配布ファイルがexe, msiだけなのが嬉しい
・モバイル版もビルドできる
レッツトライ