From f776451ce020b8a805995742b3292e0738f5ffc9 Mon Sep 17 00:00:00 2001 From: photovoltex Date: Sun, 10 Mar 2024 23:05:59 +0100 Subject: [PATCH 1/4] move command docs into code --- README.md | 73 +--------------------------------- tauri-interop-macro/src/lib.rs | 54 +++++++++++++++++++------ 2 files changed, 44 insertions(+), 83 deletions(-) diff --git a/README.md b/README.md index 7fe1fd9..b54e58a 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,12 @@ This crate tries to provide a general more enjoyable experience for developing t > tbf it is a saner approach to write the app in a mix of js + rust, because the frameworks are more mature, there are > way more devs who have experience with js and their respective frameworks etc... > -> but tbh... just because something is saner, it doesn't stop us from doing things differently ^ヮ^ +> but tbh... just because something is saner, doesn't stop us from doing things differently ^ヮ^ Writing an app in a single language gives us the option of building a common crate/module which connects the backend and frontend. A common model itself can most of the time be easily compiled to both architectures (arch's) when the types are compatible with both. The commands on the other hand don't have an option to be compiled to wasm. Which means they -need to be handled manually or be called via a wrapper/helper each time. +need to be handled manually or be called via a wrapper/helper each time. Repeating the implementation and handling for a function that is already defined properly seems to be a waste of time. For that reason this crate provides the `tauri_interop::command` macro. This macro is explained in detail in the @@ -34,75 +34,6 @@ conditional wrapper that expands to `Emit` for the tauri compilation and `Listen the intended way to use this feature. The usage is explained in the documentation of the `Event` macro. section. -## Basic usage: - -> **Disclaimer**: -> -> Some examples in this documentation can't be executed with doctests due to -> the required wasm target and tauri modified environment (see [withGlobalTauri](https://tauri.app/v1/api/config/#buildconfig.withglobaltauri)) - -### Command (Frontend => Backend Communication) -> For more examples see [cmd.rs](./test-project/api/src/cmd.rs) in test-project - -The newly provides macro `tauri_interop::command` does two things: -- it provides the function with two macros which are used depending on the targeted architecture - - `tauri_interop::binding` is used when compiling to `wasm` - - `tauri::command` is used otherwise -- additionally it provides the possibility to collect all defined commands via `tauri_interop::collect_commands!()` - - for more info see [collect commands](#collect-commands)) - - the function is not generated when targeting `wasm` - -The generated command can then be used in `wasm` like the following: -```rust , ignore -#[tauri_interop::command] -fn greet(name: &str, _handle: tauri::AppHandle) -> String { - format!("Hello, {}! You've been greeted from Rust!", name) -} - -fn main() { - console_log::init_with_level(log::Level::Info).unwrap(); - - wasm_bindgen_futures::spawn_local(async move { - let greetings = greet("frontend").await; - log::info!("{greetings}"); - }); -} -``` - -**Command representation Host/Wasm (and a bit background knowledge)** - -- the returned type of the wasm binding should be 1:1 the same type as send from the "backend" - - technically all commands need to be of type `Result` because there is always the possibility of a command - getting called, that isn't registered in the context of tauri - - when using `tauri_interop::collect_commands!()` this possibility is fully™️ removed - - for convenience, we ignore that possibility, and even if the error occurs it will be logged into the console -- all arguments with `tauri` in their name (case-insensitive) are removed as argument in a defined command - - that includes `tauri::*` usages and `Tauri` named types - - the crate itself provides type aliases for tauri types usable in a command (see [type_aliases](./src/command/type_aliases.rs)) -- most return types are automatically determined - - when using a return type with `Result` in the name, the function will also return a `Result` - - that also means, if you create a type alias for `Result` and don't include `Result` in the name of the alias, - it will not map the `Result` correctly - -#### Collect commands - -The `tauri_invoke::collect_commands` macro generates a `get_handlers` function in the current mod, which calls the -`tauri::generate_handler` macro with all function which are annotated with the `tauri_interop::command` macro. The -function is only generated for tauri and not for wasm. - -Due to technical limitations we sadly can't combine multiple `get_handlers` functions. This limitation comes to the -underlying mechanic. The `tauri::generate_handler` macro generates a function which consumes `tauri::Invoke` as single -parameter. Because it fully consumes the given parameter we can't call multiple handlers with it. In addition, the -`Builder::invoke_handler` function, which usually consumes the generated `tauri::generate_handler` can't be called -twice without losing the previous registered commands. - -Because of this limitation for splitting commands into multiple files it is recommended to create a root mod for the -command which includes other command mod's. The functions in the included mods need to be public and re-imported into -the root mod. With these prerequisites the `tauri_invoke::collect_commands` can be called at the end of the file, which -generates the usual `get_handlers` function, but with all "commands" defined inside the others mods. - -For an example see the [test-project/api/src/command.rs](test-project/api/src/command.rs). - ### QOL macros This crate also adds some quality-of-life macros. These are intended to ease the drawbacks of compiling to diff --git a/tauri-interop-macro/src/lib.rs b/tauri-interop-macro/src/lib.rs index a32febe..23926e1 100644 --- a/tauri-interop-macro/src/lib.rs +++ b/tauri-interop-macro/src/lib.rs @@ -1,6 +1,8 @@ #![feature(iter_intersperse)] #![warn(missing_docs)] -//! The macros use by `tauri_interop` to generate dynamic code depending on the target +//! The macros use by `tauri-interop` to generate dynamic code depending on the target +//! +//! Without `tauri-interop` the generated code can't compile. use proc_macro::TokenStream; use std::collections::HashSet; @@ -63,7 +65,7 @@ pub fn derive_event(stream: TokenStream) -> TokenStream { /// Generates a default `Emit` implementation for the given struct. /// /// Used for host code generation. It is not intended to be used directly. -/// See [Event] +/// See [Event] for the usage. #[cfg(feature = "event")] #[proc_macro_derive(Emit, attributes(auto_naming, mod_name))] pub fn derive_emit(stream: TokenStream) -> TokenStream { @@ -79,9 +81,10 @@ pub fn derive_emit_field(stream: TokenStream) -> TokenStream { event::emit::derive_field(stream) } -/// Generates `listen_to_` functions for the given struct. +/// Generates an implementation of [tauri_interop::Event] /// /// Used for wasm code generation. It is not intended to be used directly. +/// See [Event] for the usage. #[cfg(feature = "event")] #[proc_macro_derive(Listen, attributes(auto_naming, mod_name))] pub fn derive_listen(stream: TokenStream) -> TokenStream { @@ -113,26 +116,40 @@ lazy_static::lazy_static! { static COMMAND_MOD_NAME: Mutex> = Mutex::new(None); -/// Conditionally adds the [binding] or `tauri::command` macro to a struct -/// -/// ### Example -/// -/// The commands above the commands is the equivalent usage in wasm +/// Conditionally adds the macro [macro@binding] or `tauri::command` to a struct +/// +/// By using this macro, when compiling to wasm, a version that invokes the +/// current function is generated. +/// +/// ### Collecting commands +/// When this macro is compiled to the host target, additionally to adding the +/// `tauri::command` macro, the option to auto collect the command via +/// [macro@collect_commands] and [macro@combine_handlers] is provided. +/// +/// ### Binding generation +/// All parameter arguments with `tauri` in their name (case-insensitive) are +/// removed as argument in a defined command. That includes `tauri::*` usages +/// and `Tauri` named types. +/// +/// The type returned is evaluated automatically and is most of the time 1:1 +/// to the defined type. When using a wrapped `Result` type, it should +/// include the phrase "Result" in the type name. Otherwise, the returned type +/// can't be successfully interpreted as a result and by that will result in +/// wrong type/error handling/serialization. +/// +/// ### Example - Definition /// /// ```rust -/// // let _: () = trigger_something(); /// #[tauri_interop::command] /// fn trigger_something(name: &str) { /// print!("triggers something, but doesn't need to wait for it") /// } /// -/// // let value: String = wait_for_sync_execution("value").await; /// #[tauri_interop::command] /// fn wait_for_sync_execution(value: &str) -> String { /// format!("Has to wait that the backend completes the computation and returns the {value}") /// } /// -/// // let result: Result = asynchronous_execution(true).await; /// #[tauri_interop::command] /// async fn asynchronous_execution(change: bool) -> Result { /// if change { @@ -142,12 +159,25 @@ static COMMAND_MOD_NAME: Mutex> = Mutex::new(None); /// } /// } /// -/// // let _wait_for_completion: () = heavy_computation().await; /// #[tauri_interop::command] /// async fn heavy_computation() { /// std::thread::sleep(std::time::Duration::from_millis(5000)) /// } /// ``` +/// +/// ### Example - Usage +/// +/// ```rust , ignore +/// fn main() { +/// trigger_something(); +/// +/// wasm_bindgen_futures::spawn_local(async move { +/// wait_for_sync_execution("value").await; +/// asynchronous_execution(true).await.expect("returns ok"); +/// heavy_computation().await; +/// }); +/// } +/// ``` #[proc_macro_attribute] pub fn command(_attributes: TokenStream, stream: TokenStream) -> TokenStream { let fn_item = parse_macro_input!(stream as ItemFn); From 7d4fc369b02205d2e7f2e7bec948bb790a9b64b5 Mon Sep 17 00:00:00 2001 From: photovoltex Date: Sun, 10 Mar 2024 23:06:15 +0100 Subject: [PATCH 2/4] add own docs for the main crate --- src/lib.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 93fd0fc..34e731e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,22 @@ #![warn(missing_docs)] -#![doc = include_str!("../README.md")] #![feature(trait_alias)] +//! Tauri-Interop is a library that provides macros to improve developing tauri apps with a rust +//! frontend by generating frontend implementation out of the backend definitions. +//! +//! The main macros intended to be used are: +//! - [macro@command], which is intended to be used as replacement to [macro@tauri::command] +//! - [macro@Event], that provides an easier usage of the [Events feature of tauri](https://tauri.app/v1/guides/features/events/) +//! - derives [event::Listen] when compiling to wasm and [event::Emit] otherwise +//! +//! Additionally, some QOL macros ([host_usage] and [wasm_usage]) are provided that +//! reduce some drawbacks when simultaneously compiling to wasm and the host architecture. +//! +//! ### Explanation and Examples +//! +//! Detail explanations and example can be found on the respected traits or macros. Some +//! examples are ignored because they are only valid when compiling to wasm. + pub use tauri_interop_macro::*; /// wrapped bindings for easier use in the generated wasm commands From 66d93682b65b495d1d7b4183629127911f29af2d Mon Sep 17 00:00:00 2001 From: photovoltex Date: Sun, 10 Mar 2024 23:23:24 +0100 Subject: [PATCH 3/4] summarize crate function in readme --- README.md | 69 ++++--------------------------------------------------- 1 file changed, 5 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index b54e58a..96a0c7d 100644 --- a/README.md +++ b/README.md @@ -15,67 +15,8 @@ frontend. A common model itself can most of the time be easily compiled to both are compatible with both. The commands on the other hand don't have an option to be compiled to wasm. Which means they need to be handled manually or be called via a wrapper/helper each time. -Repeating the implementation and handling for a function that is already defined properly seems to be a waste of time. -For that reason this crate provides the `tauri_interop::command` macro. This macro is explained in detail in the -[command representation](#command-representation-hostwasm) section. This new macro provides the option to invoke the -command in wasm and by therefore call the defined command in tauri. On the other side, when compiling for tauri in addition -to the tauri logic, the macro provides the option to collect all commands in a single file via the invocation of the -`tauri_interop::collect_commands` macro at the end of the file (see [command](#command-frontend--backend-communication)). - -In addition, some quality-of-life macros are provided to ease some inconveniences when compiling to multiple arch's. See -the [QOL](#qol-macros) section. - -**Feature `event`**: - -Tauri has an [event](https://tauri.app/v1/guides/features/events) mechanic which allows the tauri side to communicate with -the frontend. The usage is not as intuitive and has to some inconveniences that make it quite hard to recommend. To -improve the usage, this crate provides the derive-marcos `Event`, `Emit` and `Listen`. The `Event` macro is just a -conditional wrapper that expands to `Emit` for the tauri compilation and `Listen` for the wasm compilation. It is -the intended way to use this feature. The usage is explained in the documentation of the `Event` macro. -section. - -### QOL macros - -This crate also adds some quality-of-life macros. These are intended to ease the drawbacks of compiling to -multiple architectures. - -#### Conditional `use` -Because most crates are not intended to be compiled to wasm and most wasm crates are not intended to be compiled to -the host-triplet they have to be excluded in each others compile process. The usual process to exclude uses for a certain -architecture would look something like this: - -```rust -#[cfg(not(target_family = "wasm"))] -use tauri::AppHandle; - -#[tauri_interop::command] -pub fn empty_invoke(_handle: AppHandle) {} -``` - -**General usage:** - -With the help of `tauri_interop::host_usage!()` and `tauri_interop::wasm_usage!()` we don't need to remember which -attribute we have to add and can just convert the above to the following: - -```rust -tauri_interop::host_usage! { - use tauri::AppHandle; -} - -#[tauri_interop::command] -pub fn empty_invoke(_handle: AppHandle) {} -``` - -**Multiple `use` usage:** - -When multiple `use` should be excluded, they need to be separated by a single pipe (`|`). For example: - -```rust -tauri_interop::host_usage! { - use tauri::State; - | use std::sync::RwLock; -} - -#[tauri_interop::command] -pub fn empty_invoke(_state: State>) {} -``` +The crates therefore provides the following features: +- generate a wasm function out of the defined tauri-command +- collect and register all defined tauri-commands +- QOL-macros to exclude multiple imports in wasm or the host architecture +- easier usage of [tauri's event feature](https://tauri.app/v1/guides/features/events/) From 686a93348d45d0f3c9c13ff92cd8a2f33a58a7f0 Mon Sep 17 00:00:00 2001 From: photovoltex Date: Sun, 10 Mar 2024 23:23:38 +0100 Subject: [PATCH 4/4] cleanup --- src/command.rs | 2 +- src/lib.rs | 22 +++++++++++----------- tauri-interop-macro/src/lib.rs | 34 +++++++++++++++++----------------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/command.rs b/src/command.rs index 0c8d5b5..3e7aee7 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,7 +1,7 @@ #[cfg(not(target_family = "wasm"))] pub use type_aliases::*; -/// wasm bindings for tauri's provided js functions (target: `wasm` or feat: `wasm`) +/// wasm bindings for tauri's provided js functions (target: `wasm`) #[cfg(any(target_family = "wasm", feature = "_wasm"))] pub mod bindings; diff --git a/src/lib.rs b/src/lib.rs index 34e731e..7354e13 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,22 +1,22 @@ -#![warn(missing_docs)] -#![feature(trait_alias)] - -//! Tauri-Interop is a library that provides macros to improve developing tauri apps with a rust -//! frontend by generating frontend implementation out of the backend definitions. -//! +//! Tauri-Interop is a library that provides macros to improve developing tauri apps with a rust +//! frontend by generating frontend implementation out of the backend definitions. +//! //! The main macros intended to be used are: //! - [macro@command], which is intended to be used as replacement to [macro@tauri::command] //! - [macro@Event], that provides an easier usage of the [Events feature of tauri](https://tauri.app/v1/guides/features/events/) //! - derives [event::Listen] when compiling to wasm and [event::Emit] otherwise -//! -//! Additionally, some QOL macros ([host_usage] and [wasm_usage]) are provided that +//! +//! Additionally, some QOL macros ([host_usage] and [wasm_usage]) are provided that //! reduce some drawbacks when simultaneously compiling to wasm and the host architecture. -//! +//! //! ### Explanation and Examples -//! -//! Detail explanations and example can be found on the respected traits or macros. Some +//! +//! Detail explanations and example can be found on the respected traits or macros. Some //! examples are ignored because they are only valid when compiling to wasm. +#![warn(missing_docs)] +#![feature(trait_alias)] + pub use tauri_interop_macro::*; /// wrapped bindings for easier use in the generated wasm commands diff --git a/tauri-interop-macro/src/lib.rs b/tauri-interop-macro/src/lib.rs index 23926e1..a3929c9 100644 --- a/tauri-interop-macro/src/lib.rs +++ b/tauri-interop-macro/src/lib.rs @@ -81,7 +81,7 @@ pub fn derive_emit_field(stream: TokenStream) -> TokenStream { event::emit::derive_field(stream) } -/// Generates an implementation of [tauri_interop::Event] +/// Generates a default `Listen` implementation for the given struct. /// /// Used for wasm code generation. It is not intended to be used directly. /// See [Event] for the usage. @@ -118,39 +118,39 @@ static COMMAND_MOD_NAME: Mutex> = Mutex::new(None); /// Conditionally adds the macro [macro@binding] or `tauri::command` to a struct /// -/// By using this macro, when compiling to wasm, a version that invokes the +/// By using this macro, when compiling to wasm, a version that invokes the /// current function is generated. -/// +/// /// ### Collecting commands /// When this macro is compiled to the host target, additionally to adding the -/// `tauri::command` macro, the option to auto collect the command via +/// `tauri::command` macro, the option to auto collect the command via /// [macro@collect_commands] and [macro@combine_handlers] is provided. /// /// ### Binding generation -/// All parameter arguments with `tauri` in their name (case-insensitive) are -/// removed as argument in a defined command. That includes `tauri::*` usages +/// All parameter arguments with `tauri` in their name (case-insensitive) are +/// removed as argument in a defined command. That includes `tauri::*` usages /// and `Tauri` named types. -/// -/// The type returned is evaluated automatically and is most of the time 1:1 +/// +/// The type returned is evaluated automatically and is most of the time 1:1 /// to the defined type. When using a wrapped `Result` type, it should /// include the phrase "Result" in the type name. Otherwise, the returned type /// can't be successfully interpreted as a result and by that will result in /// wrong type/error handling/serialization. -/// +/// /// ### Example - Definition /// /// ```rust -/// #[tauri_interop::command] +/// #[tauri_interop_macro::command] /// fn trigger_something(name: &str) { /// print!("triggers something, but doesn't need to wait for it") /// } /// -/// #[tauri_interop::command] +/// #[tauri_interop_macro::command] /// fn wait_for_sync_execution(value: &str) -> String { /// format!("Has to wait that the backend completes the computation and returns the {value}") /// } /// -/// #[tauri_interop::command] +/// #[tauri_interop_macro::command] /// async fn asynchronous_execution(change: bool) -> Result { /// if change { /// Ok("asynchronous execution returning result, need Result in their type name".into()) @@ -159,18 +159,18 @@ static COMMAND_MOD_NAME: Mutex> = Mutex::new(None); /// } /// } /// -/// #[tauri_interop::command] +/// #[tauri_interop_macro::command] /// async fn heavy_computation() { /// std::thread::sleep(std::time::Duration::from_millis(5000)) /// } /// ``` -/// +/// /// ### Example - Usage -/// +/// /// ```rust , ignore /// fn main() { /// trigger_something(); -/// +/// /// wasm_bindgen_futures::spawn_local(async move { /// wait_for_sync_execution("value").await; /// asynchronous_execution(true).await.expect("returns ok"); @@ -189,7 +189,7 @@ pub fn command(_attributes: TokenStream, stream: TokenStream) -> TokenStream { let command_macro = quote! { #[cfg_attr(target_family = "wasm", ::tauri_interop::binding)] - #[cfg_attr(not(target_family = "wasm"), tauri::command(rename_all = "snake_case"))] + #[cfg_attr(not(target_family = "wasm"), ::tauri::command(rename_all = "snake_case"))] #fn_item };