From b9c8f4da1ea38a2cbe890767f46ed6f9341eca6f Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Sat, 21 Jun 2025 11:22:10 +0000 Subject: [PATCH 1/7] Add call_js macro --- Cargo.toml | 1 + examples/call-js/Cargo.toml | 9 +++ examples/call-js/README.md | 7 +++ examples/call-js/assets/example.js | 5 ++ examples/call-js/src/main.rs | 33 +++++++++++ packages/call-js/Cargo.toml | 16 ++++++ packages/call-js/README.md | 47 +++++++++++++++ packages/call-js/src/lib.rs | 92 ++++++++++++++++++++++++++++++ 8 files changed, 210 insertions(+) create mode 100644 examples/call-js/Cargo.toml create mode 100644 examples/call-js/README.md create mode 100644 examples/call-js/assets/example.js create mode 100644 examples/call-js/src/main.rs create mode 100644 packages/call-js/Cargo.toml create mode 100644 packages/call-js/README.md create mode 100644 packages/call-js/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 9e3aa52..36bdfc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ dioxus-notification = { path = "packages/notification", version = "0.1.0-alpha.1 dioxus-sync = { path = "packages/sync", version = "0.1.0-alpha.1" } dioxus-util = { path = "packages/util", version = "0.1.0-alpha.1" } dioxus-window = { path = "packages/window", version = "0.1.0-alpha.1" } +dioxus-call-js = { path = "packages/call-js", version = "0.1.0-alpha.1" } # Dioxus dioxus = "0.6.0" diff --git a/examples/call-js/Cargo.toml b/examples/call-js/Cargo.toml new file mode 100644 index 0000000..d10a36b --- /dev/null +++ b/examples/call-js/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "call-js-example" +version = "0.1.0" +edition = "2021" + +[dependencies] +dioxus = { workspace = true } +dioxus-call-js = { workspace = true } +serde_json = "1.0" diff --git a/examples/call-js/README.md b/examples/call-js/README.md new file mode 100644 index 0000000..39aa792 --- /dev/null +++ b/examples/call-js/README.md @@ -0,0 +1,7 @@ +# call-js + +Learn how to use `call_js!` macro from `dioxus-call-js`. + +Run: + +```dx serve``` \ No newline at end of file diff --git a/examples/call-js/assets/example.js b/examples/call-js/assets/example.js new file mode 100644 index 0000000..f16d102 --- /dev/null +++ b/examples/call-js/assets/example.js @@ -0,0 +1,5 @@ + + +export function greeting(from, to) { + return `Hello ${to} from ${from}`; +} \ No newline at end of file diff --git a/examples/call-js/src/main.rs b/examples/call-js/src/main.rs new file mode 100644 index 0000000..e7de57c --- /dev/null +++ b/examples/call-js/src/main.rs @@ -0,0 +1,33 @@ +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)).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" } + }, + } + } + } + ) +} diff --git a/packages/call-js/Cargo.toml b/packages/call-js/Cargo.toml new file mode 100644 index 0000000..42266eb --- /dev/null +++ b/packages/call-js/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "dioxus-call-js" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2", features = ["full"] } +proc-macro2 = "1" +quote = "1" diff --git a/packages/call-js/README.md b/packages/call-js/README.md new file mode 100644 index 0000000..c8acc00 --- /dev/null +++ b/packages/call-js/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)).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/call-js/src/lib.rs b/packages/call-js/src/lib.rs new file mode 100644 index 0000000..bada5be --- /dev/null +++ b/packages/call-js/src/lib.rs @@ -0,0 +1,92 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +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", + )), + } +} + +#[proc_macro] +pub fn call_js(input: TokenStream) -> TokenStream { + 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()), + }; + + let args: Vec<&Expr> = function_call.args.iter().collect(); + let arg_count = args.len(); + + let send_calls: Vec = 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 + }.await + }; + + TokenStream::from(expanded) +} From fd7eb334ab8a94cf043413abcef558aff9c11506 Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Sat, 21 Jun 2025 11:33:37 +0000 Subject: [PATCH 2/7] Move await out of call_js --- examples/call-js/src/main.rs | 2 +- packages/call-js/README.md | 2 +- packages/call-js/src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/call-js/src/main.rs b/examples/call-js/src/main.rs index e7de57c..03ae0e1 100644 --- a/examples/call-js/src/main.rs +++ b/examples/call-js/src/main.rs @@ -10,7 +10,7 @@ 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)).unwrap(); + let greeting = call_js!("assets/example.js", greeting(from, to)).await.unwrap(); let greeting: String = serde_json::from_value(greeting).unwrap(); return greeting; }); diff --git a/packages/call-js/README.md b/packages/call-js/README.md index c8acc00..35a3a47 100644 --- a/packages/call-js/README.md +++ b/packages/call-js/README.md @@ -23,7 +23,7 @@ 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)).unwrap(); + let greeting = call_js!("assets/example.js", greeting(from, to)).await.unwrap(); let greeting: String = serde_json::from_value(greeting).unwrap(); return greeting; }); diff --git a/packages/call-js/src/lib.rs b/packages/call-js/src/lib.rs index bada5be..87b142c 100644 --- a/packages/call-js/src/lib.rs +++ b/packages/call-js/src/lib.rs @@ -85,7 +85,7 @@ pub fn call_js(input: TokenStream) -> TokenStream { let eval = document::eval(js.as_str()); #(#send_calls)* eval.await - }.await + } }; TokenStream::from(expanded) From 159fdbff8324d45e9b551d197e75c9e4c19dcfd6 Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Sat, 21 Jun 2025 11:57:02 +0000 Subject: [PATCH 3/7] Add js function validation for call_js macro --- packages/call-js/Cargo.toml | 10 +- packages/call-js/src/lib.rs | 215 +++++++++++++++++++++++++++++++++++- 2 files changed, 219 insertions(+), 6 deletions(-) diff --git a/packages/call-js/Cargo.toml b/packages/call-js/Cargo.toml index 42266eb..ec4b2fb 100644 --- a/packages/call-js/Cargo.toml +++ b/packages/call-js/Cargo.toml @@ -11,6 +11,10 @@ repository.workspace = true proc-macro = true [dependencies] -syn = { version = "2", features = ["full"] } -proc-macro2 = "1" -quote = "1" +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0", features = ["full"] } +swc_ecma_parser = "0.141" +swc_ecma_ast = "0.110" +swc_ecma_visit = "0.96" +swc_common = "0.33" diff --git a/packages/call-js/src/lib.rs b/packages/call-js/src/lib.rs index 87b142c..1c9eb57 100644 --- a/packages/call-js/src/lib.rs +++ b/packages/call-js/src/lib.rs @@ -1,6 +1,12 @@ use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::quote; +use swc_ecma_ast::{Decl, ExportDecl, ExportSpecifier, FnDecl, ModuleExportName, NamedExport, VarDeclarator}; +use std::sync::Arc; +use std::{fs, path::Path}; +use swc_common::SourceMap; +use swc_ecma_parser::{EsConfig, Parser, StringInput, Syntax, lexer::Lexer}; +use swc_ecma_visit::{Visit, VisitWith}; use syn::{ Expr, ExprCall, LitStr, Result, Token, parse::{Parse, ParseStream}, @@ -45,8 +51,189 @@ fn extract_function_name(call: &ExprCall) -> Result { } } +#[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(), + }; + + // Mark existing function as exported + 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()), + js_content.clone(), + ); + + let lexer = Lexer::new( + Syntax::Es(EsConfig { + jsx: true, + ..Default::default() + }), + Default::default(), + StringInput::from(&*fm), + None, + ); + + let mut parser = Parser::new_from(lexer); + + // Parse the module + 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 + ), + ) + })?; + + // Visit the AST to extract function information + 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; @@ -57,10 +244,32 @@ pub fn call_js(input: TokenStream) -> TokenStream { Err(e) => return TokenStream::from(e.to_compile_error()), }; - let args: Vec<&Expr> = function_call.args.iter().collect(); - let arg_count = args.len(); + // 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()); + } - let send_calls: Vec = args + // expand + let send_calls: Vec = function_call + .args .iter() .map(|arg| quote! { eval.send(#arg)?; }) .collect(); From 92053ddc6804eb73bb218606dd7b37d4d81b1dd2 Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Sat, 21 Jun 2025 12:00:30 +0000 Subject: [PATCH 4/7] refactor: Format --- examples/call-js/src/main.rs | 4 +++- packages/call-js/src/lib.rs | 19 +++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/examples/call-js/src/main.rs b/examples/call-js/src/main.rs index 03ae0e1..c995a18 100644 --- a/examples/call-js/src/main.rs +++ b/examples/call-js/src/main.rs @@ -10,7 +10,9 @@ 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 = call_js!("assets/example.js", greeting(from, to)) + .await + .unwrap(); let greeting: String = serde_json::from_value(greeting).unwrap(); return greeting; }); diff --git a/packages/call-js/src/lib.rs b/packages/call-js/src/lib.rs index 1c9eb57..8efde76 100644 --- a/packages/call-js/src/lib.rs +++ b/packages/call-js/src/lib.rs @@ -1,10 +1,12 @@ use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use swc_ecma_ast::{Decl, ExportDecl, ExportSpecifier, FnDecl, ModuleExportName, NamedExport, VarDeclarator}; 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::{EsConfig, Parser, StringInput, Syntax, lexer::Lexer}; use swc_ecma_visit::{Visit, VisitWith}; use syn::{ @@ -246,15 +248,16 @@ pub fn call_js(input: TokenStream) -> TokenStream { // validate js call let arg_count = function_call.args.len(); - let manifest_dir = match std::env::var("CARGO_MANIFEST_DIR") - { + 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()); + return TokenStream::from( + syn::Error::new( + proc_macro2::Span::call_site(), + "CARGO_MANIFEST_DIR environment variable not found", + ) + .to_compile_error(), + ); } }; From eb7c45305dc39c7af020faf3ac314772339ac2a3 Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Sat, 21 Jun 2025 12:05:21 +0000 Subject: [PATCH 5/7] Comments --- packages/call-js/src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/call-js/src/lib.rs b/packages/call-js/src/lib.rs index 8efde76..38f7716 100644 --- a/packages/call-js/src/lib.rs +++ b/packages/call-js/src/lib.rs @@ -134,7 +134,6 @@ impl Visit for FunctionVisitor { ModuleExportName::Str(str_lit) => str_lit.value.to_string(), }; - // Mark existing function as exported if let Some(func) = self.functions.iter_mut().find(|f| f.name == name) { func.is_exported = true; } @@ -176,7 +175,6 @@ fn parse_js_file(file_path: &Path) -> Result> { let mut parser = Parser::new_from(lexer); - // Parse the module let module = parser.parse_module().map_err(|e| { syn::Error::new( proc_macro2::Span::call_site(), @@ -188,7 +186,6 @@ fn parse_js_file(file_path: &Path) -> Result> { ) })?; - // Visit the AST to extract function information let mut visitor = FunctionVisitor::new(); module.visit_with(&mut visitor); From 873f784402b45ab825d1e10ef8df4a3ace524b30 Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Sat, 21 Jun 2025 12:12:19 +0000 Subject: [PATCH 6/7] chore: Upgrade dependencies --- packages/call-js/Cargo.toml | 8 ++++---- packages/call-js/src/lib.rs | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/call-js/Cargo.toml b/packages/call-js/Cargo.toml index ec4b2fb..939388f 100644 --- a/packages/call-js/Cargo.toml +++ b/packages/call-js/Cargo.toml @@ -14,7 +14,7 @@ proc-macro = true proc-macro2 = "1.0" quote = "1.0" syn = { version = "2.0", features = ["full"] } -swc_ecma_parser = "0.141" -swc_ecma_ast = "0.110" -swc_ecma_visit = "0.96" -swc_common = "0.33" +swc_ecma_parser = "17" +swc_ecma_ast = "13" +swc_ecma_visit = "13" +swc_common = "13" diff --git a/packages/call-js/src/lib.rs b/packages/call-js/src/lib.rs index 38f7716..46bc543 100644 --- a/packages/call-js/src/lib.rs +++ b/packages/call-js/src/lib.rs @@ -1,13 +1,14 @@ use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::quote; +use swc_ecma_parser::EsSyntax; 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::{EsConfig, Parser, StringInput, Syntax, lexer::Lexer}; +use swc_ecma_parser::{Parser, StringInput, Syntax, lexer::Lexer}; use swc_ecma_visit::{Visit, VisitWith}; use syn::{ Expr, ExprCall, LitStr, Result, Token, @@ -159,12 +160,12 @@ fn parse_js_file(file_path: &Path) -> Result> { let cm = Arc::new(SourceMap::default()); let fm = cm.new_source_file( - swc_common::FileName::Custom(file_path.display().to_string()), + swc_common::FileName::Custom(file_path.display().to_string()).into(), js_content.clone(), ); let lexer = Lexer::new( - Syntax::Es(EsConfig { + Syntax::Es(EsSyntax { jsx: true, ..Default::default() }), From 470b6c99463b293633be6367151b9903dd29b44e Mon Sep 17 00:00:00 2001 From: mcmah309 Date: Sat, 21 Jun 2025 12:27:06 +0000 Subject: [PATCH 7/7] refactor: Format --- packages/call-js/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/call-js/src/lib.rs b/packages/call-js/src/lib.rs index 46bc543..a84c69c 100644 --- a/packages/call-js/src/lib.rs +++ b/packages/call-js/src/lib.rs @@ -1,13 +1,13 @@ use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use swc_ecma_parser::EsSyntax; 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::{