extern crate proc_macro;
use proc_macro::TokenStream;
use syn::parse_macro_input;
mod html_parser;
mod live_parser;
#[proc_macro]
pub fn html(input: TokenStream) -> TokenStream {
let node = parse_macro_input!(input as html_parser::HtmlNode);
let expanded = node.to_tokens();
expanded.into()
}
#[proc_macro_attribute]
#[allow(clippy::collapsible_if)]
pub fn island(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(item as syn::ItemFn);
let vis = &input_fn.vis;
let sig = &input_fn.sig;
let name = &sig.ident;
let body = &input_fn.block;
let mut arg_names = Vec::new();
let mut arg_types = Vec::new();
for arg in &sig.inputs {
if let syn::FnArg::Typed(pat_type) = arg {
if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
arg_names.push(&pat_ident.ident);
arg_types.push(&pat_type.ty);
}
}
}
let props_struct_name =
syn::Ident::new(&format!("{}_Props", name), proc_macro2::Span::call_site());
let hydrate_fn_name =
syn::Ident::new(&format!("hydrate_{}", name), proc_macro2::Span::call_site());
let expanded = quote::quote! {
#[cfg(not(target_arch = "wasm32"))]
#vis fn #name(#(#arg_names: #arg_types),*) -> String {
let inner_html = {
#body
};
let props_json = serde_json::json!({
#(stringify!(#arg_names): #arg_names),*
}).to_string();
let escaped_props = rullst::html::escape_str(&props_json);
format!(
"<div data-island=\"{}\" data-props=\"{}\">{}</div>",
stringify!(#name),
escaped_props,
inner_html
)
}
#[cfg(target_arch = "wasm32")]
#vis fn #name(#(#arg_names: #arg_types),*) -> String {
let Some(element) = web_sys::window()
.and_then(|w| w.document())
.and_then(|d| d.create_element("div").ok())
else {
return String::new();
};
let _ = {
#body
};
String::new()
}
#[cfg(target_arch = "wasm32")]
#[derive(serde::Deserialize)]
#[allow(non_camel_case_types)]
struct #props_struct_name {
#(#arg_names: #arg_types),*
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen::prelude::wasm_bindgen]
#[allow(non_snake_case)]
pub fn #hydrate_fn_name(element: web_sys::Element, props_json: &str) {
let props: #props_struct_name = match serde_json::from_str(props_json) {
Ok(p) => p,
Err(_) => return,
};
#(let #arg_names = props.#arg_names;)*
let element = element;
let _ = {
#body
};
}
};
expanded.into()
}
#[proc_macro_attribute]
pub fn live_component(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = item.into();
live_parser::parse_live_component(input).into()
}
#[proc_macro_attribute]
pub fn live_event(_attr: TokenStream, item: TokenStream) -> TokenStream {
item
}
#[proc_macro_attribute]
pub fn memoize(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(item as syn::ItemFn);
let vis = &input_fn.vis;
let sig = &input_fn.sig;
let name = &sig.ident;
let body = &input_fn.block;
let output_type = match &sig.output {
syn::ReturnType::Default => quote::quote!(()),
syn::ReturnType::Type(_, ty) => quote::quote!(#ty),
};
let mut arg_names = Vec::new();
let mut arg_types = Vec::new();
for arg in &sig.inputs {
if let syn::FnArg::Typed(pat_type) = arg
&& let syn::Pat::Ident(pat_ident) = &*pat_type.pat
{
arg_names.push(&pat_ident.ident);
arg_types.push(&pat_type.ty);
}
}
let expanded = quote::quote! {
#vis fn #name(#(#arg_names: #arg_types),*) -> #output_type {
let cache_key = format!("{}:{}", stringify!(#name), serde_json::json!([#(#arg_names),*]).to_string());
if let Some(cached) = rullst::cache::memory::get(&cache_key) {
if let Ok(cached_str) = serde_json::from_str::<#output_type>(&cached) {
return cached_str;
}
}
let result: #output_type = { #body };
if let Ok(serialized) = serde_json::to_string(&result) {
rullst::cache::memory::set(&cache_key, &serialized);
}
result
}
};
expanded.into()
}
#[proc_macro_attribute]
pub fn route(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(item as syn::ItemFn);
let vis = &input_fn.vis;
let sig = &input_fn.sig;
let name = &sig.ident;
let body = &input_fn.block;
let expanded = quote::quote! {
#vis fn #name() -> String {
let result = { #body };
result
}
};
expanded.into()
}
#[proc_macro_attribute]
pub fn server_function(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(item as syn::ItemFn);
let vis = &input_fn.vis;
let sig = &input_fn.sig;
let name = &sig.ident;
let body = &input_fn.block;
let output_type = match &sig.output {
syn::ReturnType::Default => quote::quote!(()),
syn::ReturnType::Type(_, ty) => quote::quote!(#ty),
};
let name_str = name.to_string();
let expanded = quote::quote! {
#[cfg(not(target_arch = "wasm32"))]
#vis async fn #name() -> #output_type {
#body
}
#[cfg(target_arch = "wasm32")]
#vis async fn #name() -> #output_type {
let res = rullst::client::rpc_call(#name_str).await;
serde_json::from_str(&res).unwrap_or_else(|_| panic!("Failed to parse RPC response from server"))
}
};
expanded.into()
}