From 61d21ada595dd2f0298fcafe8e22739b8bba84e1 Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Sun, 22 Jun 2025 05:55:47 +0000 Subject: [PATCH 01/15] Move call_js macro over from dioxus_sdk https://github.com/DioxusLabs/sdk/pull/87 --- Cargo.lock | 4 + Cargo.toml | 1 + packages/use-js-macro/Cargo.toml | 16 ++ packages/use-js-macro/README.md | 47 +++++ packages/use-js-macro/src/lib.rs | 302 +++++++++++++++++++++++++++++++ 5 files changed, 370 insertions(+) create mode 100644 packages/use-js-macro/Cargo.toml create mode 100644 packages/use-js-macro/README.md create mode 100644 packages/use-js-macro/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index d0a3863d2a..a7b8b04058 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15490,6 +15490,10 @@ dependencies = [ "url", ] +[[package]] +name = "use-js-macro" +version = "0.7.0-alpha.1" + [[package]] name = "usvg" version = "0.45.1" diff --git a/Cargo.toml b/Cargo.toml index a3a04b7fc6..47c41aadf6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ members = [ "packages/asset-resolver", "packages/depinfo", "packages/server", + "packages/use-js-macro", # Playwright tests "packages/playwright-tests/liveview", diff --git a/packages/use-js-macro/Cargo.toml b/packages/use-js-macro/Cargo.toml new file mode 100644 index 0000000000..eea833b2ee --- /dev/null +++ b/packages/use-js-macro/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "use-js-macro" +edition = "2024" +version.workspace = true + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0", features = ["full"] } +swc_ecma_parser = "17" +swc_ecma_ast = "13" +swc_ecma_visit = "13" +swc_common = "13" diff --git a/packages/use-js-macro/README.md b/packages/use-js-macro/README.md new file mode 100644 index 0000000000..35a3a472e1 --- /dev/null +++ b/packages/use-js-macro/README.md @@ -0,0 +1,47 @@ +# Dioxus call-js + +A macro to simplify calling javascript from rust + +## Usage +Add `dioxus-call-js` to your `Cargo.toml`: +```toml +[dependencies] +dioxus-call-js = "0.1" +``` + +Example: +```rust +use dioxus::prelude::*; +use dioxus_call_js::call_js; + +fn main() { + launch(App); +} + +#[component] +fn App() -> Element { + let future = use_resource(|| async move { + let from = "dave"; + let to = "john"; + let greeting = call_js!("assets/example.js", greeting(from, to)).await.unwrap(); + let greeting: String = serde_json::from_value(greeting).unwrap(); + return greeting; + }); + + rsx!( + div { + h1 { "Dioxus `call_js!` macro example!" } + { + match &*future.read() { + Some(greeting) => rsx! { + p { "Greeting from JavaScript: {greeting}" } + }, + None => rsx! { + p { "Running js" } + }, + } + } + } + ) +} +``` \ No newline at end of file diff --git a/packages/use-js-macro/src/lib.rs b/packages/use-js-macro/src/lib.rs new file mode 100644 index 0000000000..a84c69ceae --- /dev/null +++ b/packages/use-js-macro/src/lib.rs @@ -0,0 +1,302 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use std::sync::Arc; +use std::{fs, path::Path}; +use swc_common::SourceMap; +use swc_ecma_ast::{ + Decl, ExportDecl, ExportSpecifier, FnDecl, ModuleExportName, NamedExport, VarDeclarator, +}; +use swc_ecma_parser::EsSyntax; +use swc_ecma_parser::{Parser, StringInput, Syntax, lexer::Lexer}; +use swc_ecma_visit::{Visit, VisitWith}; +use syn::{ + Expr, ExprCall, LitStr, Result, Token, + parse::{Parse, ParseStream}, + parse_macro_input, +}; + +struct CallJsInput { + asset_path: LitStr, + function_call: ExprCall, +} + +impl Parse for CallJsInput { + fn parse(input: ParseStream) -> Result { + let asset_path: LitStr = input.parse()?; + input.parse::()?; + + let function_call: ExprCall = input.parse()?; + + Ok(CallJsInput { + asset_path, + function_call, + }) + } +} + +fn extract_function_name(call: &ExprCall) -> Result { + match &*call.func { + Expr::Path(path) => { + if let Some(ident) = path.path.get_ident() { + Ok(ident.to_string()) + } else { + Err(syn::Error::new_spanned( + &path.path, + "Function call must be a simple identifier", + )) + } + } + _ => Err(syn::Error::new_spanned( + &call.func, + "Function call must be a simple identifier", + )), + } +} + +#[derive(Debug)] +struct FunctionInfo { + name: String, + param_count: usize, + is_exported: bool, +} + +struct FunctionVisitor { + functions: Vec, +} + +impl FunctionVisitor { + fn new() -> Self { + Self { + functions: Vec::new(), + } + } +} + +impl Visit for FunctionVisitor { + /// Visit function declarations: function foo() {} + fn visit_fn_decl(&mut self, node: &FnDecl) { + self.functions.push(FunctionInfo { + name: node.ident.sym.to_string(), + param_count: node.function.params.len(), + is_exported: false, + }); + node.visit_children_with(self); + } + + /// Visit function expressions: const foo = function() {} + fn visit_var_declarator(&mut self, node: &VarDeclarator) { + if let swc_ecma_ast::Pat::Ident(ident) = &node.name { + if let Some(init) = &node.init { + match &**init { + swc_ecma_ast::Expr::Fn(fn_expr) => { + self.functions.push(FunctionInfo { + name: ident.id.sym.to_string(), + param_count: fn_expr.function.params.len(), + is_exported: false, + }); + } + swc_ecma_ast::Expr::Arrow(arrow_fn) => { + self.functions.push(FunctionInfo { + name: ident.id.sym.to_string(), + param_count: arrow_fn.params.len(), + is_exported: false, + }); + } + _ => {} + } + } + } + node.visit_children_with(self); + } + + /// Visit export declarations: export function foo() {} + fn visit_export_decl(&mut self, node: &ExportDecl) { + match &node.decl { + Decl::Fn(fn_decl) => { + self.functions.push(FunctionInfo { + name: fn_decl.ident.sym.to_string(), + param_count: fn_decl.function.params.len(), + is_exported: true, + }); + } + _ => {} + } + node.visit_children_with(self); + } + + /// Visit named exports: export { foo } + fn visit_named_export(&mut self, node: &NamedExport) { + for spec in &node.specifiers { + match spec { + ExportSpecifier::Named(named) => { + let name = match &named.orig { + ModuleExportName::Ident(ident) => ident.sym.to_string(), + ModuleExportName::Str(str_lit) => str_lit.value.to_string(), + }; + + if let Some(func) = self.functions.iter_mut().find(|f| f.name == name) { + func.is_exported = true; + } + } + _ => {} + } + } + node.visit_children_with(self); + } +} + +fn parse_js_file(file_path: &Path) -> Result> { + let js_content = fs::read_to_string(&file_path).map_err(|e| { + syn::Error::new( + proc_macro2::Span::call_site(), + format!( + "Could not read JavaScript file '{}': {}", + file_path.display(), + e + ), + ) + })?; + + let cm = Arc::new(SourceMap::default()); + let fm = cm.new_source_file( + swc_common::FileName::Custom(file_path.display().to_string()).into(), + js_content.clone(), + ); + + let lexer = Lexer::new( + Syntax::Es(EsSyntax { + jsx: true, + ..Default::default() + }), + Default::default(), + StringInput::from(&*fm), + None, + ); + + let mut parser = Parser::new_from(lexer); + + let module = parser.parse_module().map_err(|e| { + syn::Error::new( + proc_macro2::Span::call_site(), + format!( + "Failed to parse JavaScript file '{}': {:?}", + file_path.display(), + e + ), + ) + })?; + + let mut visitor = FunctionVisitor::new(); + module.visit_with(&mut visitor); + + Ok(visitor.functions) +} + +fn validate_function_call( + functions: &[FunctionInfo], + function_name: &str, + arg_count: usize, +) -> Result<()> { + let function = functions + .iter() + .find(|f| f.name == function_name) + .ok_or_else(|| { + syn::Error::new( + proc_macro2::Span::call_site(), + format!("Function '{}' not found in JavaScript file", function_name), + ) + })?; + + if !function.is_exported { + return Err(syn::Error::new( + proc_macro2::Span::call_site(), + format!( + "Function '{}' is not exported from the JavaScript module", + function_name + ), + )); + } + + if function.param_count != arg_count { + return Err(syn::Error::new( + proc_macro2::Span::call_site(), + format!( + "Function '{}' expects {} arguments, but {} were provided", + function_name, function.param_count, arg_count + ), + )); + } + + Ok(()) +} + +#[proc_macro] +pub fn call_js(input: TokenStream) -> TokenStream { + // parse + let input = parse_macro_input!(input as CallJsInput); + + let asset_path = &input.asset_path; + let function_call = &input.function_call; + + let function_name = match extract_function_name(function_call) { + Ok(name) => name, + Err(e) => return TokenStream::from(e.to_compile_error()), + }; + + // validate js call + let arg_count = function_call.args.len(); + let manifest_dir = match std::env::var("CARGO_MANIFEST_DIR") { + Ok(dir) => dir, + Err(_) => { + return TokenStream::from( + syn::Error::new( + proc_macro2::Span::call_site(), + "CARGO_MANIFEST_DIR environment variable not found", + ) + .to_compile_error(), + ); + } + }; + + let js_file_path = std::path::Path::new(&manifest_dir).join(asset_path.value()); + let functions = match parse_js_file(&js_file_path) { + Ok(funcs) => funcs, + Err(e) => return TokenStream::from(e.to_compile_error()), + }; + if let Err(e) = validate_function_call(&functions, &function_name, arg_count) { + return TokenStream::from(e.to_compile_error()); + } + + // expand + let send_calls: Vec = function_call + .args + .iter() + .map(|arg| quote! { eval.send(#arg)?; }) + .collect(); + + let mut js_format = format!(r#"const {{{{ {function_name} }}}} = await import("{{}}");"#,); + for i in 0..arg_count { + js_format.push_str(&format!("\nlet arg{} = await dioxus.recv();", i)); + } + js_format.push_str(&format!("\nreturn {}(", function_name)); + for i in 0..arg_count { + if i > 0 { + js_format.push_str(", "); + } + js_format.push_str(&format!("arg{}", i)); + } + js_format.push_str(");"); + + let expanded = quote! { + async move { + const MODULE: Asset = asset!(#asset_path); + let js = format!(#js_format, MODULE); + let eval = document::eval(js.as_str()); + #(#send_calls)* + eval.await + } + }; + + TokenStream::from(expanded) +} From db63b32a7b8284778995354c726743a04e27e506 Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Sun, 22 Jun 2025 06:50:12 +0000 Subject: [PATCH 02/15] chore: Update cli-opt dependency versions --- packages/cli-opt/Cargo.toml | 52 ++++++++++++++++++------------------- packages/cli-opt/src/js.rs | 2 +- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/cli-opt/Cargo.toml b/packages/cli-opt/Cargo.toml index dea1f9c871..0f73cbf8ab 100644 --- a/packages/cli-opt/Cargo.toml +++ b/packages/cli-opt/Cargo.toml @@ -40,33 +40,33 @@ grass = "0.13.4" codemap = "0.1.3" # Js minification - swc has introduces minor versions with breaking changes in the past so we pin all of their crates -swc_allocator = { version = "=2.0.0", default-features = false } -swc_atoms = { version = "=3.0.2", default-features = false } -swc_bundler = { version = "=7.0.0", default-features = false } -swc_cached = { version = "=1.0.0", default-features = false } -swc_common = { version = "=5.0.0", features = ["tty-emitter"], default-features = false } -swc_config = { version = "=1.0.0", default-features = false } -swc_config_macro = { version = "=1.0.0", default-features = false } -swc_ecma_ast = { version = "=5.0.1", default-features = false } -swc_ecma_codegen = { version = "=5.0.1", default-features = false } -swc_ecma_codegen_macros = { version = "=1.0.0", default-features = false } -swc_ecma_loader = { version = "=5.0.0", features = ["cache", "node"], default-features = false } -swc_ecma_minifier = { version = "=7.0.1", default-features = false } -swc_ecma_parser = { version = "=6.0.2", default-features = false } -swc_ecma_transforms_base = { version = "=7.0.0", default-features = false } -swc_ecma_transforms_macros = { version = "=1.0.0", default-features = false } -swc_ecma_transforms_optimization = { version = "=7.0.1", default-features = false } -swc_ecma_usage_analyzer = { version = "=7.0.0", default-features = false } -swc_ecma_utils = { version = "=7.0.0", default-features = false } -swc_ecma_visit = { version = "=5.0.0", default-features = false } -swc_eq_ignore_macros = { version = "=1.0.0", default-features = false } -swc_fast_graph = { version = "=6.0.0", default-features = false } -swc_graph_analyzer = { version = "=5.0.0", default-features = false } -swc_macros_common = { version = "=1.0.0", default-features = false } -swc_parallel = { version = "=1.0.1", default-features = false } +swc_allocator = { version = "=4.0.0", default-features = false } +swc_atoms = { version = "=6.0.0", default-features = false } +swc_bundler = { version = "=22.0.0", default-features = false } +swc_cached = { version = "=2.0.0", default-features = false } +swc_common = { version = "=13.0.1", features = ["tty-emitter"], default-features = false } +swc_config = { version = "=3.1.1", default-features = false } +swc_config_macro = { version = "=1.0.1", default-features = false } +swc_ecma_ast = { version = "=13.0.0", default-features = false } +swc_ecma_codegen = { version = "=15.0.1", default-features = false } +swc_ecma_codegen_macros = { version = "=2.0.1", default-features = false } +swc_ecma_loader = { version = "=13.0.0", features = ["cache", "node"], default-features = false } +swc_ecma_minifier = { version = "=23.0.2", default-features = false } +swc_ecma_parser = { version = "=17.0.1", default-features = false } +swc_ecma_transforms_base = { version = "=18.0.0", default-features = false } +swc_ecma_transforms_macros = { version = "=1.0.1", default-features = false } +swc_ecma_transforms_optimization = { version = "=19.0.0", default-features = false } +swc_ecma_usage_analyzer = { version = "=19.0.0", default-features = false } +swc_ecma_utils = { version = "=18.0.0", default-features = false } +swc_ecma_visit = { version = "=13.0.0", default-features = false } +swc_eq_ignore_macros = { version = "=1.0.1", default-features = false } +swc_fast_graph = { version = "=9.0.0", default-features = false } +swc_graph_analyzer = { version = "=14.0.0", default-features = false } +swc_macros_common = { version = "=1.0.1", default-features = false } +swc_parallel = { version = "=1.3.0", default-features = false } swc_timer = { version = "=1.0.0", default-features = false } -swc_visit = { version = "=2.0.0", default-features = false } -browserslist-rs = { version = "=0.16.0" } +swc_visit = { version = "=2.0.1", default-features = false } +browserslist-rs = { version = "=0.18.2" } [build-dependencies] built = { version = "0.7.5", features = ["git2"] } diff --git a/packages/cli-opt/src/js.rs b/packages/cli-opt/src/js.rs index 2aacfb5268..52ab2b0c86 100644 --- a/packages/cli-opt/src/js.rs +++ b/packages/cli-opt/src/js.rs @@ -30,7 +30,7 @@ use crate::hash::hash_file_contents; struct TracingEmitter; impl Emitter for TracingEmitter { - fn emit(&mut self, db: &swc_common::errors::DiagnosticBuilder<'_>) { + fn emit(&mut self, db: &mut swc_common::errors::DiagnosticBuilder<'_>) { match db.level { swc_common::errors::Level::Bug | swc_common::errors::Level::Fatal From d647a857e4d34efffbad299d6e4c6e4c0efcf439 Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Sun, 22 Jun 2025 07:46:11 +0000 Subject: [PATCH 03/15] Convert call_js macro to use_js macro --- packages/use-js-macro/Cargo.toml | 21 ++- packages/use-js-macro/src/lib.rs | 245 ++++++++++++++++++------------- 2 files changed, 159 insertions(+), 107 deletions(-) diff --git a/packages/use-js-macro/Cargo.toml b/packages/use-js-macro/Cargo.toml index eea833b2ee..43e2df0ff3 100644 --- a/packages/use-js-macro/Cargo.toml +++ b/packages/use-js-macro/Cargo.toml @@ -1,7 +1,13 @@ [package] -name = "use-js-macro" -edition = "2024" -version.workspace = true +name = "dioxus-use-js-macro" +version = { workspace = true } +authors = ["Dillon Henry McMahon"] +edition = "2021" +description = "javascript use bridge macro for Dioxus" +license = "MIT OR Apache-2.0" +repository = "https://github.com/DioxusLabs/dioxus/" +homepage = "https://dioxuslabs.com" +keywords = ["web", "desktop", "mobile", "gui", "wasm"] [lib] proc-macro = true @@ -10,7 +16,8 @@ proc-macro = true proc-macro2 = "1.0" quote = "1.0" syn = { version = "2.0", features = ["full"] } -swc_ecma_parser = "17" -swc_ecma_ast = "13" -swc_ecma_visit = "13" -swc_common = "13" +swc_ecma_parser = { version = "=17.0.1", default-features = false } +swc_ecma_ast = { version = "=13.0.0", default-features = false } +swc_ecma_visit = { version = "=13.0.0", default-features = false } +swc_common = { version = "=13.0.1", default-features = false } + diff --git a/packages/use-js-macro/src/lib.rs b/packages/use-js-macro/src/lib.rs index a84c69ceae..09fdabc227 100644 --- a/packages/use-js-macro/src/lib.rs +++ b/packages/use-js-macro/src/lib.rs @@ -1,6 +1,6 @@ use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; -use quote::quote; +use quote::{format_ident, quote}; use std::sync::Arc; use std::{fs, path::Path}; use swc_common::SourceMap; @@ -8,53 +8,69 @@ use swc_ecma_ast::{ Decl, ExportDecl, ExportSpecifier, FnDecl, ModuleExportName, NamedExport, VarDeclarator, }; use swc_ecma_parser::EsSyntax; -use swc_ecma_parser::{Parser, StringInput, Syntax, lexer::Lexer}; +use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax}; use swc_ecma_visit::{Visit, VisitWith}; use syn::{ - Expr, ExprCall, LitStr, Result, Token, parse::{Parse, ParseStream}, - parse_macro_input, + parse_macro_input, Ident, LitStr, Result, Token, }; -struct CallJsInput { +#[derive(Debug, Clone)] +enum ImportSpec { + /// * + All, + /// {greeting, other_func} + Named(Vec), + /// greeting + Single(String), +} + +struct UseJsInput { asset_path: LitStr, - function_call: ExprCall, + import_spec: ImportSpec, } -impl Parse for CallJsInput { +impl Parse for UseJsInput { fn parse(input: ParseStream) -> Result { let asset_path: LitStr = input.parse()?; - input.parse::()?; + input.parse::()?; + + let import_spec = if input.peek(Token![*]) { + input.parse::()?; + ImportSpec::All + } else if input.peek(syn::token::Brace) { + let content; + syn::braced!(content in input); + let mut functions = Vec::new(); + + loop { + let ident: Ident = content.parse()?; + functions.push(ident.to_string()); + + if content.peek(Token![,]) { + content.parse::()?; + if content.is_empty() { + break; + } + } else { + break; + } + } - let function_call: ExprCall = input.parse()?; + ImportSpec::Named(functions) + } else { + let ident: Ident = input.parse()?; + ImportSpec::Single(ident.to_string()) + }; - Ok(CallJsInput { + Ok(UseJsInput { asset_path, - function_call, + import_spec, }) } } -fn extract_function_name(call: &ExprCall) -> Result { - match &*call.func { - Expr::Path(path) => { - if let Some(ident) = path.path.get_ident() { - Ok(ident.to_string()) - } else { - Err(syn::Error::new_spanned( - &path.path, - "Function call must be a simple identifier", - )) - } - } - _ => Err(syn::Error::new_spanned( - &call.func, - "Function call must be a simple identifier", - )), - } -} - -#[derive(Debug)] +#[derive(Debug, Clone)] struct FunctionInfo { name: String, param_count: usize, @@ -193,59 +209,106 @@ fn parse_js_file(file_path: &Path) -> Result> { Ok(visitor.functions) } -fn validate_function_call( +fn get_functions_to_generate( functions: &[FunctionInfo], - function_name: &str, - arg_count: usize, -) -> Result<()> { - let function = functions + import_spec: &ImportSpec, +) -> Result> { + let exported_functions: Vec<_> = functions .iter() - .find(|f| f.name == function_name) - .ok_or_else(|| { - syn::Error::new( - proc_macro2::Span::call_site(), - format!("Function '{}' not found in JavaScript file", function_name), - ) - })?; - - if !function.is_exported { - return Err(syn::Error::new( - proc_macro2::Span::call_site(), - format!( - "Function '{}' is not exported from the JavaScript module", - function_name - ), - )); + .filter(|f| f.is_exported) + .cloned() + .collect(); + + match import_spec { + ImportSpec::All => Ok(exported_functions), + ImportSpec::Single(name) => { + let func = exported_functions + .iter() + .find(|f| &f.name == name) + .ok_or_else(|| { + syn::Error::new( + proc_macro2::Span::call_site(), + format!( + "Function '{}' not found or not exported in JavaScript file", + name + ), + ) + })?; + Ok(vec![func.clone()]) + } + ImportSpec::Named(names) => { + let mut result = Vec::new(); + for name in names { + let func = exported_functions + .iter() + .find(|f| &f.name == name) + .ok_or_else(|| { + syn::Error::new( + proc_macro2::Span::call_site(), + format!( + "Function '{}' not found or not exported in JavaScript file", + name + ), + ) + })?; + result.push(func.clone()); + } + Ok(result) + } } +} - if function.param_count != arg_count { - return Err(syn::Error::new( - proc_macro2::Span::call_site(), - format!( - "Function '{}' expects {} arguments, but {} were provided", - function_name, function.param_count, arg_count - ), - )); +fn generate_function_wrapper(func: &FunctionInfo, asset_path: &LitStr) -> TokenStream2 { + let func_name = format_ident!("{}", func.name); + let js_func_name = &func.name; + + let params: Vec<_> = (0..func.param_count) + .map(|i| format_ident!("arg{}", i)) + .collect(); + + let send_calls: Vec = params + .iter() + .map(|param| quote! { eval.send(#param)?; }) + .collect(); + + let mut js_format = format!(r#"const {{{{ {js_func_name} }}}} = await import("{{}}");"#); + for i in 0..func.param_count { + js_format.push_str(&format!("\nlet arg{} = await dioxus.recv();", i)); + } + js_format.push_str(&format!("\nreturn {}(", js_func_name)); + for i in 0..func.param_count { + if i > 0 { + js_format.push_str(", "); + } + js_format.push_str(&format!("arg{}", i)); } + js_format.push_str(");"); - Ok(()) + let param_types: Vec<_> = (0..func.param_count) + .map(|i| { + let param = format_ident!("arg{}", i); + quote! { #param: impl serde::Serialize } + }) + .collect(); + + quote! { + pub async fn #func_name(#(#param_types),*) -> Result { + const MODULE: Asset = asset!(#asset_path); + let js = format!(#js_format, MODULE); + let eval = document::eval(js.as_str()); + #(#send_calls)* + eval.await + } + } } #[proc_macro] -pub fn call_js(input: TokenStream) -> TokenStream { - // parse - let input = parse_macro_input!(input as CallJsInput); +pub fn use_js(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as UseJsInput); let asset_path = &input.asset_path; - let function_call = &input.function_call; - - let function_name = match extract_function_name(function_call) { - Ok(name) => name, - Err(e) => return TokenStream::from(e.to_compile_error()), - }; + let import_spec = &input.import_spec; - // validate js call - let arg_count = function_call.args.len(); let manifest_dir = match std::env::var("CARGO_MANIFEST_DIR") { Ok(dir) => dir, Err(_) => { @@ -260,42 +323,24 @@ pub fn call_js(input: TokenStream) -> TokenStream { }; let js_file_path = std::path::Path::new(&manifest_dir).join(asset_path.value()); - let functions = match parse_js_file(&js_file_path) { + + let all_functions = match parse_js_file(&js_file_path) { + Ok(funcs) => funcs, + Err(e) => return TokenStream::from(e.to_compile_error()), + }; + + let functions_to_generate = match get_functions_to_generate(&all_functions, import_spec) { Ok(funcs) => funcs, Err(e) => return TokenStream::from(e.to_compile_error()), }; - if let Err(e) = validate_function_call(&functions, &function_name, arg_count) { - return TokenStream::from(e.to_compile_error()); - } - // expand - let send_calls: Vec = function_call - .args + let function_wrappers: Vec = functions_to_generate .iter() - .map(|arg| quote! { eval.send(#arg)?; }) + .map(|func| generate_function_wrapper(func, asset_path)) .collect(); - let mut js_format = format!(r#"const {{{{ {function_name} }}}} = await import("{{}}");"#,); - for i in 0..arg_count { - js_format.push_str(&format!("\nlet arg{} = await dioxus.recv();", i)); - } - js_format.push_str(&format!("\nreturn {}(", function_name)); - for i in 0..arg_count { - if i > 0 { - js_format.push_str(", "); - } - js_format.push_str(&format!("arg{}", i)); - } - js_format.push_str(");"); - let expanded = quote! { - async move { - const MODULE: Asset = asset!(#asset_path); - let js = format!(#js_format, MODULE); - let eval = document::eval(js.as_str()); - #(#send_calls)* - eval.await - } + #(#function_wrappers)* }; TokenStream::from(expanded) From ed029f308a3510b88f7505060164f660b07e98f8 Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Sun, 22 Jun 2025 07:46:57 +0000 Subject: [PATCH 04/15] Add use_js to prelude and workspace --- Cargo.lock | 457 ++++++++++++++++++++++++------------- Cargo.toml | 1 + packages/dioxus/Cargo.toml | 3 +- packages/dioxus/src/lib.rs | 4 + 4 files changed, 309 insertions(+), 156 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a7b8b04058..dccd8003eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -639,6 +639,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + [[package]] name = "ash" version = "0.37.3+1.3.251" @@ -721,9 +727,9 @@ dependencies = [ [[package]] name = "ast_node" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fb5864e2f5bf9fd9797b94b2dfd1554d4c3092b535008b27d7e15c86675a2f" +checksum = "c6ea666cbca3830383d6ce836593e88ade6f61b12c6066c09dc1257c3079a5b6" dependencies = [ "proc-macro2", "quote", @@ -1451,9 +1457,9 @@ dependencies = [ [[package]] name = "better_scoped_tls" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50fd297a11c709be8348aec039c8b91de16075d2b2bdaee1bd562c0875993664" +checksum = "7cd228125315b132eed175bf47619ac79b945b26e56b848ba203ae4ea8603609" dependencies = [ "scoped-tls", ] @@ -1834,27 +1840,9 @@ dependencies = [ [[package]] name = "browserslist-rs" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf0ca73de70c3da94e4194e4a01fe732378f55d47cf4c0588caab22a0dbfa14" -dependencies = [ - "ahash 0.8.12", - "chrono", - "either", - "indexmap 2.9.0", - "itertools 0.13.0", - "nom", - "once_cell", - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "browserslist-rs" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f95aff901882c66e4b642f3f788ceee152ef44f8a5ef12cb1ddee5479c483be" +checksum = "abf24e007a83ff1f58d2441b459fa26124aa1a7367da88948e9940f14e723d06" dependencies = [ "ahash 0.8.12", "chrono", @@ -1965,6 +1953,16 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "bytes-str" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c60b5ce37e0b883c37eb89f79a1e26fbe9c1081945d024eee93e8d91a7e18b3" +dependencies = [ + "bytes", + "serde", +] + [[package]] name = "bytesize" version = "1.3.3" @@ -2525,6 +2523,19 @@ dependencies = [ "memchr", ] +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa 1.0.15", + "ryu", + "static_assertions", +] + [[package]] name = "compact_str" version = "0.8.1" @@ -3615,6 +3626,7 @@ dependencies = [ "dioxus-server", "dioxus-signals", "dioxus-ssr", + "dioxus-use-js-macro", "dioxus-web", "dioxus_server_macro", "env_logger 0.11.8", @@ -3796,7 +3808,7 @@ name = "dioxus-cli-opt" version = "0.7.0-alpha.1" dependencies = [ "anyhow", - "browserslist-rs 0.16.0", + "browserslist-rs", "built", "codemap", "const-serialize", @@ -3813,10 +3825,10 @@ dependencies = [ "serde", "serde_json", "swc_allocator", - "swc_atoms", + "swc_atoms 6.0.0", "swc_bundler", "swc_cached", - "swc_common", + "swc_common 13.0.1", "swc_config", "swc_config_macro", "swc_ecma_ast", @@ -4567,6 +4579,19 @@ dependencies = [ "manganis", ] +[[package]] +name = "dioxus-use-js-macro" +version = "0.7.0-alpha.1" +dependencies = [ + "proc-macro2", + "quote", + "swc_common 13.0.1", + "swc_ecma_ast", + "swc_ecma_parser", + "swc_ecma_visit", + "syn 2.0.101", +] + [[package]] name = "dioxus-web" version = "0.7.0-alpha.1" @@ -5478,9 +5503,9 @@ dependencies = [ [[package]] name = "from_variant" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d7ccf961415e7aa17ef93dcb6c2441faaa8e768abe09e659b908089546f74c5" +checksum = "accfe8b52dc15c1bace718020831f72ce91a4c096709a4d733868f4f4034e22a" dependencies = [ "proc-macro2", "swc_macros_common", @@ -6956,15 +6981,15 @@ dependencies = [ [[package]] name = "hstr" -version = "0.2.17" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a26def229ea95a8709dad32868d975d0dd40235bd2ce82920e4a8fe692b5e0" +checksum = "2d1638d2018a21b9ff65d7fc28c2271c76a5af6ff4f621b204d032bc649763a4" dependencies = [ "hashbrown 0.14.5", "new_debug_unreachable", "once_cell", "phf 0.11.3", - "rustc-hash 1.1.0", + "rustc-hash 2.1.1", "triomphe", ] @@ -8353,7 +8378,7 @@ checksum = "9a73ffa17de66534e4b527232f44aa0a89fad22c4f4e0735f9be35494f058e54" dependencies = [ "ahash 0.8.12", "bitflags 2.9.1", - "browserslist-rs 0.18.1", + "browserslist-rs", "const-str 0.3.2", "cssparser 0.33.0", "cssparser-color", @@ -10049,6 +10074,25 @@ dependencies = [ "system-deps", ] +[[package]] +name = "par-core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96cbd21255b7fb29a5d51ef38a779b517a91abd59e2756c039583f43ef4c90f" +dependencies = [ + "once_cell", +] + +[[package]] +name = "par-iter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eae0176a010bb94b9a67f0eb9da0fd31410817d58850649c54f485124c9a71a" +dependencies = [ + "either", + "par-core", +] + [[package]] name = "parcel_selectors" version = "0.28.2" @@ -11251,7 +11295,7 @@ checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ "bitflags 2.9.1", "cassowary", - "compact_str", + "compact_str 0.8.1", "crossterm 0.28.1", "indoc", "instability", @@ -11449,6 +11493,16 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "regress" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ef7fa9ed0256d64a688a3747d0fef7a88851c18a5e1d57f115f38ec2e09366" +dependencies = [ + "hashbrown 0.15.3", + "memchr", +] + [[package]] name = "relative-path" version = "1.9.3" @@ -12282,6 +12336,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f97841a747eef040fcd2e7b3b9a220a7205926e60488e673d9e4926d27772ce5" +[[package]] +name = "seq-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" + [[package]] name = "serde" version = "1.0.219" @@ -12897,24 +12957,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "sourcemap" -version = "9.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdee719193ae5c919a3ee43f64c2c0dd87f9b9a451d67918a2a5ec2e3c70561c" -dependencies = [ - "base64-simd 0.8.0", - "bitvec", - "data-encoding", - "debugid", - "if_chain", - "rustc-hash 2.1.1", - "serde", - "serde_json", - "unicode-id-start", - "url", -] - [[package]] name = "spake2" version = "0.4.0" @@ -13257,9 +13299,9 @@ dependencies = [ [[package]] name = "string_enum" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9fe66b8ee349846ce2f9557a26b8f1e74843c4a13fb381f9a3d73617a5f956a" +checksum = "24b0e5369ebc6ec5fadbc400599467eb6ba5a614c03de094fcb233dddac2f5f4" dependencies = [ "proc-macro2", "quote", @@ -13597,34 +13639,47 @@ dependencies = [ [[package]] name = "swc_allocator" -version = "2.0.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117d5d3289663f53022ebf157df8a42b3872d7ac759e63abf96b5987b85d4af3" +checksum = "cc6b926f0d94bbb34031fe5449428cfa1268cdc0b31158d6ad9c97e0fc1e79dd" dependencies = [ + "allocator-api2", "bumpalo", "hashbrown 0.14.5", "ptr_meta 0.3.0", - "rustc-hash 1.1.0", + "rustc-hash 2.1.1", "triomphe", ] [[package]] name = "swc_atoms" -version = "3.0.2" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a640bf2e4430a149c87b5eaf377477ce8615ca7cb808054dd20e79e42da5d6ba" +checksum = "9d7077ba879f95406459bc0c81f3141c529b34580bc64d7ab7bd15e7118a0391" dependencies = [ "hstr", "once_cell", - "rustc-hash 1.1.0", + "rustc-hash 2.1.1", + "serde", +] + +[[package]] +name = "swc_atoms" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebf4c40238f7224596754940676547dab6bbf8f33d9f4560b966fc66f2fe00db" +dependencies = [ + "hstr", + "once_cell", + "rustc-hash 2.1.1", "serde", ] [[package]] name = "swc_bundler" -version = "7.0.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c88a91910cd8430f88f8987019cf3a96d92a5d5dded3e0ba8203e0379e4a2f6f" +checksum = "179ea4f155224df226993151f012b0fabed186e10e42daf26de3b73a99de79a4" dependencies = [ "anyhow", "crc 2.1.0", @@ -13632,11 +13687,12 @@ dependencies = [ "is-macro", "once_cell", "parking_lot", - "petgraph 0.6.5", + "petgraph 0.7.1", "radix_fmt", "relative-path", - "swc_atoms", - "swc_common", + "rustc-hash 2.1.1", + "swc_atoms 6.0.0", + "swc_common 13.0.1", "swc_ecma_ast", "swc_ecma_codegen", "swc_ecma_loader", @@ -13645,31 +13701,31 @@ dependencies = [ "swc_ecma_transforms_optimization", "swc_ecma_utils", "swc_ecma_visit", - "swc_fast_graph", "swc_graph_analyzer", "tracing", ] [[package]] name = "swc_cached" -version = "1.0.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b6a5ef4cfec51d3fa30b73600f206453a37fc30cf1141e4644a57b1ed88616" +checksum = "d7133338c3bef796430deced151b0eaa5430710a90e38da19e8e3045e8e36eeb" dependencies = [ - "ahash 0.8.12", "anyhow", "dashmap 5.5.3", "once_cell", "regex", + "rustc-hash 2.1.1", "serde", ] [[package]] name = "swc_common" -version = "5.0.0" +version = "8.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a521e8120dc0401580864a643b5bffa035c29fc3fc41697c972743d4f008ed22" +checksum = "4f8c8e4348383e4154f8d384cdad7e48f5d6d3daef78af376ac4e5ddbbf60c88" dependencies = [ + "anyhow", "ast_node", "better_scoped_tls", "cfg-if", @@ -13678,11 +13734,39 @@ dependencies = [ "new_debug_unreachable", "num-bigint", "once_cell", - "rustc-hash 1.1.0", + "rustc-hash 2.1.1", "serde", "siphasher 0.3.11", "swc_allocator", - "swc_atoms", + "swc_atoms 5.0.0", + "swc_eq_ignore_macros", + "swc_visit", + "tracing", + "unicode-width 0.1.14", + "url", +] + +[[package]] +name = "swc_common" +version = "13.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6865f71f363e63306cedec3f3cf1cb9e80acaa9229261ba2569467a19060c7c8" +dependencies = [ + "anyhow", + "ast_node", + "better_scoped_tls", + "bytes-str", + "cfg-if", + "either", + "from_variant", + "new_debug_unreachable", + "num-bigint", + "once_cell", + "rustc-hash 2.1.1", + "serde", + "siphasher 0.3.11", + "swc_allocator", + "swc_atoms 6.0.0", "swc_eq_ignore_macros", "swc_visit", "termcolor", @@ -13693,24 +13777,30 @@ dependencies = [ [[package]] name = "swc_config" -version = "1.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa30931f9b26af8edcb4cce605909d15dcfd7577220b22c50a2988f2a53c4c1" +checksum = "d94f41e0f3c4c119a06af5e164674b63ae7eb6d7c1c60e46036c4a548f9fbe44" dependencies = [ "anyhow", + "bytes-str", + "dashmap 5.5.3", + "globset", "indexmap 2.9.0", + "once_cell", + "regex", + "regress", + "rustc-hash 2.1.1", "serde", "serde_json", - "sourcemap", - "swc_cached", "swc_config_macro", + "swc_sourcemap", ] [[package]] name = "swc_config_macro" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2ebd37ef52a8555c8c9be78b694d64adcb5e3bc16c928f030d82f1d65fac57" +checksum = "7b416e8ce6de17dc5ea496e10c7012b35bbc0e3fef38d2e065eed936490db0b3" dependencies = [ "proc-macro2", "quote", @@ -13720,48 +13810,54 @@ dependencies = [ [[package]] name = "swc_ecma_ast" -version = "5.0.1" +version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82f448db2d1c52ffd2bd3788d89cafd8b5a75b97f0dc8aae00874dda2647f6b6" +checksum = "f1ddc264ed13ae03aa30e1c89798502f9ddbe765a4ad695054add1074ffbc5cb" dependencies = [ "bitflags 2.9.1", "is-macro", "num-bigint", + "once_cell", "phf 0.11.3", + "rustc-hash 2.1.1", "scoped-tls", "serde", "string_enum", - "swc_atoms", - "swc_common", + "swc_atoms 6.0.0", + "swc_common 13.0.1", "swc_visit", "unicode-id-start", ] [[package]] name = "swc_ecma_codegen" -version = "5.0.1" +version = "15.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f93692de35a77d920ce8d96a46217735e5f86bf42f76cc8f1a60628c347c4c8" +checksum = "1719b3bb5bff1c99cfb6fbd2129e7a7a363d3ddf50e22b95143c1877559d872a" dependencies = [ + "ascii", + "compact_str 0.7.1", "memchr", "num-bigint", "once_cell", "regex", + "rustc-hash 2.1.1", + "ryu-js", "serde", - "sourcemap", "swc_allocator", - "swc_atoms", - "swc_common", + "swc_atoms 6.0.0", + "swc_common 13.0.1", "swc_ecma_ast", "swc_ecma_codegen_macros", + "swc_sourcemap", "tracing", ] [[package]] name = "swc_ecma_codegen_macros" -version = "1.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9a42f479a6475647e248fa9750982c87cd985e19d1016a1fc18a70682305d1" +checksum = "845c8312c82545780f837992bb15fff1dc3464f644465d5ed0abd1196cd090d3" dependencies = [ "proc-macro2", "quote", @@ -13769,11 +13865,37 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "swc_ecma_lexer" +version = "17.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc377700d6e293f2c357660769625b97f9318b057d90ebc11c5f1e6d3b22729" +dependencies = [ + "arrayvec", + "bitflags 2.9.1", + "either", + "new_debug_unreachable", + "num-bigint", + "num-traits", + "phf 0.11.3", + "rustc-hash 2.1.1", + "seq-macro", + "serde", + "smallvec", + "smartstring", + "stacker", + "swc_atoms 6.0.0", + "swc_common 13.0.1", + "swc_ecma_ast", + "tracing", + "typed-arena", +] + [[package]] name = "swc_ecma_loader" -version = "5.0.0" +version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a19b132079bfcd19d6fdabce7e55ece93a30787f3b8684c8646ddaf2237812d" +checksum = "9b08fa5f55ac0188a35a75f27574329e129b06cbb0d517ab7b2093eff45b2745" dependencies = [ "anyhow", "dashmap 5.5.3", @@ -13783,35 +13905,39 @@ dependencies = [ "parking_lot", "path-clean", "pathdiff", + "rustc-hash 2.1.1", "serde", "serde_json", - "swc_atoms", - "swc_common", + "swc_atoms 6.0.0", + "swc_common 13.0.1", "tracing", ] [[package]] name = "swc_ecma_minifier" -version = "7.0.1" +version = "23.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164291b068cca947462d87ede1baf276f69da137db1a0c66059a8aed81b785b2" +checksum = "50b6d32c649a7c78973768b629c8d60f06774be2a648b03c9c342200d4fd9f8b" dependencies = [ "arrayvec", + "bitflags 2.9.1", "indexmap 2.9.0", "num-bigint", "num_cpus", "once_cell", + "par-core", + "par-iter", "parking_lot", "phf 0.11.3", "radix_fmt", "regex", - "rustc-hash 1.1.0", + "rustc-hash 2.1.1", "ryu-js", "serde", "serde_json", "swc_allocator", - "swc_atoms", - "swc_common", + "swc_atoms 6.0.0", + "swc_common 13.0.1", "swc_config", "swc_ecma_ast", "swc_ecma_codegen", @@ -13821,62 +13947,65 @@ dependencies = [ "swc_ecma_usage_analyzer", "swc_ecma_utils", "swc_ecma_visit", - "swc_parallel", "swc_timer", "tracing", ] [[package]] name = "swc_ecma_parser" -version = "6.0.2" +version = "17.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b92d3a25349d7f612c38d940f09f9c19c7b7aa3bf4d22fbe31ea44fd5354de02" +checksum = "3ff5b5fbe4316b7d1272ac6f204a871f0d5785755d6a14ddf38e7fa8af73be30" dependencies = [ + "arrayvec", + "bitflags 2.9.1", "either", "new_debug_unreachable", "num-bigint", "num-traits", "phf 0.11.3", + "rustc-hash 2.1.1", "serde", "smallvec", "smartstring", "stacker", - "swc_atoms", - "swc_common", + "swc_atoms 6.0.0", + "swc_common 13.0.1", "swc_ecma_ast", + "swc_ecma_lexer", "tracing", "typed-arena", ] [[package]] name = "swc_ecma_transforms_base" -version = "7.0.0" +version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fdc36d220bcd51f70b1d78bdd8c1e1a172b4e594c385bdd9614b84a7c0e112" +checksum = "a6a9971c1f27f6b3ebcad7424e81861c35bfd009be81786da71a6e3a7002d808" dependencies = [ "better_scoped_tls", "bitflags 2.9.1", "indexmap 2.9.0", "once_cell", + "par-core", "phf 0.11.3", - "rustc-hash 1.1.0", + "rustc-hash 2.1.1", "serde", "smallvec", - "swc_atoms", - "swc_common", + "swc_atoms 6.0.0", + "swc_common 13.0.1", "swc_ecma_ast", "swc_ecma_parser", "swc_ecma_utils", "swc_ecma_visit", - "swc_parallel", "tracing", ] [[package]] name = "swc_ecma_transforms_macros" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6845dfb88569f3e8cd05901505916a8ebe98be3922f94769ca49f84e8ccec8f7" +checksum = "bc777288799bf6786e5200325a56e4fbabba590264a4a48a0c70b16ad0cf5cd8" dependencies = [ "proc-macro2", "quote", @@ -13886,38 +14015,40 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_optimization" -version = "7.0.1" +version = "19.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e4232534b28fc57b745e8c709723544e5548af29abaa62281eab427099f611d" +checksum = "148b59208253c618c0e1c363f6f5bdd6ff309fb09743dfa4c0fa0b1bd220e454" dependencies = [ + "bytes-str", "dashmap 5.5.3", "indexmap 2.9.0", "once_cell", - "petgraph 0.6.5", - "rustc-hash 1.1.0", + "par-core", + "petgraph 0.7.1", + "rustc-hash 2.1.1", "serde_json", - "swc_atoms", - "swc_common", + "swc_atoms 6.0.0", + "swc_common 13.0.1", "swc_ecma_ast", "swc_ecma_parser", "swc_ecma_transforms_base", "swc_ecma_transforms_macros", "swc_ecma_utils", "swc_ecma_visit", - "swc_fast_graph", "tracing", ] [[package]] name = "swc_ecma_usage_analyzer" -version = "7.0.0" +version = "19.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15eb86aaa82d7ec4c1a6c3a8a824b1fdbbaace73c3ed81035a1fbbac49f8e0bd" +checksum = "c357c7ff7ae2bf50adbe89c04ed94c9b99d34563df69386aba15a05e4560fc9a" dependencies = [ + "bitflags 2.9.1", "indexmap 2.9.0", - "rustc-hash 1.1.0", - "swc_atoms", - "swc_common", + "rustc-hash 2.1.1", + "swc_atoms 6.0.0", + "swc_common 13.0.1", "swc_ecma_ast", "swc_ecma_utils", "swc_ecma_visit", @@ -13927,34 +14058,35 @@ dependencies = [ [[package]] name = "swc_ecma_utils" -version = "7.0.0" +version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c9d22b4883dc6d6c21a8216bbf5aacedd7f104230b1557367ae126a2ec3a2b5" +checksum = "4e6aa9ebca452a7594290928de2a90464b92ae1634d1c22e2593a77b32953016" dependencies = [ "indexmap 2.9.0", "num_cpus", "once_cell", - "rustc-hash 1.1.0", + "par-core", + "par-iter", + "rustc-hash 2.1.1", "ryu-js", - "swc_atoms", - "swc_common", + "swc_atoms 6.0.0", + "swc_common 13.0.1", "swc_ecma_ast", "swc_ecma_visit", - "swc_parallel", "tracing", "unicode-id", ] [[package]] name = "swc_ecma_visit" -version = "5.0.0" +version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b04c06c1805bda18c27165560f1617a57453feb9fb0638d90839053641af42d4" +checksum = "7ad28e3449b376bfe1f2bde28bfcf305961ba23c1e205bedb03a7c108a1d1ff6" dependencies = [ "new_debug_unreachable", "num-bigint", - "swc_atoms", - "swc_common", + "swc_atoms 6.0.0", + "swc_common 13.0.1", "swc_ecma_ast", "swc_visit", "tracing", @@ -13962,9 +14094,9 @@ dependencies = [ [[package]] name = "swc_eq_ignore_macros" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96e15288bf385ab85eb83cff7f9e2d834348da58d0a31b33bdb572e66ee413e" +checksum = "c16ce73424a6316e95e09065ba6a207eba7765496fed113702278b7711d4b632" dependencies = [ "proc-macro2", "quote", @@ -13973,34 +14105,34 @@ dependencies = [ [[package]] name = "swc_fast_graph" -version = "6.0.0" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c22e0a0478b1b06610453a97c8371cafa742e371a79aff860ccfbabe1ab160a7" +checksum = "bd24b9798b0538803d0a69cffa5f5e051087fa2bd0d23e5a2f05d32edf9ab671" dependencies = [ "indexmap 2.9.0", "petgraph 0.6.5", - "rustc-hash 1.1.0", - "swc_common", + "rustc-hash 2.1.1", + "swc_common 8.1.1", ] [[package]] name = "swc_graph_analyzer" -version = "5.0.0" +version = "14.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b9841af596d2ddb37e56defca81387b60a14863e251cede839d1e349e6209d" +checksum = "eb4b7c00f9a8b2038ae9b68b9762f8d3e7b8b48323d3985a55bae9e478531b87" dependencies = [ "auto_impl", - "petgraph 0.6.5", - "swc_common", - "swc_fast_graph", + "petgraph 0.7.1", + "rustc-hash 2.1.1", + "swc_common 13.0.1", "tracing", ] [[package]] name = "swc_macros_common" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a509f56fca05b39ba6c15f3e58636c3924c78347d63853632ed2ffcb6f5a0ac7" +checksum = "aae1efbaa74943dc5ad2a2fb16cbd78b77d7e4d63188f3c5b4df2b4dcd2faaae" dependencies = [ "proc-macro2", "quote", @@ -14009,13 +14141,32 @@ dependencies = [ [[package]] name = "swc_parallel" -version = "1.0.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cde1a0f344924be62d01de0c8a98e840feae271b77dc8c1d9d2e340687225c" +checksum = "8f16052d5123ec45c1c49100781363f3f4e4a6be2da6d82f473b79db1e3abeb8" dependencies = [ "once_cell", ] +[[package]] +name = "swc_sourcemap" +version = "9.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9755c673c6a83c461e98fa018f681adb8394a3f44f89a06f27e80fd4fe4fa1e4" +dependencies = [ + "base64-simd 0.8.0", + "bitvec", + "bytes-str", + "data-encoding", + "debugid", + "if_chain", + "rustc-hash 2.1.1", + "serde", + "serde_json", + "unicode-id-start", + "url", +] + [[package]] name = "swc_timer" version = "1.0.0" @@ -14027,9 +14178,9 @@ dependencies = [ [[package]] name = "swc_visit" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9138b6a36bbe76dd6753c4c0794f7e26480ea757bee499738bedbbb3ae3ec5f3" +checksum = "62fb71484b486c185e34d2172f0eabe7f4722742aad700f426a494bb2de232a2" dependencies = [ "either", "new_debug_unreachable", @@ -15490,10 +15641,6 @@ dependencies = [ "url", ] -[[package]] -name = "use-js-macro" -version = "0.7.0-alpha.1" - [[package]] name = "usvg" version = "0.45.1" diff --git a/Cargo.toml b/Cargo.toml index 47c41aadf6..69272e5320 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -176,6 +176,7 @@ dioxus-logger = { path = "packages/logger", version = "0.7.0-alpha.1" } dioxus-native = { path = "packages/native", version = "0.7.0-alpha.1" } dioxus-asset-resolver = { path = "packages/asset-resolver", version = "0.7.0-alpha.1" } dioxus-config-macros = { path = "packages/config-macros", version = "0.7.0-alpha.1" } +dioxus-use-js-macro = { path = "packages/use-js-macro", version = "0.7.0-alpha.1" } const-serialize = { path = "packages/const-serialize", version = "0.7.0-alpha.1" } const-serialize-macro = { path = "packages/const-serialize-macro", version = "0.7.0-alpha.1" } generational-box = { path = "packages/generational-box", version = "0.7.0-alpha.1" } diff --git a/packages/dioxus/Cargo.toml b/packages/dioxus/Cargo.toml index cf1992a5f9..a2eeff9e9d 100644 --- a/packages/dioxus/Cargo.toml +++ b/packages/dioxus/Cargo.toml @@ -30,6 +30,7 @@ dioxus-server = { workspace = true, optional = true } dioxus-ssr = { workspace = true, optional = true } dioxus-native = { workspace = true, optional = true } dioxus_server_macro = { workspace = true, optional = true } +dioxus-use-js-macro = { workspace = true, optional = true } manganis = { workspace = true, features = ["dioxus"], optional = true } dioxus-logger = { workspace = true, optional = true } warnings = { workspace = true, optional = true } @@ -58,7 +59,7 @@ default = [ ] minimal = ["macro", "html", "signals", "hooks", "launch"] signals = ["dep:dioxus-signals"] -macro = ["dep:dioxus-core-macro"] +macro = ["dep:dioxus-core-macro", "dep:dioxus-use-js-macro"] html = ["dep:dioxus-html"] hooks = ["dep:dioxus-hooks"] devtools = ["dep:dioxus-devtools", "dioxus-web?/devtools", "dioxus-fullstack?/devtools"] diff --git a/packages/dioxus/src/lib.rs b/packages/dioxus/src/lib.rs index f14b72b811..098ec921ea 100644 --- a/packages/dioxus/src/lib.rs +++ b/packages/dioxus/src/lib.rs @@ -150,6 +150,10 @@ pub mod prelude { #[allow(deprecated)] pub use dioxus_core_macro::{component, rsx, Props}; + #[cfg(feature = "macro")] + #[cfg_attr(docsrs, doc(cfg(feature = "macro")))] + pub use dioxus_use_js_macro::use_js; + #[cfg(feature = "launch")] #[cfg_attr(docsrs, doc(cfg(feature = "launch")))] pub use dioxus_config_macro::*; From f24c672614c4e773c01165b9ae27c2ea009f3a1e Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Sun, 22 Jun 2025 07:47:16 +0000 Subject: [PATCH 05/15] Add use_js example --- examples/assets/example.js | 11 +++++++++ examples/use_js_macro.rs | 49 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 examples/assets/example.js create mode 100644 examples/use_js_macro.rs diff --git a/examples/assets/example.js b/examples/assets/example.js new file mode 100644 index 0000000000..ac0b465ebc --- /dev/null +++ b/examples/assets/example.js @@ -0,0 +1,11 @@ +export function greeting(from, to) { + return `Hello ${to}, this is ${from} speaking from JavaScript!`; +} + +export function add(a, b) { + return a + b; +} + +export function processData(data) { + return data.map(item => item.toUpperCase()); +} \ No newline at end of file diff --git a/examples/use_js_macro.rs b/examples/use_js_macro.rs new file mode 100644 index 0000000000..5e5beec55d --- /dev/null +++ b/examples/use_js_macro.rs @@ -0,0 +1,49 @@ +use dioxus::prelude::*; + +// Generate the greeting function at compile time +// use_js!("examples/assets/example.js"::greeting); + +// Or generate multiple functions: +use_js!("examples/assets/example.js"::{greeting, add}); + +// Or generate all exported functions: +// use_js!("examples/assets/example.js"::*); + +fn main() { + launch(App); +} + +#[component] +fn App() -> Element { + let future = use_resource(|| async move { + let from = "dave"; + let to = "john"; + + // Now we can call the generated function directly! + let greeting_result = greeting(from, to) + .await + .map_err(Box::::from)?; + let greeting: String = + serde_json::from_value(greeting_result).map_err(Box::::from)?; + Ok::>(greeting) + }); + + rsx!( + div { + h1 { "Dioxus `use_js!` macro example!" } + { + match &*future.read() { + Some(Ok(greeting)) => rsx! { + p { "Greeting from JavaScript: {greeting}" } + }, + Some(Err(e)) => rsx! { + p { "Error: {e}" } + }, + None => rsx! { + p { "Running js..." } + }, + } + } + } + ) +} From d1fcf69ba0c25b856ed19eb3251cc842589f6a36 Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Sun, 22 Jun 2025 09:27:32 +0000 Subject: [PATCH 06/15] Add comment extraction for use_js --- packages/use-js-macro/src/lib.rs | 81 +++++++++++++++++++++++++++----- 1 file changed, 70 insertions(+), 11 deletions(-) diff --git a/packages/use-js-macro/src/lib.rs b/packages/use-js-macro/src/lib.rs index 09fdabc227..a8b6996593 100644 --- a/packages/use-js-macro/src/lib.rs +++ b/packages/use-js-macro/src/lib.rs @@ -1,9 +1,10 @@ use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; -use std::sync::Arc; use std::{fs, path::Path}; -use swc_common::SourceMap; +use swc_common::comments::{CommentKind, Comments}; +use swc_common::Spanned; +use swc_common::{comments::SingleThreadedComments, SourceMap, Span}; use swc_ecma_ast::{ Decl, ExportDecl, ExportSpecifier, FnDecl, ModuleExportName, NamedExport, VarDeclarator, }; @@ -75,16 +76,54 @@ struct FunctionInfo { name: String, param_count: usize, is_exported: bool, + /// The stripped lines + doc_comment: Vec, } struct FunctionVisitor { functions: Vec, + comments: SingleThreadedComments, } impl FunctionVisitor { - fn new() -> Self { + fn new(comments: SingleThreadedComments) -> Self { Self { functions: Vec::new(), + comments, + } + } + + fn extract_doc_comment(&self, span: Span) -> Vec { + // Get leading comments for the span + let leading_comment = self.comments.get_leading(span.lo()); + + if let Some(comments) = leading_comment { + let mut doc_lines = Vec::new(); + + for comment in comments.iter() { + let comment_text = &comment.text; + match comment.kind { + // Handle `///`. `//` is already stripped + CommentKind::Line => { + if let Some(content) = comment_text.strip_prefix("/") { + let cleaned = content.trim_start(); + doc_lines.push(cleaned.to_string()); + } + } + // Handle `/*` `*/`. `/*` `*/` is already stripped + CommentKind::Block => { + for line in comment_text.lines() { + if let Some(cleaned) = line.trim_start().strip_prefix("*") { + doc_lines.push(cleaned.to_string()); + } + } + } + }; + } + + doc_lines + } else { + Vec::new() } } } @@ -92,10 +131,13 @@ impl FunctionVisitor { impl Visit for FunctionVisitor { /// Visit function declarations: function foo() {} fn visit_fn_decl(&mut self, node: &FnDecl) { + let doc_comment = self.extract_doc_comment(node.span()); + self.functions.push(FunctionInfo { name: node.ident.sym.to_string(), param_count: node.function.params.len(), is_exported: false, + doc_comment, }); node.visit_children_with(self); } @@ -104,12 +146,15 @@ impl Visit for FunctionVisitor { fn visit_var_declarator(&mut self, node: &VarDeclarator) { if let swc_ecma_ast::Pat::Ident(ident) = &node.name { if let Some(init) = &node.init { + let doc_comment = self.extract_doc_comment(node.span()); + match &**init { swc_ecma_ast::Expr::Fn(fn_expr) => { self.functions.push(FunctionInfo { name: ident.id.sym.to_string(), param_count: fn_expr.function.params.len(), is_exported: false, + doc_comment, }); } swc_ecma_ast::Expr::Arrow(arrow_fn) => { @@ -117,6 +162,7 @@ impl Visit for FunctionVisitor { name: ident.id.sym.to_string(), param_count: arrow_fn.params.len(), is_exported: false, + doc_comment, }); } _ => {} @@ -130,10 +176,13 @@ impl Visit for FunctionVisitor { fn visit_export_decl(&mut self, node: &ExportDecl) { match &node.decl { Decl::Fn(fn_decl) => { + let doc_comment = self.extract_doc_comment(node.span()); + self.functions.push(FunctionInfo { name: fn_decl.ident.sym.to_string(), param_count: fn_decl.function.params.len(), is_exported: true, + doc_comment, }); } _ => {} @@ -174,20 +223,17 @@ fn parse_js_file(file_path: &Path) -> Result> { ) })?; - let cm = Arc::new(SourceMap::default()); + let cm = SourceMap::default(); let fm = cm.new_source_file( swc_common::FileName::Custom(file_path.display().to_string()).into(), js_content.clone(), ); - + let comments = SingleThreadedComments::default(); let lexer = Lexer::new( - Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), + Syntax::Es(EsSyntax::default()), Default::default(), StringInput::from(&*fm), - None, + Some(&comments), ); let mut parser = Parser::new_from(lexer); @@ -203,7 +249,7 @@ fn parse_js_file(file_path: &Path) -> Result> { ) })?; - let mut visitor = FunctionVisitor::new(); + let mut visitor = FunctionVisitor::new(comments); module.visit_with(&mut visitor); Ok(visitor.functions) @@ -291,7 +337,20 @@ fn generate_function_wrapper(func: &FunctionInfo, asset_path: &LitStr) -> TokenS }) .collect(); + // Generate documentation comment if available - preserve original JSDoc format + let doc_comment = if func.doc_comment.is_empty() { + quote! {} + } else { + let doc_lines: Vec<_> = func + .doc_comment + .iter() + .map(|line| quote! { #[doc = #line] }) + .collect(); + quote! { #(#doc_lines)* } + }; + quote! { + #doc_comment pub async fn #func_name(#(#param_types),*) -> Result { const MODULE: Asset = asset!(#asset_path); let js = format!(#js_format, MODULE); From 76e1f897c015e6311c75e500f29f41051207d56d Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Sun, 22 Jun 2025 09:40:04 +0000 Subject: [PATCH 07/15] Keep parameter names in use_js macro --- packages/use-js-macro/src/lib.rs | 63 +++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/packages/use-js-macro/src/lib.rs b/packages/use-js-macro/src/lib.rs index a8b6996593..a2e2455a97 100644 --- a/packages/use-js-macro/src/lib.rs +++ b/packages/use-js-macro/src/lib.rs @@ -6,7 +6,8 @@ use swc_common::comments::{CommentKind, Comments}; use swc_common::Spanned; use swc_common::{comments::SingleThreadedComments, SourceMap, Span}; use swc_ecma_ast::{ - Decl, ExportDecl, ExportSpecifier, FnDecl, ModuleExportName, NamedExport, VarDeclarator, + Decl, ExportDecl, ExportSpecifier, FnDecl, ModuleExportName, NamedExport, Param, Pat, + VarDeclarator, }; use swc_ecma_parser::EsSyntax; use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax}; @@ -74,7 +75,7 @@ impl Parse for UseJsInput { #[derive(Debug, Clone)] struct FunctionInfo { name: String, - param_count: usize, + params: Vec, is_exported: bool, /// The stripped lines doc_comment: Vec, @@ -128,6 +129,33 @@ impl FunctionVisitor { } } +fn function_params_to_names(params: &Vec) -> Vec { + params + .iter() + .enumerate() + .map(|(i, param)| { + if let Some(ident) = param.pat.as_ident() { + ident.id.sym.to_string() + } else { + format!("arg{}", i) + } + }) + .collect() +} + +fn function_pat_to_names(pats: &Vec) -> Vec { + pats.iter() + .enumerate() + .map(|(i, pat)| { + if let Some(ident) = pat.as_ident() { + ident.id.sym.to_string() + } else { + format!("arg{}", i) + } + }) + .collect() +} + impl Visit for FunctionVisitor { /// Visit function declarations: function foo() {} fn visit_fn_decl(&mut self, node: &FnDecl) { @@ -135,7 +163,7 @@ impl Visit for FunctionVisitor { self.functions.push(FunctionInfo { name: node.ident.sym.to_string(), - param_count: node.function.params.len(), + params: function_params_to_names(&node.function.params), is_exported: false, doc_comment, }); @@ -152,7 +180,7 @@ impl Visit for FunctionVisitor { swc_ecma_ast::Expr::Fn(fn_expr) => { self.functions.push(FunctionInfo { name: ident.id.sym.to_string(), - param_count: fn_expr.function.params.len(), + params: function_params_to_names(&fn_expr.function.params), is_exported: false, doc_comment, }); @@ -160,7 +188,7 @@ impl Visit for FunctionVisitor { swc_ecma_ast::Expr::Arrow(arrow_fn) => { self.functions.push(FunctionInfo { name: ident.id.sym.to_string(), - param_count: arrow_fn.params.len(), + params: function_pat_to_names(&arrow_fn.params), is_exported: false, doc_comment, }); @@ -180,7 +208,7 @@ impl Visit for FunctionVisitor { self.functions.push(FunctionInfo { name: fn_decl.ident.sym.to_string(), - param_count: fn_decl.function.params.len(), + params: function_params_to_names(&fn_decl.function.params), is_exported: true, doc_comment, }); @@ -308,31 +336,30 @@ fn generate_function_wrapper(func: &FunctionInfo, asset_path: &LitStr) -> TokenS let func_name = format_ident!("{}", func.name); let js_func_name = &func.name; - let params: Vec<_> = (0..func.param_count) - .map(|i| format_ident!("arg{}", i)) - .collect(); - - let send_calls: Vec = params + let send_calls: Vec = func + .params .iter() .map(|param| quote! { eval.send(#param)?; }) .collect(); let mut js_format = format!(r#"const {{{{ {js_func_name} }}}} = await import("{{}}");"#); - for i in 0..func.param_count { - js_format.push_str(&format!("\nlet arg{} = await dioxus.recv();", i)); + for param in func.params.iter() { + js_format.push_str(&format!("\nlet {} = await dioxus.recv();", param)); } js_format.push_str(&format!("\nreturn {}(", js_func_name)); - for i in 0..func.param_count { + for (i, param) in func.params.iter().enumerate() { if i > 0 { js_format.push_str(", "); } - js_format.push_str(&format!("arg{}", i)); + js_format.push_str(param.as_str()); } js_format.push_str(");"); - let param_types: Vec<_> = (0..func.param_count) - .map(|i| { - let param = format_ident!("arg{}", i); + let param_types: Vec<_> = func + .params + .iter() + .map(|param| { + let param = format_ident!("{}", param); quote! { #param: impl serde::Serialize } }) .collect(); From 8e727200823f415d5a198289e0f9dc624e2d9e28 Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Sun, 22 Jun 2025 09:40:21 +0000 Subject: [PATCH 08/15] Add comments to use_js macro example js --- examples/assets/example.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/assets/example.js b/examples/assets/example.js index ac0b465ebc..e212ac61de 100644 --- a/examples/assets/example.js +++ b/examples/assets/example.js @@ -1,7 +1,12 @@ +/** + * This is a doc comment + * second line +*/ export function greeting(from, to) { return `Hello ${to}, this is ${from} speaking from JavaScript!`; } +/// This is another doc comment export function add(a, b) { return a + b; } From edcca1ebb3cf626e6b87dcb84ce13b501162e87d Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Sun, 22 Jun 2025 10:00:21 +0000 Subject: [PATCH 09/15] Bind use_js macro param to generated function name --- packages/use-js-macro/src/lib.rs | 84 +++++++++++++++----------------- 1 file changed, 38 insertions(+), 46 deletions(-) diff --git a/packages/use-js-macro/src/lib.rs b/packages/use-js-macro/src/lib.rs index a2e2455a97..a69b3d9c05 100644 --- a/packages/use-js-macro/src/lib.rs +++ b/packages/use-js-macro/src/lib.rs @@ -22,9 +22,9 @@ enum ImportSpec { /// * All, /// {greeting, other_func} - Named(Vec), + Named(Vec), /// greeting - Single(String), + Single(Ident), } struct UseJsInput { @@ -47,7 +47,7 @@ impl Parse for UseJsInput { loop { let ident: Ident = content.parse()?; - functions.push(ident.to_string()); + functions.push(ident); if content.peek(Token![,]) { content.parse::()?; @@ -62,7 +62,7 @@ impl Parse for UseJsInput { ImportSpec::Named(functions) } else { let ident: Ident = input.parse()?; - ImportSpec::Single(ident.to_string()) + ImportSpec::Single(ident) }; Ok(UseJsInput { @@ -75,6 +75,8 @@ impl Parse for UseJsInput { #[derive(Debug, Clone)] struct FunctionInfo { name: String, + /// If specified in the use declaration + name_ident: Option, params: Vec, is_exported: bool, /// The stripped lines @@ -163,6 +165,7 @@ impl Visit for FunctionVisitor { self.functions.push(FunctionInfo { name: node.ident.sym.to_string(), + name_ident: None, params: function_params_to_names(&node.function.params), is_exported: false, doc_comment, @@ -180,6 +183,7 @@ impl Visit for FunctionVisitor { swc_ecma_ast::Expr::Fn(fn_expr) => { self.functions.push(FunctionInfo { name: ident.id.sym.to_string(), + name_ident: None, params: function_params_to_names(&fn_expr.function.params), is_exported: false, doc_comment, @@ -188,6 +192,7 @@ impl Visit for FunctionVisitor { swc_ecma_ast::Expr::Arrow(arrow_fn) => { self.functions.push(FunctionInfo { name: ident.id.sym.to_string(), + name_ident: None, params: function_pat_to_names(&arrow_fn.params), is_exported: false, doc_comment, @@ -208,6 +213,7 @@ impl Visit for FunctionVisitor { self.functions.push(FunctionInfo { name: fn_decl.ident.sym.to_string(), + name_ident: None, params: function_params_to_names(&fn_decl.function.params), is_exported: true, doc_comment, @@ -283,49 +289,34 @@ fn parse_js_file(file_path: &Path) -> Result> { Ok(visitor.functions) } +fn remove_function_info(name: &str, functions: &mut Vec) -> Result { + if let Some(pos) = functions.iter().position(|f| f.name == name) { + Ok(functions.remove(pos)) + } else { + Err(syn::Error::new( + proc_macro2::Span::call_site(), + format!("Function '{}' not found in JavaScript file", name), + )) + } +} + fn get_functions_to_generate( - functions: &[FunctionInfo], - import_spec: &ImportSpec, + mut functions: Vec, + import_spec: ImportSpec, ) -> Result> { - let exported_functions: Vec<_> = functions - .iter() - .filter(|f| f.is_exported) - .cloned() - .collect(); - match import_spec { - ImportSpec::All => Ok(exported_functions), + ImportSpec::All => Ok(functions), ImportSpec::Single(name) => { - let func = exported_functions - .iter() - .find(|f| &f.name == name) - .ok_or_else(|| { - syn::Error::new( - proc_macro2::Span::call_site(), - format!( - "Function '{}' not found or not exported in JavaScript file", - name - ), - ) - })?; - Ok(vec![func.clone()]) + let mut func = remove_function_info(name.to_string().as_str(), &mut functions)?; + func.name_ident.replace(name); + Ok(vec![func]) } ImportSpec::Named(names) => { let mut result = Vec::new(); for name in names { - let func = exported_functions - .iter() - .find(|f| &f.name == name) - .ok_or_else(|| { - syn::Error::new( - proc_macro2::Span::call_site(), - format!( - "Function '{}' not found or not exported in JavaScript file", - name - ), - ) - })?; - result.push(func.clone()); + let mut func = remove_function_info(name.to_string().as_str(), &mut functions)?; + func.name_ident.replace(name); + result.push(func); } Ok(result) } @@ -333,15 +324,13 @@ fn get_functions_to_generate( } fn generate_function_wrapper(func: &FunctionInfo, asset_path: &LitStr) -> TokenStream2 { - let func_name = format_ident!("{}", func.name); - let js_func_name = &func.name; - let send_calls: Vec = func .params .iter() .map(|param| quote! { eval.send(#param)?; }) .collect(); + let js_func_name = &func.name; let mut js_format = format!(r#"const {{{{ {js_func_name} }}}} = await import("{{}}");"#); for param in func.params.iter() { js_format.push_str(&format!("\nlet {} = await dioxus.recv();", param)); @@ -376,6 +365,10 @@ fn generate_function_wrapper(func: &FunctionInfo, asset_path: &LitStr) -> TokenS quote! { #(#doc_lines)* } }; + let func_name = func + .name_ident + .as_ref() + .expect("Function name should be set by the import spec"); quote! { #doc_comment pub async fn #func_name(#(#param_types),*) -> Result { @@ -392,9 +385,6 @@ fn generate_function_wrapper(func: &FunctionInfo, asset_path: &LitStr) -> TokenS pub fn use_js(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as UseJsInput); - let asset_path = &input.asset_path; - let import_spec = &input.import_spec; - let manifest_dir = match std::env::var("CARGO_MANIFEST_DIR") { Ok(dir) => dir, Err(_) => { @@ -408,6 +398,7 @@ pub fn use_js(input: TokenStream) -> TokenStream { } }; + let asset_path = &input.asset_path; let js_file_path = std::path::Path::new(&manifest_dir).join(asset_path.value()); let all_functions = match parse_js_file(&js_file_path) { @@ -415,7 +406,8 @@ pub fn use_js(input: TokenStream) -> TokenStream { Err(e) => return TokenStream::from(e.to_compile_error()), }; - let functions_to_generate = match get_functions_to_generate(&all_functions, import_spec) { + let import_spec = input.import_spec; + let functions_to_generate = match get_functions_to_generate(all_functions, import_spec) { Ok(funcs) => funcs, Err(e) => return TokenStream::from(e.to_compile_error()), }; From 5a0fdfa4cc29d366b091634ed08206440d8ab1b5 Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Sun, 22 Jun 2025 10:03:13 +0000 Subject: [PATCH 10/15] use_js correct param name --- packages/use-js-macro/src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/use-js-macro/src/lib.rs b/packages/use-js-macro/src/lib.rs index a69b3d9c05..a30d224eb3 100644 --- a/packages/use-js-macro/src/lib.rs +++ b/packages/use-js-macro/src/lib.rs @@ -327,7 +327,12 @@ fn generate_function_wrapper(func: &FunctionInfo, asset_path: &LitStr) -> TokenS let send_calls: Vec = func .params .iter() - .map(|param| quote! { eval.send(#param)?; }) + .map(|param| { + let param = format_ident!("{}", param); + quote! { + eval.send(#param)?; + } + }) .collect(); let js_func_name = &func.name; From bae43dfc5c21cb5149bb36d80e07d3a5df6d1d36 Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Sun, 22 Jun 2025 10:07:32 +0000 Subject: [PATCH 11/15] chore: Update use-js-macro readme --- packages/use-js-macro/README.md | 41 +++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/packages/use-js-macro/README.md b/packages/use-js-macro/README.md index 35a3a472e1..37e297b20c 100644 --- a/packages/use-js-macro/README.md +++ b/packages/use-js-macro/README.md @@ -1,18 +1,20 @@ -# Dioxus call-js +# Dioxus use-js-macro -A macro to simplify calling javascript from rust +A to create rust binding to javascript functions ## Usage -Add `dioxus-call-js` to your `Cargo.toml`: -```toml -[dependencies] -dioxus-call-js = "0.1" -``` - Example: ```rust use dioxus::prelude::*; -use dioxus_call_js::call_js; + +// Generate the greeting function at compile time +use_js!("assets/example.js"::greeting); + +// Or generate multiple functions: +// use_js!("assets/example.js"::{greeting, add}); + +// Or generate all exported functions: +// use_js!("assets/example.js"::*); fn main() { launch(App); @@ -23,25 +25,34 @@ fn App() -> Element { let future = use_resource(|| async move { let from = "dave"; let to = "john"; - let greeting = call_js!("assets/example.js", greeting(from, to)).await.unwrap(); - let greeting: String = serde_json::from_value(greeting).unwrap(); - return greeting; + + // Now we can call the generated function directly! + let greeting_result = greeting(from, to) + .await + .map_err(Box::::from)?; + let greeting: String = + serde_json::from_value(greeting_result).map_err(Box::::from)?; + Ok::>(greeting) }); rsx!( div { - h1 { "Dioxus `call_js!` macro example!" } + h1 { "Dioxus `use_js!` macro example!" } { match &*future.read() { - Some(greeting) => rsx! { + Some(Ok(greeting)) => rsx! { p { "Greeting from JavaScript: {greeting}" } }, + Some(Err(e)) => rsx! { + p { "Error: {e}" } + }, None => rsx! { - p { "Running js" } + p { "Running js..." } }, } } } ) } + ``` \ No newline at end of file From 7f839a979af6f475f0df17ece0257facdb4afe7f Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Sun, 22 Jun 2025 10:19:52 +0000 Subject: [PATCH 12/15] use_js touch up --- packages/use-js-macro/src/lib.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/use-js-macro/src/lib.rs b/packages/use-js-macro/src/lib.rs index a30d224eb3..ff8618795e 100644 --- a/packages/use-js-macro/src/lib.rs +++ b/packages/use-js-macro/src/lib.rs @@ -286,6 +286,10 @@ fn parse_js_file(file_path: &Path) -> Result> { let mut visitor = FunctionVisitor::new(comments); module.visit_with(&mut visitor); + // Functions are added twice for some reason + visitor + .functions + .dedup_by(|e1, e2| e1.name.as_str() == e2.name.as_str()); Ok(visitor.functions) } @@ -372,8 +376,9 @@ fn generate_function_wrapper(func: &FunctionInfo, asset_path: &LitStr) -> TokenS let func_name = func .name_ident - .as_ref() - .expect("Function name should be set by the import spec"); + .clone() + // Can not exist if `::*` + .unwrap_or_else(|| Ident::new(func.name.as_str(), proc_macro2::Span::call_site())); quote! { #doc_comment pub async fn #func_name(#(#param_types),*) -> Result { From 5887dfbacd93d765fc72e1d3dad43ebb5f429fce Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Sun, 22 Jun 2025 10:25:17 +0000 Subject: [PATCH 13/15] Update example --- examples/use_js_macro.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/use_js_macro.rs b/examples/use_js_macro.rs index 5e5beec55d..aee8c26024 100644 --- a/examples/use_js_macro.rs +++ b/examples/use_js_macro.rs @@ -1,10 +1,10 @@ use dioxus::prelude::*; // Generate the greeting function at compile time -// use_js!("examples/assets/example.js"::greeting); +use_js!("examples/assets/example.js"::greeting); // Or generate multiple functions: -use_js!("examples/assets/example.js"::{greeting, add}); +// use_js!("examples/assets/example.js"::{greeting, add}); // Or generate all exported functions: // use_js!("examples/assets/example.js"::*); From 40e21cb221754665c68473890360512ac23821f5 Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Sun, 22 Jun 2025 10:25:25 +0000 Subject: [PATCH 14/15] use_js doc comment --- packages/use-js-macro/src/lib.rs | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/packages/use-js-macro/src/lib.rs b/packages/use-js-macro/src/lib.rs index ff8618795e..ee6c3e5274 100644 --- a/packages/use-js-macro/src/lib.rs +++ b/packages/use-js-macro/src/lib.rs @@ -391,6 +391,58 @@ fn generate_function_wrapper(func: &FunctionInfo, asset_path: &LitStr) -> TokenS } } +/// A macro to create rust binding to javascript functions. +///```rust,ignore +/// use dioxus::prelude::*; +/// +/// // Generate the greeting function at compile time +/// use_js!("examples/assets/example.js"::greeting); +/// +/// // Or generate multiple functions: +/// // use_js!("examples/assets/example.js"::{greeting, add}); +/// +/// // Or generate all exported functions: +/// // use_js!("examples/assets/example.js"::*); +/// +/// fn main() { +/// launch(App); +/// } +/// +/// #[component] +/// fn App() -> Element { +/// let future = use_resource(|| async move { +/// let from = "dave"; +/// let to = "john"; +/// +/// // Now we can call the generated function directly! +/// let greeting_result = greeting(from, to) +/// .await +/// .map_err(Box::::from)?; +/// let greeting: String = +/// serde_json::from_value(greeting_result).map_err(Box::::from)?; +/// Ok::>(greeting) +/// }); +/// +/// rsx!( +/// div { +/// h1 { "Dioxus `use_js!` macro example!" } +/// { +/// match &*future.read() { +/// Some(Ok(greeting)) => rsx! { +/// p { "Greeting from JavaScript: {greeting}" } +/// }, +/// Some(Err(e)) => rsx! { +/// p { "Error: {e}" } +/// }, +/// None => rsx! { +/// p { "Running js..." } +/// }, +/// } +/// } +/// } +/// ) +/// } +/// ``` #[proc_macro] pub fn use_js(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as UseJsInput); From 745381b61218c826694b92f220613de405378891 Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Sun, 22 Jun 2025 10:41:15 +0000 Subject: [PATCH 15/15] clippy --- packages/use-js-macro/src/lib.rs | 48 ++++++++++++++------------------ 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/packages/use-js-macro/src/lib.rs b/packages/use-js-macro/src/lib.rs index ee6c3e5274..353da8f301 100644 --- a/packages/use-js-macro/src/lib.rs +++ b/packages/use-js-macro/src/lib.rs @@ -131,7 +131,7 @@ impl FunctionVisitor { } } -fn function_params_to_names(params: &Vec) -> Vec { +fn function_params_to_names(params: &[Param]) -> Vec { params .iter() .enumerate() @@ -145,7 +145,7 @@ fn function_params_to_names(params: &Vec) -> Vec { .collect() } -fn function_pat_to_names(pats: &Vec) -> Vec { +fn function_pat_to_names(pats: &[Pat]) -> Vec { pats.iter() .enumerate() .map(|(i, pat)| { @@ -207,19 +207,16 @@ impl Visit for FunctionVisitor { /// Visit export declarations: export function foo() {} fn visit_export_decl(&mut self, node: &ExportDecl) { - match &node.decl { - Decl::Fn(fn_decl) => { - let doc_comment = self.extract_doc_comment(node.span()); - - self.functions.push(FunctionInfo { - name: fn_decl.ident.sym.to_string(), - name_ident: None, - params: function_params_to_names(&fn_decl.function.params), - is_exported: true, - doc_comment, - }); - } - _ => {} + if let Decl::Fn(fn_decl) = &node.decl { + let doc_comment = self.extract_doc_comment(node.span()); + + self.functions.push(FunctionInfo { + name: fn_decl.ident.sym.to_string(), + name_ident: None, + params: function_params_to_names(&fn_decl.function.params), + is_exported: true, + doc_comment, + }); } node.visit_children_with(self); } @@ -227,18 +224,15 @@ impl Visit for FunctionVisitor { /// Visit named exports: export { foo } fn visit_named_export(&mut self, node: &NamedExport) { for spec in &node.specifiers { - match spec { - ExportSpecifier::Named(named) => { - let name = match &named.orig { - ModuleExportName::Ident(ident) => ident.sym.to_string(), - ModuleExportName::Str(str_lit) => str_lit.value.to_string(), - }; - - if let Some(func) = self.functions.iter_mut().find(|f| f.name == name) { - func.is_exported = true; - } + if let ExportSpecifier::Named(named) = spec { + let name = match &named.orig { + ModuleExportName::Ident(ident) => ident.sym.to_string(), + ModuleExportName::Str(str_lit) => str_lit.value.to_string(), + }; + + if let Some(func) = self.functions.iter_mut().find(|f| f.name == name) { + func.is_exported = true; } - _ => {} } } node.visit_children_with(self); @@ -246,7 +240,7 @@ impl Visit for FunctionVisitor { } fn parse_js_file(file_path: &Path) -> Result> { - let js_content = fs::read_to_string(&file_path).map_err(|e| { + let js_content = fs::read_to_string(file_path).map_err(|e| { syn::Error::new( proc_macro2::Span::call_site(), format!(