Introduction :
If you have ever worked with Tauri, you probably already used the tauri::command
macro but after a few commands you saw that you needed to write each function manually inside of the invoke_handler
, that seemed a bit tedious but acceptable.
However the more my project grew personally the harder it became to manage hundreds of commands; writing them, remembering you wrote them, even modifying the name of the function was annoying. After a few weeks I stumbled upon specta
, a Rust crate that allowed me to pass on Struct as TS Types, and Functions with their right args, etc...
BUT misery struck again and I saw that I had to rewrite all the names functions that I had inside of another invoke_handler
, that could have been solved by just copy pasting but I had enough of all this boilerplate and of my code being hundreds of lines because of such a simple thing so I decided once and for all to write a small Rust crate,tauri-helper
, that would help us, developers, write Rust code in Tauri quickly and without all these useless lines.
The Problem I Encountoured
While building tauri-helper
, I ran into a bunch of Rust issues that made development a bit trickier:
Macro limitations: Sadly, Rust macros can’t know the exact location of a function in a module or workspace. That meant if I wanted to pass functions around, I had to fully import the module, e.g.,
use some_module::*;
. This made writing the collection macros more complicated than expected, even though it will be possible in the near future to do that without importing the whole module, it is still in nightly and I wanted to avoid that.Multiple workspaces: Collecting commands across several crates in a workspace was messy. Each crate had its own modules and paths, so I had to make the macros workspace-aware and handle cross-crate function discovery that noticed every change to a function or the creation of a function to be able to note it down.
File structure and
build.rs
: To generate the command list automatically, I ended up writing generated files in a separate folder usingbuild.rs
. It worked, but I had to carefully manage paths and ensure everything stayed in sync when functions moved or were renamed.
Each of these challenges required careful design and testing to make tauri-helper
reliable and easy to use for others since well, it is the goal of this crate.
Tauri-Helper Usage
Once I solved the development issues, using the crate became way more straightforward and made tauri simpler to use :
Installation
Add tauri-helper
to your Cargo.toml
:
[dependencies]
tauri-helper = "0.1.4"
If you want to use the WithLogging
macro with tracing
, enable the tracing
feature:
[dependencies]
tauri-helper = { version = "0.1.4", features = ["tracing"] }
Then add it to the [build-dependencies]
:
[build-dependencies]
tauri-helper = "0.1.4"
IMPORTANT
Before using any command collection, you have to add this to the build.rs
file.
fn main() {
tauri_helper::generate_command_file(tauri_helper::TauriHelperOptions::default());
tauri_build::build();
}
And then be sure your workspace is correct and has the current crate defined in your Cargo.toml
such as this :
[workspace]
members = [
".",
"local-crates/some-commands1",
"local-crates/some-commands2",
"local-crates/some-commands3",
]
Don't forget to add
.
as a member or else the crate will not be able to get the commands from the default crate.
Usage
Command Collection
Annotate your Tauri command functions with #[auto_collect_command]
to automatically collect them:
#[tauri::command]
#[auto_collect_command]
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}
Generate a tauri::generate_handler!
invocation:
tauri_collect_commands!();
Generate a tauri_specta::collect_commands!
invocation:
specta_collect_commands!();
Note
If you do not want to have to annotate every command with #[auto_collect_command]
, you can do this in the build.rs
.
fn main() {
tauri_helper::generate_command_file(tauri_helper::TauriHelperOptions::new(true));
tauri_build::build();
}
This will tell the build script to get every tauri_command available in every member of the workspace.
This is not recommended as it can lead to adding functions that are not meant to be exported.
If your workspace contains multiple crates, you must export all functions in the root file (lib.rs
) of each crate.
Example
In my_commands.rs
:
#[tauri::command]
#[auto_collect_command]
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}
In lib.rs
:
pub mod my_commands;
pub use my_commands::*;
Note: This is required because the feature that enables full module path retrieval is still only available in the nightly version of Rust.
Example
Here’s a complete example of using tauri_helper
in a Tauri application:
#[tauri::command]
#[auto_collect_command]
fn greet(name: String) -> String {
format!("Hello, {}!", name)
}
#[tauri::command]
#[auto_collect_command]
fn calculate_sum(a: i32, b: i32) -> i32 {
a + b
}
fn main() {
let builder: tauri_specta::Builder = tauri_specta::Builder::<tauri::Wry>::new()
.commands(specta_collect_commands!());
#[cfg(debug_assertions)]
builder
.export(
Typescript::default().bigint(specta_typescript::BigIntExportBehavior::Number),
"../src/bindings.ts",
)
.expect("should work");
tauri::Builder::default()
.invoke_handler(tauri_collect_commands!())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Conclusion
Creating tauri-helper
wasn’t as easy as expected honestly, especially for a first project, Rust macros, multi-crate workspaces, and build-time code generation all posed major issues during the development. But solving them resulted in a small, robust crate that simplifies at least a bit command collection in Tauri projects.
If you’re building Tauri apps and tired of boilerplate, tauri-helper
lets you write Rust code efficiently while keeping command registration automatic and organized and let you avoid this hellish thousands of lines lib.rs
that is a nightmare to work with.
Not because it is my crate but I honestly use it on EVERY new Tauri project I start, it's just that useful.
A small side note for Rust devs: with Rust 1.90.0, cargo publish --workspace
was introduced, this makes publishing multi-crate workspaces much easier, which simplify quite a lot the publishing of multi-crate workspaces and will make tauri-helper
even more useful since you can now create as many crates as you want without the headache of publishing them. It’s not critical for using the crate, but it’s a nice quality-of-life improvement for developers managing multi-crate projects.
Top comments (0)