feat: #[island(lazy)]

This commit is contained in:
Greg Johnston
2025-05-21 21:53:19 -04:00
parent 37405ec778
commit 775e2eabed
2 changed files with 55 additions and 20 deletions

View File

@@ -19,6 +19,7 @@ use syn::{
pub struct Model {
is_transparent: bool,
is_lazy: bool,
island: Option<String>,
docs: Docs,
unknown_attrs: UnknownAttrs,
@@ -66,6 +67,7 @@ impl Parse for Model {
Ok(Self {
is_transparent: false,
is_lazy: false,
island: None,
docs,
unknown_attrs,
@@ -140,6 +142,7 @@ impl ToTokens for Model {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
is_transparent,
is_lazy,
island,
docs,
unknown_attrs,
@@ -530,15 +533,38 @@ impl ToTokens for Model {
};
let hydrate_fn_name = hydrate_fn_name.as_ref().unwrap();
quote! {
#[::leptos::wasm_bindgen::prelude::wasm_bindgen(wasm_bindgen = ::leptos::wasm_bindgen)]
#[allow(non_snake_case)]
pub fn #hydrate_fn_name(el: ::leptos::web_sys::HtmlElement) {
#deserialize_island_props
let island = #name(#island_props);
let state = island.hydrate_from_position::<true>(&el, ::leptos::tachys::view::Position::Current);
// TODO better cleanup
std::mem::forget(state);
let hydrate_fn_inner = quote! {
#deserialize_island_props
let island = #name(#island_props);
let state = island.hydrate_from_position::<true>(&el, ::leptos::tachys::view::Position::Current);
// TODO better cleanup
std::mem::forget(state);
};
if *is_lazy {
let outer_name =
Ident::new(&format!("{name}_loader"), name.span());
quote! {
#[::leptos::prelude::lazy]
#[allow(non_snake_case)]
async fn #outer_name (el: ::leptos::web_sys::HtmlElement) {
#hydrate_fn_inner
}
#[::leptos::wasm_bindgen::prelude::wasm_bindgen(wasm_bindgen = ::leptos::wasm_bindgen)]
#[allow(non_snake_case)]
pub fn #hydrate_fn_name(el: ::leptos::web_sys::HtmlElement) {
::leptos::task::spawn_local(#outer_name(el))
}
}
} else {
quote! {
#[::leptos::wasm_bindgen::prelude::wasm_bindgen(wasm_bindgen = ::leptos::wasm_bindgen)]
#[allow(non_snake_case)]
pub fn #hydrate_fn_name(el: ::leptos::web_sys::HtmlElement) {
#hydrate_fn_inner
}
}
}
} else {
@@ -610,6 +636,13 @@ impl Model {
self
}
#[allow(clippy::wrong_self_convention)]
pub fn is_lazy(mut self, is_lazy: bool) -> Self {
self.is_lazy = is_lazy;
self
}
#[allow(clippy::wrong_self_convention)]
pub fn with_island(mut self, island: Option<String>) -> Self {
self.island = island;

View File

@@ -560,7 +560,7 @@ pub fn component(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
false
};
component_macro(s, is_transparent, None)
component_macro(s, is_transparent, false, None)
}
/// Defines a component as an interactive island when you are using the
@@ -637,36 +637,37 @@ pub fn component(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
#[proc_macro_error2::proc_macro_error]
#[proc_macro_attribute]
pub fn island(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
let is_transparent = if !args.is_empty() {
let transparent = parse_macro_input!(args as syn::Ident);
let (is_transparent, is_lazy) = if !args.is_empty() {
let arg = parse_macro_input!(args as syn::Ident);
if transparent != "transparent" {
if arg != "transparent" && arg != "lazy" {
abort!(
transparent,
"only `transparent` is supported";
help = "try `#[island(transparent)]` or `#[island]`"
arg,
"only `transparent` or `lazy` are supported";
help = "try `#[island(transparent)]`, `#[island(lazy)]`, or `#[island]`"
);
}
true
(arg == "transparent", arg == "lazy")
} else {
false
(false, false)
};
let island_src = s.to_string();
component_macro(s, is_transparent, Some(island_src))
component_macro(s, is_transparent, is_lazy, Some(island_src))
}
fn component_macro(
s: TokenStream,
is_transparent: bool,
is_lazy: bool,
island: Option<String>,
) -> TokenStream {
let mut dummy = syn::parse::<DummyModel>(s.clone());
let parse_result = syn::parse::<component::Model>(s);
if let (Ok(ref mut unexpanded), Ok(model)) = (&mut dummy, parse_result) {
let expanded = model.is_transparent(is_transparent).with_island(island).into_token_stream();
let expanded = model.is_transparent(is_transparent).is_lazy(is_lazy).with_island(island).into_token_stream();
if !matches!(unexpanded.vis, Visibility::Public(_)) {
unexpanded.vis = Visibility::Public(Pub {
span: unexpanded.vis.span(),
@@ -674,6 +675,7 @@ fn component_macro(
}
unexpanded.sig.ident =
unmodified_fn_name_from_fn_name(&unexpanded.sig.ident);
quote! {
#expanded