結局のところデータ保存したいですよね。
前回にタイルエディターを作ったような気がするのですが、データ保存が出来なきゃ意味がないのですよ。
Node.js に同じみな人に言うならfs
みたいなのが欲しいですよね。
fs-plugin でデータを保存する
Tauri の File System という機能は、Plugin という形で提供されています。
そのため、まずはインストールする必要があります。
$ pnpm tauri add fs
これで、fs
のパッケージのインストールと Rust 側のコード調整が自動で行われます。
適当にコードを書いてお試ししてみましょう。
import { exists, BaseDirectory } from "@tauri-apps/api/fs";
console.log(await exists("test.txt", { baseDir: BaseDirectory.AppData }));
というようなコードが Docs で紹介されているので説明してみましょう。
今回はexists
とBaseDirectory
というものをインポートしています。
exists
はファイルが存在するかどうかを確認する関数なのですが、親ディレクトリを指定する必要があります。
また、セキュリティ上の理由からパスは後述のbaseDir
からの相対パス(親ディレクトリに行くことは出来ない)で指定する必要があります。
(path.join
というものを使うことで指定しなくてもよい)
でそのbaseDir
ですが、BaseDirectory
から生える列挙型から選ぶことが出来ます。
ここら辺は、どこのフォルダにアクセスする必要があるのかで変わってきますね。
...で、なんとなくわかったかもしれないですが、あんまり自由度が高いわけではないというか...
好きな場所の好きなファイルを操作することはできないですね。多分。
また、セキュリティ上の理由からpermission
にパスを書き込まなければならず、少しそこらへんもめんどくさいです。
Rustで書けばいいじゃね?(原点回帰)
簡単な話、フロントエンドはセキュリティ上の理由から制限されています。
ですがバックエンドはそこまで制限されていないので、書くことが出来ます。
が、何のためにフロントエンドが制限されているのかだけは考えておく必要がありますよね...?
というわけで、Rust...よりTauri的な部分にフォーカスを当てていきましょう
tauri-specta
を使ってみる
tauri-specta
は、Rustで定義した関数...特にコマンドをフロントエンド上で勝手に定義してくれて、楽に呼び出せるようにするライブラリというかツールというかです。
という訳でインストールしていきましょう。
インストール
基本はドキュメントに従っていきます。
$ cd ./src-tauri
$ cargo add specta@=2.0.0-rc.20
$ cargo add specta-typescript@0.0.7
$ cargo add tauri-specta@=2.0.0-rc.20 --features derive,typescript
これでtauri-specta
とそれに必要なspecta
、specta-typescript
がインストールされます。
使ってみる
TauriのRust側のコードはsrc-tauri/src/lib.rs
に書かれています。
多分何も触ってなかったらこんな感じなのかな?
// 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_shell::init())
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
このgreet
関数が、フロントエンドから呼び出されるコマンドなのは、だいぶ前に紹介しましたね。
まずはこの関数に新しいマクロとしてspecta::specta
を追加してみましょう。
#[tauri::command]
#[specta::specta]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
そのうえで、run
関数の中でコマンドをなんか色々する処理を書きましょう。
全部でこんな感じです。
+ use specta_typescript::Typescript;
+ use tauri_specta;
#[tauri::command]
+ #[specta::specta]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
+ let builder =
+ tauri_specta::Builder::<tauri::Wry>::new().commands(tauri_specta::collect_commands![greet]);
+ builder
+ .export(Typescript::default(), "../src/binding.ts")
+ .expect("failed to export typescript bindings");
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
- .invoke_handler(tauri::generate_handler![greet])
+ .invoke_handler(builder.invoke_handler())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
これで、Tauri起動時に../src/binding.ts
に TypeScript の型定義とかが書き出されるようになります。
(src-tauri
からの相対パスなので、../
がついている)
まぁまずはpnpm tauri dev
でTauriを起動してみましょう。
起動したら閉じても大丈夫です。
そうすると/src/binding.ts
に TypeScript の型定義が書き出されているはずです。
...
export const commands = {
async greet(name: string) : Promise<string> {
return await TAURI_INVOKE("greet", { name });
}
}
...
大事なのはここですかね。
commands
の中で、Rustで定義したgreet
関数があります。
中身はもちろんinvoke
で呼び出しているのですが、関数に型などもついているので、TypeScript から呼び出すときに便利ですよね。
実際にこれを使うときは、こんな感じです。
import { commands } from "../binding.ts";
console.log(await commands.greet("Anonymouse"));
commands
をimportして、greet
関数を呼び出しています。
ちなみに、RustにはResult
という型がありますが、これもしっかり再現出来るようになっており、
TypeScriptではbinding.ts
内にResult<T, E>
という型が定義されています。
Result
のstatus
というプロパティがok
かerror
かで、ok
の場合はdata
にTが入り、error
の場合はerror
にEが入ります。
というわけで、tauri-specta
を使ってみると、Rustのコマンドを簡単にフロントエンドから呼び出すことが出来るようになります。
コード紹介は?
しないのよ~
セキュリティ上の制限が突破された今、Rustでファイルを扱うコードについては普通に調べれば出てきちゃうので、省略します。
タイルのリストをコマンドに渡して後はそれをシリアライズしてファイルに書き込むとか、そういう感じですね。
特定のキーを押したときにそれを発動させればもう完璧ですもんね。
次回は、記事冒頭で紹介したようなプラグインと呼ばれるやつをいろいろ紹介してみたいと思います。