Skip to main content

wasmworker_proc_macro/
lib.rs

1#![deny(rustdoc::broken_intra_doc_links, missing_docs)]
2
3//! Procedural macros for exporting functions to WebWorkers.
4//!
5//! This crate provides two macros:
6//! - [`webworker_fn`]: For simple, synchronous functions
7//! - [`webworker_channel_fn`]: For async functions with bidirectional channel support
8
9use proc_macro::TokenStream;
10use quote::{format_ident, quote};
11use syn::{parse_macro_input, ItemFn};
12
13/// A procedural macro that exports a simple function for use with a WebWorker.
14///
15/// Use this for functions that take a single argument and return a result synchronously.
16/// The function will be callable via `WebWorkerFn` and the `webworker!` macro.
17///
18/// # Example
19///
20/// ```ignore
21/// use wasmworker_proc_macro::webworker_fn;
22///
23/// #[webworker_fn]
24/// fn sort_vec(mut v: Vec<u32>) -> Vec<u32> {
25///     v.sort();
26///     v
27/// }
28/// ```
29#[proc_macro_attribute]
30pub fn webworker_fn(_attr: TokenStream, item: TokenStream) -> TokenStream {
31    let input = parse_macro_input!(item as ItemFn);
32    let fn_name = &input.sig.ident;
33    let wrapper_fn_name = format_ident!("__webworker_{}", fn_name);
34
35    let mod_code = quote! {
36        #[doc(hidden)]
37        pub mod #fn_name {
38            pub const __WEBWORKER: () = ();
39            const _: () = {
40                #[wasm_bindgen::prelude::wasm_bindgen]
41                pub fn #wrapper_fn_name(arg: Box<[u8]>) -> Box<[u8]> {
42                    let arg = wasmworker::convert::from_bytes(&arg);
43                    let res = super::#fn_name(arg);
44                    wasmworker::convert::to_bytes(&res)
45                }
46            };
47        }
48    };
49
50    let expanded = quote! {
51        #input
52
53        #mod_code
54    };
55
56    TokenStream::from(expanded)
57}
58
59/// A procedural macro that exports an async function with channel support for use with a WebWorker.
60///
61/// Use this for functions that need bidirectional communication with the main thread,
62/// such as progress reporting or interactive workflows. The function must be async and
63/// take a `Channel` as its second parameter.
64///
65/// The function will be callable via `WebWorkerChannelFn` and the `webworker_channel!` macro.
66///
67/// # Example
68///
69/// ```ignore
70/// use wasmworker_proc_macro::webworker_channel_fn;
71/// use wasmworker::Channel;
72///
73/// #[webworker_channel_fn]
74/// async fn process_with_progress(data: Vec<u8>, channel: Channel) -> Result<Output, Error> {
75///     channel.send(&Progress { percent: 50 });
76///     let response: UserChoice = channel.recv().await?;
77///     // ... process data ...
78///     Ok(output)
79/// }
80/// ```
81#[proc_macro_attribute]
82pub fn webworker_channel_fn(_attr: TokenStream, item: TokenStream) -> TokenStream {
83    let input = parse_macro_input!(item as ItemFn);
84    let fn_name = &input.sig.ident;
85    let wrapper_fn_name = format_ident!("__webworker_channel_{}", fn_name);
86
87    let mod_code = quote! {
88        #[doc(hidden)]
89        pub mod #fn_name {
90            pub const __WEBWORKER_CHANNEL: () = ();
91            const _: () = {
92                #[wasm_bindgen::prelude::wasm_bindgen]
93                pub async fn #wrapper_fn_name(arg: Box<[u8]>, port: wasm_bindgen::JsValue) -> Box<[u8]> {
94                    use wasm_bindgen::JsCast;
95                    let arg = wasmworker::convert::from_bytes(&arg);
96                    let channel = port
97                        .dyn_into::<wasmworker::MessagePort>()
98                        .map(wasmworker::Channel::from)
99                        .expect("webworker_channel_fn requires a MessagePort");
100                    let res = super::#fn_name(arg, channel).await;
101                    wasmworker::convert::to_bytes(&res)
102                }
103            };
104        }
105    };
106
107    let expanded = quote! {
108        #input
109
110        #mod_code
111    };
112
113    TokenStream::from(expanded)
114}