[!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.22
cargo add specta@=2.0.0-rc.12
cargo add tauri-specta@=2.0.0-rc.11 --features javascript,typescript
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,
}
#[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}!")
}
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");
}
import * as commands from "./bindings"; // This should point to the file we export from Rust
await commands.greet("Brendan");
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);
+ Ok(())
+ })
.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")
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 probaly 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.
It may be useful to export a constant from your Rust into your Typescript. You can do this like the following:
let builder = ts::builder()
// < your commands and events are probaly here
.types(StaticCollection::default().register("myConstant", 42)); // < call `register` as much as you want.
This value must implement serde::Serialize
.
It’s very common that your are using a linting or formatting tool on your codebase and it’s likely that the output of Tauri Specta will not match your style. You can configure the header of the file like the following to solve this:
let builder = ts::builder()
// < your commands and events are probally here
.header("// @ts-nocheck");