#Understanding when to invoke native code vs running in the "frontend"

11 messages · Page 1 of 1 (latest)

fleet grove
#

Hi! I'm having a hard time understanding the "idiomatic" way to split between implementing things in the frontend (either in JS or webassembly) and the "backend" / raw rust code. In my mind, most things could be done in pure frontend, like an electron app. And thanks to webassembly the performance shouldn't even be that bad, right? So when is it appropriate to use commands to call into native code? Whats the benefit?

This is not a technical question, I'm mostly looking to gain insight / a feeling for the idiomatic way to use tauri, or links to resources where I could read more about the design philosophy. Thanks!

jolly estuary
#

IMO -
Use frontend whenever it's easily possible, and the performance isn't matters (eg. non heavy functions).
Use Rust whenever it's easier and simpler then in frontend, or for doing native things which browser doesn't do, or when doing heavy tasks.

midnight urchin
#

I'm on the other side of the spectrum with that

Put everything in the backend that you possibly can. Webviews are slow, dumb, bug prone messes that don't perform the same across devices

The way I develop my Tauri apps is by imagining that it's actually a regular Rust CLI application. In fact I've started moving from using #[tauri::command] at all an instead have a custom invoke handler that I map to a Clap based CLI argument parser, so I invoke commands like this invoke('subcommand --flag --argument value'), literally as if it had been ran in the terminal

I use the webview as a source for user inputs and as a way of displaying data. It's for I/O again the user. All actual data and logic resides in the backend. That's not to say that there's not a decent chunk of code that goes into developing the frontend ofc, but it means the frontend can be highly optimised at just displaying data and taking inputs, doing what it does best.

All that said, it's not wrong to do logic in the frontend, it's just a preference, you either choose that you're more comfortable with coding in the frontend, in which case it makes more sense to remain productive and put the code in the frontend, or you say you're more confident in Rust and the backend, in which case you do like me and stick to the backend. Neither choice is wrong, there are benefits to my approach for sure, but you're not wrong for picking the frontend as your main target of coding

hexed sequoia
#

Hi. Sorry for highjacking. Can you please write more on this @midnight urchin ? I am also kind of in the "why even bother using rust at all" boat. I have a fastapi backend which lets users of the app sync the data whenever they need it. I have created a config.yaml file locally in the appdata, but this is also done from the frontend. This yaml file lets the users (admin) change endpoints to the backend.

#

The best scenario (in my opinion) would be to fetch the data from the fastapi backend and store some or all of it locally and then sync it with fastapi backend. I guess this would fit inside the rust backend.

midnight urchin
# hexed sequoia Hi. Sorry for highjacking. Can you please write more on this <@32975209753083904...

Got a more specific question? Because if not my previous answer is pretty much all there is to say 😅

When you create that config.yaml, if you do it in the frontend you're really just calling out to a pre-made command, you're using Rust, you just didn't make the command yourself. I prefer making my own commands. And using pre-made commands means you often have to use several of them in conjunction to achieve more and more logic. I don't like putting such logic in the frontend, I prefer making a new command that calls out to those sets of functions instead, allowing the frontend to be slimmer, more performant, more responsive, hyper focused on just getting inputs from the user and displaying data

#

Take something as simple as writing your config.yaml, if you do that with frontend code there's a higher probability you're relying on the IPC a lot to transfer data, and you're hogging a lot of resources from the webview to process what you want, resources it could've been using on displaying data nicer instead, and if you had sent just a quick "run this function" event to the backend instead of a "run this function and here's tonnes of data for it" then the backend is also quicker to respond, and it gets to do what it does best, namely running Rust code instead of Python or JavaScript, both of which can literally perform millions of times slower than Rust

hexed sequoia
#

@midnight urchin Sorry. I am going to be bit for specific. 🙂

I meant this part.

The way I develop my Tauri apps is by imagining that it's actually a regular Rust CLI application. In fact I've started moving from using #[tauri::command] at all an instead have a custom invoke handler that I map to a Clap based CLI argument parser, so I invoke commands like this invoke('subcommand --flag --argument value'), literally as if it had been ran in the terminal.

#

Thanks for clarifying that I am actually using Rust commands when calling them from the frontend. I guess I need to learn a lot more Rust.It makes total sense to do all this in the backend. Yes, you are correct. I am using a lot of IPC.

midnight urchin
# hexed sequoia <@329752097530839041> Sorry. I am going to be bit for specific. 🙂 I meant thi...

So my process is kinda like this

  1. Create a basic Tauri app
  2. Take this crate that does argument parsing https://docs.rs/clap/latest/clap/, it's the same one the Tauri CLI feature uses but I prefer using it directly
  3. Develop your CLI, where every command you wanna invoke in the frontend is actually a CLI command
  4. Create a custom invoke_handler that maps invoke calls to the Clap CLI

The invoke_handler is this part of an app .invoke_handler(tauri::generate_handler!([])). What that macro does is generate a function for you that's essentially just a match statement on the string you pass to invoke() to figure out which function it should run and with which arguments

What I do is have .invoke_handler(command::invoke::handler()), where that handler() function is a custom implementation of that handler instead of using the one that the macro can generate for you. So when I invoke a command it looks more like this invoke('install docker --context default')

Now I can either run my app with a fancy flashy frontend where I have some button somewhere I can click for running that install command, or I can pass arguments to my app if I run it in the command line, e.g. my-app install docker --context default

This means I'm making greater use of Rust and can use my app as a command line tool. I can even add it to GitHub workflows to execute specific commands. Say my app can register new accounts in my system. I can then have my-app create account --name Simon

Then for data sync I use the event system, emitting events to all windows when Tauri State's are updated. I even make an AppHandle available in my Clap CLI commands so that I can keep using the same state management system regardless of whether the app runs as a CLI or as a full fledged app

hexed sequoia
#

That is useful information! Thanks a lot! I will look into this.