Skip to content

Latest commit

 

History

History
195 lines (147 loc) · 5.64 KB

v2.md

File metadata and controls

195 lines (147 loc) · 5.64 KB

Tauri Specta v2

Installation

Warning

Tauri Specta v2 is still in beta, until Tauri v2 lands as stable.

However, it's safe to use as long as you lock your versions.

cargo add tauri@=2.0.0-beta.17
cargo add specta@=2.0.0-rc.12
cargo add tauri-specta@=2.0.0-rc.5 --features javascript,typescript

Adding Specta to custom types

use specta::Type;
use serde::{Deserialize, Serialize};

// The `specta::Type` macro allows us to understand your types
// We implement `specta::Type` on primitive types for you.
// If you want to use a type from an external crate you may need to enable the feature on Specta.
#[derive(Serialize, Type)]
pub struct MyCustomReturnType {
    pub some_field: String,
}

#[derive(Deserialize, Type)]
pub struct MyCustomArgumentType {
    pub foo: String,
    pub bar: i32,
}

Annotate your Tauri commands with Specta

#[tauri::command]
#[specta::specta] // <-- This bit here
fn greet3() -> MyCustomReturnType {
    MyCustomReturnType {
        some_field: "Hello World".into(),
    }
}

#[tauri::command]
#[specta::specta] // <-- This bit here
fn greet(name: String) -> String {
  format!("Hello {name}!")
}

Export your bindings

use specta::collect_types;
use tauri_specta::{ts, js};

// this example exports your types on startup when in debug mode. You can do whatever.

fn main() {
    let invoke_handler = {
        // You can use `tauri_specta::js::builder` for exporting JS Doc instead of Typescript!`
        let builder = tauri_specta::ts::builder()
            .commands(tauri_specta::collect_commands![greet, greet2, greet3 ]); // <- Each of your commands


        #[cfg(debug_assertions)] // <- Only export on non-release builds
        let builder = builder.path("../src/bindings.ts");

        builder.build().unwrap()
    };

    tauri::Builder::default()
        .invoke_handler(invoke_handler)
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Usage on frontend

import * as commands from "./bindings"; // This should point to the file we export from Rust

await commands.greet("Brendan");

Events

You must reconfigure your builder to support events:

fn main() {
-    let invoke_handler = {
+    let (invoke_handler, register_events) = {
        // You can use `tauri_specta::js::builder` for exporting JS Doc instead of Typescript!`
        let builder = tauri_specta::ts::builder()
            .commands(tauri_specta::collect_commands![greet, greet2, greet3 ]) // <- Each of your commands
+           .events(tauri_specta::collect_events![]); // This should contain all your events.

        #[cfg(debug_assertions)] // <- Only export on non-release builds
        let builder = builder.path("../src/bindings.ts");

        builder.build().unwrap()
    };

    tauri::Builder::default()
        .invoke_handler(invoke_handler)
+       .setup(|app| {
+           register_events(app);
+       })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Now you can add your first event. You can add as many of these as your want.

#[derive(Debug, Clone, Serialize, Deserialize, specta::Type, tauri_specta::Event)]
pub struct DemoEvent(String);

and make sure you register the event with Tauri Specta by adding it to the collect_events macro like the following:

.events(tauri_specta::collect_events![DemoEvent]); // This should contain all your events, comma separated.

Finally, you can setup a listener or emit a message from Rust like the following:

tauri::Builder::default()
    .invoke_handler(invoke_handler)
    .setup(|app| {
        let handle = app.handle();

        DemoEvent::listen_global(&handle, |event| {
            dbg!(event.payload);
        });

        DemoEvent("Test".to_string()).emit_all(&handle).unwrap();
    })
    ...

Most methods take a handle which can be any of the following types:

The Event trait defines all methods that can be used to emit or listen so refer to it.

and it can be used in TS like the following:

import { commands, events } from "./bindings";
import { appWindow } from "@tauri-apps/api/window";

// For all windows
events.demoEvent.listen((e) => console.log(e));

// For a single window
events.demoEvent(appWindow).listen((e) => console.log(e));

// Emit to the backend and all windows
await events.demoEvent.emit("Test")

// Emit to a window
await events.demoEvent(appWindow).emit("Test")

Extra Types

Sometimes you might want to export a type to Typescript but it doesn't actually show up in any of our commands.

You can do that like the following:

let builder = ts::builder()
    // < your commands and events are probally here
    .types(TypeCollection::default().register::<Custom>()); // < call `register` as much as you want.

register only supports named types as otherwise you would run into the following problem:

//     vvv      - What would this be without a name?
export ... = {};

Any type implemented using the Type derive macro will meet this requirement.