🐥

TauriとLeptosで作るデスクトップアプリ(2)カウンタコンポーネントを作成する

2024/08/18に公開

はじめに

TauriとLeptosでデスクトップアプリを作っていきます。今回は、シンプルなカウンタを作成します。

関連記事
環境
  • Windows 11 Home
  • Rust 1.80
  • Tauri 2.0.0-rc

https://v2.tauri.app/

  • Leptos 0.7.0-beta

https://leptos.dev/

コンポーネントライブラリThaw UIを導入する

Leptosのコンポーネントライブラリはいくつかありますが、個人的にデザインが気に入っているThaw UIを使います。

https://thawui.vercel.app/

./apps/src-ui/Cargo.toml[dependencies]thawを追加します。

./apps/src-ui/Cargo.toml
# 略

[dependencies]
console_error_panic_hook = "0.1.7"
leptos = { version = "0.7.0-beta2", features = ["csr"] }
thaw = { version = "0.4.0-beta", features = ["csr"] }

VS Codeにフォーマッタを導入する

Leptosではview!マクロを用いてRustソースコードの中にHTMLを書いていきます。VS Codeでソースコードを編集するなら、フォーマッタleptosfmtを導入すると便利です。

https://github.com/bram209/leptosfmt

leptosfmtをインストールします。

> cargo install leptosfmt

以下の2つのファイルを新規作成します。

./.vscode/settings.json
{
  "rust-analyzer.rustfmt.overrideCommand": ["leptosfmt", "--stdin", "--rustfmt"]
}
./rustfmt.toml
edition = "2021"

カウンタコンポーネントを作成する

Leptosフロントエンドクレート./apps/src-uiにコンポーネントをまとめたモジュールcomponentsを作ります。その中にサブモジュールcounterを作ります。ディレクトリ構成は以下のとおりです。

tauri-leptos-example
├─ apps/
│  ├─ src-tauri/
│  └─ src-ui/
│     ├─ src/
│     │  ├─ components/
│     │  │  ├─ counter.rs
│     │  │  └─ mod.rs
│     │  ├─ app.rs
│     │  ├─ lib.rs
:     :  └─ main.rs

mod.rsは単純にサブモジュールcounterを宣言するだけです。

./apps/src-ui/src/components/mod.rs
pub mod counter;

counter.rsにカウンタを実装します。Leptos公式ページの例を参考にしました。

https://github.com/leptos-rs/leptos/tree/main/examples/counter

./apps/src-ui/src/components/counter.rs
use leptos::prelude::*;
use thaw::{Button, Tag};

#[component]
pub fn SimpleCounter(
    /// The starting value for the counter
    initial_value: i32,
    /// The change that should be applied each time the button is clicked.
    step: i32,
) -> impl IntoView {
    let value = RwSignal::new(initial_value);
    let dec = move |_| *value.write() -= step;
    let inc = move |_| value.update(|value| *value += step);

    view! {
        <div>
            <Button on_click=dec>"-1"</Button>
            <Tag>"Value: " {value} "!"</Tag>
            <Button on_click=inc>"+1"</Button>
            <Button on_click=move |_| value.set(0)>"Clear"</Button>
        </div>
    }
}

<Button>コンポーネントのon_click属性でボタンがクリックされたときの動作を指定します。RwSignalのメソッドを使っています。[+1]ボタンと[-1]ボタンであえて異なった記述をしています。[Clear]ボタンではクロージャをview!マクロの中に直に書いています。

SimpleCounterコンポーネントをアプリのAppコンポーネント内に配置します。

./apps/src-ui/src/app.rs
use leptos::prelude::*;
use thaw::ConfigProvider;

use crate::components::counter::SimpleCounter;

#[component]
pub fn App() -> impl IntoView {
    view! {
        <ConfigProvider>
            <SimpleCounter initial_value=0 step=1 />
        </ConfigProvider>
    }
}

ConfigProviderコンポーネントで全体を囲うことで、中のコンポーネントにThaw UIのスタイルが適用されます。SimpleCounterコンポーネントは属性を2つ取ります。コンポーネントの定義における引数が、そのまま属性となります。初期値0、ステップ1を指定しています。

ライブラリクレートのクレートルートlib.rsに、モジュールcomponentsの宣言を追加します。

./apps/src-ui/src/lib.rs
use app::App;
use leptos::prelude::*;

mod app;
pub mod components;

pub fn run() {
    console_error_panic_hook::set_once();
    mount_to_body(App)
}

準備が整いました。プロジェクトを実行します。

> cargo tauri dev

カウンタがThaw UIライブラリのスタイルで表示され、ボタンが期待通りに動作すれば完成です。

counter

カウンタコンポーネントをテストする

wasm-packを使ってカウンタコンポーネントの動作をテストします。

https://rustwasm.github.io/wasm-pack/

テストコードはLeptos公式サイトを参考にしました。

https://github.com/leptos-rs/leptos/tree/main/examples/counter_without_macros

まず、./apps/src-ui/Cargo.toml[dev-dependencies]にテスト用のパッケージを追加します。

./apps/src-ui/Cargo.toml
# 略

[dev-dependencies]
wasm-bindgen = "0.2.93"
wasm-bindgen-test = "0.3.43"

[dev-dependencies.web-sys]
features = ["HtmlElement", "XPathResult"]
version = "0.3.70"

テスト用ディレクトリtestsを作成します。

tauri-leptos-example
├─ apps/
│  ├─ src-tauri/
│  └─ src-ui/
│     ├─ src/
│     ├─ tests/
│     │  ├─ common/
│     │  │  └─ mod.rs
:     :  └─ counter.rs

他のコンポーネントでも使う可能性のあるコードをcommonモジュールにまとめておきます。

./apps/src-ui/tests/common/mod.rs
use leptos::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::HtmlElement;

pub fn find_by_text(text: &str) -> HtmlElement {
    let xpath = format!("//*[text()='{}']", text);
    let document = document();
    document
        .evaluate(&xpath, &document)
        .unwrap()
        .iterate_next()
        .unwrap()
        .unwrap()
        .dyn_into::<HtmlElement>()
        .unwrap()
}

pub fn click_text(text: &str) {
    find_by_text(text).click();
}

カウンタコンポーネント用のテストコードは以下になります。

./apps/src-ui/tests/counter.rs
use leptos::{prelude::*, spawn::tick};
use wasm_bindgen_test::*;

use src_ui_lib::components::counter::SimpleCounter;

mod common;

wasm_bindgen_test_configure!(run_in_browser);

#[wasm_bindgen_test]
async fn increment() {
    open_counter();
    common::click_text("+1");
    common::click_text("+1");
    tick().await;

    assert_eq!(get_text(), Some("Value: 12!".to_string()));
}

#[wasm_bindgen_test]
async fn decrement() {
    open_counter();
    common::click_text("-1");
    common::click_text("-1");
    tick().await;

    assert_eq!(get_text(), Some("Value: 8!".to_string()));
}

#[wasm_bindgen_test]
async fn clear() {
    open_counter();
    common::click_text("Clear");
    tick().await;

    assert_eq!(get_text(), Some("Value: 0!".to_string()));
}

fn open_counter() {
    if let Some(top_div) = document().query_selector("body div").unwrap() {
        top_div.remove();
    }
    mount_to_body(move || view! { <SimpleCounter initial_value=10 step=1 /> });
}

fn get_text() -> Option<String> {
    common::find_by_text("Value: ").text_content()
}

wasm-packをインストールします。

> cargo install wasm-pack

テストを実行します。

> wasm-pack test --chrome apps/src-ui --test counter

--chromeオプションを指定することによって、Chromeでテストを行います。Chromeがインストールされている必要があります。他には--node, --firefox, --safariが選択できるようです。apps/src-uiはテストしたいクレートのディレクトリです。--testオプションでテスト対象を選びます。今回はコンポーネントが1つしかないので必要ありませんが、複数あるときには指定が必要です。cargo testコマンドと違って、1つのテストファイルしかテストが実行されないためです。(一度に複数のテストをする方法があるかもしれません。)

テストを実行してから、ブラウザでhttp://127.0.0.1:8000を開きます。下のようにテストにパスしたら成功です。

test

--headlessオプションを使うことで、ブラウザを開くことなくテスト結果を確認できます。また、このときにはtestsディレクトリにある全ファイルを一度にテストできるため、--testを指定することは必須ではありません。

> wasm-pack test --chrome --headless apps/src-ui
[INFO]: Checking for the Wasm target...
   Compiling src-ui v0.2.0 (...)
:
test result: ok. 3 passed; ...

サンプルコード

今回作成したプロジェクトは以下からダウンロード可能です。

https://github.com/daizutabi/tauri-leptos-example/tree/0.2

GitHubで編集を提案

Discussion