Tauri を使ってみる
pnpm create tauri-app
で雛形生成。
✔ Project name · tauri-example
✔ Choose which language to use for your frontend · TypeScript / JavaScript - (pnpm, yarn, npm, bun)
✔ Choose your package manager · pnpm
✔ Choose your UI template · React - (https://react.dev/)
✔ Choose your UI flavor · TypeScript
プロジェクトのディレクトリが生成される。
以下のコマンドを叩けば、アプリが立ち上がる。
cd tauri-example
pnpm install
pnpm tauri dev
Web (JS) と Rust の連携には Command
という仕組みを使う。
関数のアノテーションに #[tauri::command]
を追加して Command
を宣言する。
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
#[tauri::command]
fn my_custom_command() {
println!("I was invoked from JS!");
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![my_custom_command])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
JS (TS) から呼び出す。
import { invoke } from '@tauri-apps/api/tauri'
invoke('my_custom_command')
ちなみに Rust の style guide では、インデントは半角スペース 4 個がマストとされているらしい。
Each level of indentation must be 4 spaces (that is, all indentation outside of string literals and comments must be a multiple of 4).
https://github.com/rust-lang/rust/tree/HEAD/src/doc/style-guide/src#formatting-conventions
引数と返り値
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
#[tauri::command(rename_all = "snake_case")]
fn greet(invoke_message: String) -> String {
format!("{} World!", invoke_message)
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
JS (React) 側から greet
を呼び出す。
import { useEffect, useState } from "react";
import { invoke } from "@tauri-apps/api/tauri";
function App() {
const [message, setMessage] = useState('');
useEffect(() => {
(async () => {
const message = await invoke<string>('greet', { invoke_message: 'Hello' })
setMessage(message);
})();
}, [])
return (
<div>
<h1>{message}</h1>
</div>
);
}
export default App;
Rust 側へは JSON オブジェクトとして渡す。デフォルトの場合、Rust 側でスネークケースを使って引数を宣言すると、JS 側ではキャメルケースに変換される。つまり invoke('greet', { invokeMessage: 'hello' })
とする必要がある。Command
のアノテーションで #[tauri::command(rename_all = "snake_case")]
と宣言すればスネークケースのままになる。
Python をバックエンドとして組み込む
Python で書いたスクリプトを Tauri アプリのバックエンドとして組み込んでみる。
Python スクリプトは src-python
以下に適当なものを用意。
print("Hello from Python")
package.json に下記を追加。
"scripts": {
"setup-python": "python -m venv .venv && . ./.venv/bin/activate && pip install -r requirements.txt",
"build-python": ". ./.venv/bin/activate && pyinstaller -F src-python/test.py --distpath src-tauri/bin --clean -n test-aarch64-apple-darwin"
}
仮想環境を用意し、その環境下で pyinstaller
をインストール。pyinstaller
で Python スクリプトを実行可能なファイルにビルドする。
tauri.conf.json
を編集する。
{
"tauri": {
"allowlist": {
"shell": {
"sidecar": true,
"scope": [
{
"name": "bin/test",
"sidecar": true,
"args": true
}
]
}
},
"bundle": {
"externalBin": [
"bin/test"
]
},
}
}
あとは、フロント側から Command
経由でバイナリを呼び出せばよい。
import { useEffect, useState } from "react";
import { Command } from '@tauri-apps/api/shell';
function App() {
const [message, setMessage] = useState("");
useEffect(() => {
(async () => {
const command = Command.sidecar("bin/test")
const output = await command.execute();
setMessage(output.stdout);
})();
}, [])
return (
<div>
<h1>{message}</h1>
</div>
);
}
export default App;
データの永続化
設定ファイルのようなものをつくる必要があったのだけど、このプラグインで対応できた。
使い方もシンプル。
import { Store } from "tauri-plugin-store-api";
const store = new Store(".settings.dat");
await store.set("some-key", { value: 5 });
const val = await store.get("some-key");
assert(val, { value: 5 });
await store.save(); // this manually saves the store, otherwise the store is only saved when your app is closed
これでもたぶんいけそう。試してはいない。