Tauri Tips - #1 invoke_handler()
I’ve been working on a desktop application built with Tauri for the last few months. It’s been a great experience so far. Most of my application functionality lives in Rust and I find the language a joy to use (at least so far).
One of the downsides of Tauri compared to Electron is that there isn’t nearly as much documentation and discussion around good patterns and best practices. My approach has been to find large, open-source codebases and survey how they are solving the small paper cuts that I have run into.
One of the things that annoyed me (admittedly primarily for aesthetic reasons) was having all of my commands listed right in the middle of my lib.rs.
Instead of API endpoints, Tauri uses the concept of commands. These are Rust
functions called in JavaScript via
ipc, each of which
must be registered by calling .invoke_handler() on a giant list of their fully
qualified names. As you can imagine, this list can get pretty big.
At this stage, I didn’t even have that many commands and it was already
cluttering up the run() function making it difficult to parse what was
happening before and after that block.
One approach I found was in
Tolaria,
which moves the list of commands into another macro using macro_rules!. At
first this seemed promising, but losing syntax highlighting and autocompletion
proved too painful.
One afternoon I was annoyed enough to dig a bit deeper.
To register commands in the Tauri::builder(), you call the invoke_handler()
method on it. This takes a function with the following signature:
#[must_use]
pub fn invoke_handler<F>(mut self, invoke_handler: F) -> Self
where
F: Fn(Invoke<R>) -> bool + Send + Sync + 'static,
{
self.invoke_handler = Box::new(invoke_handler);
self
}
Tauri also provides a handy macro - tauri::generate_handler! - that takes a list
of command handlers and returns a function with that signature.
With that knowledge, it becomes pretty straightforward to wrap this in our own function and use the macro to generate the function body.
The end result looks something like this:
// in a module called commands, but
// could live anywhere in your application
pub fn app_invoke_handler<R: Runtime>() -> impl Fn(Invoke<R>) -> bool + Send + Sync + 'static {
tauri::generate_handler![
episodes::get_episode,
episodes::get_episode_with_relations,
// etc...
]
}
// in /src-tauri/lib.rs
pub fn run() {
tauri::Builder::default()
.invoke_handler(commands::app_invoke_handler());
}
Now our commands can live anywhere in the application as long as they are
visible to our app_invoke_handler() and our command registration takes place
in a single dedicated module with syntax highlighting and autocompletion.
If this annoyed you too, hopefully you find this helpful!