【 Tauri 】Vanillaでのフロントエンド⇔バックエンドの通信方法まとめ

# Tauriとは

Rustのためのデスクトップアプリケーションフレームワーク。似たような技術にElectronがあるが、それに比べて何倍か速いらしい。

# 環境構築

Rustを最新版にする。

```bash
$ rustup update
```

Node.jsも最新版をインストール。バージョン管理ソフト`n`がおすすめ。

[Node.jsとnpmをアップデートする方法 | Rriver](https://parashuto.com/rriver/tools/updating-node-js-and-npm)

できたらアプリの雛形を作成。

```bash
$ npx create-tauri-app
```

対話形式で色々聞かれるので答えていくとアプリの雛形ができる。

途中で使うパッケージを聞かれるので自分の得意なやつを選ぶ。何もわからない人は`Vanilla.js`を選べばいいです（パッケージを使わない生`javascript`のこと）。

```bash
Your installation completed.
$ cd tauri-app
$ npm install
$ npm run tauri dev
```

こんな感じに出たらOKで、実際にこれらをやってみるとサンプルアプリが実行されるはず。

アプリの見た目は選んだプロジェクトによって違う。

今回はVanillaで色々やってみる（ReactもVueもなんもしらないから）。

# HTML/JSからtauriの機能を使う

`src-tauri/tairi.conf.json`の`build`セクションに`"withGlobalTauri" : true`を追加する。

vanilla以外では必要ないかも。Reactではimportしている（[はじめに｜Rust GUI の決定版！ Tauri を使ってクロスプラットフォームなデスクトップアプリを作ろう](https://zenn.dev/kumassy/books/6e518fe09a86b2/viewer/fd305b)）。他のフレームワークではわかりません。
これで`window.__TAURI__`プロパティ経由でtauriのAPIを使用できる。

```json
  "build": {
    "beforeDevCommand": "",
    "beforeBuildCommand": "",
    "distDir": "../dist",
    "devPath": "../dist",
    "withGlobalTauri": true
  },
```

# Rustの関数→HTML/JS

1. Rustで`#[tauri::command]`のattributeをつけた関数を作成。

2. mainでhandlerに登録

3. `window.__TAURI__`経由でinvokeして関数化

4. 好きな時に呼び出す

---

## 例：Rustでprintln!するだけの関数を作って、それをJavaScriptで呼び出す。

- **Rust**

`HTML/JS`側から呼び出す関数には`#[tauri::command]` という attribute をつける。

```rust
#[tauri::command]
fn print_command() {
    println!("Call from JavaScript");
}
```

これをmainでhandlerとして登録。

```rust
fn main() {
  tauri::Builder::default()
    .invoke_handler(tauri::generate_handler![
      print_command,
    ])
    .run(tauri::generate_context!())
    .expect("error while running tauri application");
}
```

- **HTML/JS**

JavaScriptでRustの関数を`invoke`する。Vanillaでは`window.__TAURI__`プロパティ経由でtauriのAPIを使用する。

```javascript
function print_command(){
    window.__TAURI__
        .invoke('print_command')
}
```

HTMLではどうにかしてfunctionを呼び出す。ButtonとonClickとか。

```html
<button onclick="print_command()"> println! </button>
```

ちなみにRust側の`println!`は`npm run tauri dev`のコンソールで表示されて、
`console.log()`はアプリを右クリックした時にでるメニューの`Inspact Element`→`Console`に表示される。

# HTML/JS→Rust間の引数の受け渡し

- 構文

```javascript
window.__TAURI__
        .invoke('Rust関数名', { 引数: 代入する値})
        .then(返り値 => {
            // 処理
        })
```

---

## 例：【String → String 】文字列反転

- **Rust**

handlerに追加するのを忘れずに！

```rust
#[tauri::command]
fn rev_string_command(s : String) -> String {
  let rev_s: String = s.chars().rev().collect();
  rev_s
}
```

- **HTML/JS**

JavaScriptでは入力文字列と反転した文字列を表示している。

- `display_Msg`：入力文字列を表示する`dev`

- `rev_display_Msg` : 反転文字列を表示する`dev`

- `Msg` :  入力された文字列

`window.__TAURI__`以下でRustの`rev_string_command()`をinvokeしている。引数`s`には`Msg`を代入、`rev_s`として返り値を受け取る。

`rev_display_Msg`の`textContent` に反転文字列を代入して表示。

```javascript
function change_text(){
    var display_Msg = document.getElementById('text-msg');
    var rev_display_Msg = document.getElementById('rev-text-msg');
    var Msg = document.getElementById('text_area').value;
    display_Msg.textContent = Msg;

    window.__TAURI__
        .invoke('rev_string_command', { s: Msg})
        .then(rev_s => {
            rev_display_Msg.textContent = rev_s;
        })
}
```

HTMLはイベントハンドラーをbuttonとinputにつけていい感じに。

```html
<input type="text" id="text_area" onchange="change_text()"></input>
<button onclick="change_text()">Change</button><br>
<lebel>入力文字列：<dev id="text-msg"></dev></lebel><br>
<label>反転文字列：<dev id="rev-text-msg"></dev></label>
```

# 引数・返り値での構造体(struct)の受け渡し

構造体は`Serialize`と`Deserialize`が実装されている時に受け渡しできる。

引数は`Deserialize`されてRustが受け取り、返り値が`Serialize`されてJavaScriptに受け渡される。

- **構文**

Rust

```rust
use serde::{ Serialize, Deserialize };

#[derive(Debug, Serialize, Deserialize)]
struct MyStruct {
    メンバ1 : 型,
    メンバ2 : 型,
    ...
}
```

JavaScrpt

```javascript
window.__TAURI__
    .invoke('Rust関数名',
        { 構造体名: {
            メンバ1: 引数1,
            メンバ2: 引数2,
        }})
    .then(返り値 => {
        //処理
    })
```

---

## 例：【Struct → Struct】構造体を渡して整形して返す

- **Rust**

`chat_command(text)`に入力された`ChatMesagge`型の引数は、

- `name` ： そのまま

- `lv` ： +1

- `message` : 「 \<name> : \<message>」

に変形されて返る。

```rust
#[derive(Debug, Serialize, Deserialize)]
struct ChatMessage {
    name: String,
    lv: u32,
    message:String
}

#[tauri::command]
fn chat_command(text: ChatMessage) -> ChatMessage {
  let username = text.name.clone();
  ChatMessage {
        name: text.name,
        lv: text.lv + 1,
        message: format!("{} : {}", username, text.message)
    }
}
```

- **JavaScript**

HTMLの要素に表示してるだけです。

```javascript
function chat_command(){
    var chat_name = document.getElementById("name_text_area").value;
    var chat_msg = document.getElementById("msg_text_area").value;
    var display_lv = document.getElementById("display_lv");
    var display_msg = document.getElementById("display_msg");
    var lv = 0;
    if(document.getElementById("display_lv").textContent != null){
        lv = Number(document.getElementById("display_lv").textContent);
    }

    window.__TAURI__
        .invoke('chat_command',
            { text: {
                name: chat_name,
                lv: lv,
                message: chat_msg
            }})
        .then(text => {
            display_lv.textContent = text.lv;
            display_msg.textContent = text.message;
        })
}
```

- **HTML**

マジで適当です。

```html
<label>名前：<br>
  <input type="text" id="name_text_area"></input>
</label>
<br>
<label>本文：<br>
  <textarea type="text" id="msg_text_area"
    cols="50" rows="5" onchange="chat_command()">
  </textarea>
</label>
<button onclick="chat_command()">送信</button> <br>
<label>Lv：<div id="display_lv"></div></label> <br>
<label><div id="display_msg"></div></label> <br>
```

# Result型の受け渡し

Result型でRustから返した値は、

- `.then( Okの値 ).catch( Errの値 )`

の形でJavaScriptで受け取ることができる。

---

## 例：【 i32  → Result\<String, String> 】 年齢の区分（？）の表示

- **Rust**

```rust
#[tauri::command]
fn age_command(age: i32) -> Result<String, String>{
  match age {
      0..=19 => Ok(format!("未成年")),
      20..=125 => Ok(format!("成人")),
      126.. => Err(format!("死んでいます")),
      _ => Err(format!("生まれてません")),
  }
}
```

- **JavaScript**

今回も同じようなことしかしてない。

```javascript
function age_command(){
    var age = parseInt(document.getElementById("age_text_area").value);
    var display_age = document.getElementById("display_age");
    console.log(age);
    if(isNaN(age)){
        display_age.textContent = "Error: 多分数字じゃないです";
    }else{
    window.__TAURI__
        .invoke('age_command', { age })
        .then(age_class => {
            display_age.textContent = age_class;
        })
        .catch(e => {
            display_age.textContent = e;
        })
    }
}
```

# Tauri API

Tauriではよく使われるCommandがAPIとして登録されている。公式ドキュメントがわかりやすいのでいくつか例を試して、公式ドキュメントを確認すれば、簡単に使えると思います。

https://tauri.studio/v1/api/js/

Vanillaではプロパティ経由でそれぞれのAPIを利用できる。

---

## 例：【dialog.open()】ファイル選択ダイアログを出し、ファイルのパスを console に表示。

```javascript
function file_dialog_command () {
    window.__TAURI__.dialog
        .open().then(files => console.log(files));
}
```

## 例：【window.appWindow.listen()】ウィンドウを動かすたびに座標をconsoleに表示（左上の座標）。

```javascript
window.__TAURI__.window
    .appWindow.listen('tauri://move', ({ event, payload }) => {
            const { x, y } = payload // payload here is a `PhysicalPosition`
            console.log('x', x);
            console.log('y', y);
        })
```

## 例：【dialog.ask(message, title)】Yes/Noダイアログを出し、選択をboolで受け取る。

```javascript
function ask_command() {
    window.__TAURI__.dialog
        .ask("質問", "タイトル")
        .then(ans => {
            console.log(ans); // true or false
        });
}
```

他のAPI詳しいの使い方は[公式ドキュメント](https://tauri.studio/v1/api/js/)を参照。

## API許可リスト

Tauri の API は許可制で、 `allowlist` に記載した API しか利用できないようになっている。`tauri.conf.json > tauri > allowlist`で編集できる。

デフォルトでは全て許可されている。

```json
"allowlist": {
   "all": true
},
```

例えば、`dialog.open()`のみを許可する場合はこうする。

```json
"allowlist": {
   "dialog": {
      "open": true
   }
},
```

`dialog`の全てを許可することもできる。

```javascript
"allowlist": {
   "dialog": {
      "all" : true
   }
},
```

# event APIでプロセス間通信

`window.__TAURI__.event`で使えるevent APIで任意のタイミングでHTML/JS側とRust側のやり取りができる

https://tauri.studio/v1/api/js/modules/event

JavaScript側では[`emit`](https://tauri.studio/v1/api/js/modules/event/#emit)を使い、Rust側では`tauri::Manager`の[`listen_global`](https://docs.rs/tauri/1.0.0-rc.9/tauri/trait.Manager.html#method.listen_global)をhookとして登録しておく。

---

## 例：JavaScript → Rust

- **Rust**

`emit(event, message)`を使う。eventは通信の識別子。

 [`setup(F)`](https://docs.rs/tauri/1.0.0-rc.11/tauri/struct.Builder.html#method.setup)はクロージャをとって`Self`を返す関数。`listen_global(event, F)`も引数のクロージャで処理を書く。`emit`で送られたmessageはクロージャの引数で受け取る。

```rust
use tauri::Manager;
fn main() {
    tauri::Builder::default()
        .setup(|app| {
            let id = app.listen_global("front-to-back", |event| {
                println!(
                    "got front-to-back with payload {:?}",
                    event.payload().unwrap()
                )
            });
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
```

- **JavaScript**

```javascript
function emitMessage_command() {
    window.__TAURI__.event
        .emit('front-to-back', "hello from front")
}
```

- **HTML**

```html
<button onclick="emitMessage_command()">エミット</button>
```

## 例：Rust → JavaScript

- **Rust**

Rust側で`Manager`に実装されている`emit_all`を使い、全てのフロントエンドのwindowにメッセージを送ることができる。

`emit_all(event, message)`の引数は`emit`と同じで識別子とメッセージ。

```rust
use tauri::Manager;

fn main() {
    tauri::Builder::default()
        .setup(|app| {
            let app_handle = app.app_handle();
            std::thread::spawn(move || loop {
                app_handle
                    .emit_all("back-to-front", "ping frontend".to_string())
                    .unwrap();
                std::thread::sleep(std::time::Duration::from_secs(1))
            });
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
```

完全にパクリです。[プロセス間通信をしてみよう｜Rust GUI の決定版！ Tauri を使ってクロスプラットフォームなデスクトップアプリを作ろう](https://zenn.dev/kumassy/books/6e518fe09a86b2/viewer/1dbeeb)

- **JavaScript**

JS側ではeventプロパティの`listen`を使うことで受け取ることができる。

```javascript
async function f() {
    await window.__TAURI__.event.listen('back-to-front', event => {
        console.log('back-to-front',event.payload,new Date());
    });
}
f();

```

# ファイルを使って任意のタイミングで通信

Rust側の並列処理でそれぞれのスレッドの処理が終わったタイミングでフロント側の表示を変更したいなど、`emit`や関数の引数では情報の受け渡しがうまくできない場合がある。

Rustでlogファイルを作り、スレッドごとにファイルに伝えたい情報を書く。
JavaScript側はファイルの情報を監視し、それをもとにフロントエンドを書き換える。

情報の受け渡しはこのシステムを使えば（並列処理に限らず）全て可能になる。

イメージ図。

![RustJSファイル通信.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2587116/b988607b-a166-b1e1-05bf-3260718aea72.png)


# JavaScriptでローカルファイルを読み込む

JSでlogfileを監視するためにはローカルファイルを読み込む必要がある。しかし、JSはもともとWebの技術なので、セキュリティの観点から任意のローカルファイルを読み込むのは非常に面倒（一応HTML5からユーザーがinputタグで選択したファイル、ディレクトリを読み込むことはできるが任意はめっちゃめんどい）。

なので、Rustで`read_file`コマンドを作ってJS側から呼び出すといい

- **Rust**

```rust
#[tauri::command]
fn read_file_command(path: String) -> Result<String, String>{
  let filepath = std::path::Path::new(&path);
  let content = match std::fs::read_to_string(filepath){
    Ok(content) => content,
    Err(e) => return Err(e.to_string()),
  };
  Ok(content)
}
```

- **JavaScript**

```javascript
function read_file_command(filepath) {
    return window.__TAURI__.invoke("read_file_command", {path: filepath});
}
```

# ビルド

簡単。

```bash
$ npm run tauri build
```

で終わり。

macだと`/{appname}/src-tauri/target/release/bundle/dmg`に.dmgで、`/{appname}/src-tauri/target/release/bundle/macos`に.appがあった。

## 注意 : Warningがあるとビルドできない！

RustのコードにWarningがあると、

```bash
Error running CLI: failed to bundle project: error running bundle_dmg.sh:
```

ってエラーが出てbuildができないので注意。`npm run tauri dev`はできるからハマりかけた。

# 参考文献

- [Rust GUI の決定版！ Tauri を使ってクロスプラットフォームなデスクトップアプリを作ろう](https://zenn.dev/kumassy/books/6e518fe09a86b2)

- [tauri - 公式ドキュメント](https://tauri.studio/v1/guides/getting-started/prerequisites)

- [tauri - Rust](https://docs.rs/tauri/1.0.0-rc.0/tauri/index.html)