mirror of
https://github.com/leptos-rs/leptos
synced 2025-02-03 07:23:26 +00:00
feat: #[lazy]
macros to support lazy loading and code splitting (#3477)
This commit is contained in:
parent
1c3e013a63
commit
22b2d8ec84
5 changed files with 135 additions and 2 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1999,10 +1999,12 @@ dependencies = [
|
|||
name = "leptos_router_macro"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"leptos_macro",
|
||||
"leptos_router",
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
32
leptos_macro/src/lazy.rs
Normal file
32
leptos_macro/src/lazy.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use convert_case::{Case, Casing};
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Ident;
|
||||
use proc_macro_error2::abort;
|
||||
use quote::quote;
|
||||
use syn::{spanned::Spanned, ItemFn};
|
||||
|
||||
pub fn lazy_impl(
|
||||
_args: proc_macro::TokenStream,
|
||||
s: TokenStream,
|
||||
) -> TokenStream {
|
||||
let fun = syn::parse::<ItemFn>(s).unwrap_or_else(|e| {
|
||||
abort!(e.span(), "`lazy` can only be used on a function")
|
||||
});
|
||||
if fun.sig.asyncness.is_none() {
|
||||
abort!(
|
||||
fun.sig.asyncness.span(),
|
||||
"`lazy` can only be used on an async function"
|
||||
)
|
||||
}
|
||||
|
||||
let converted_name = Ident::new(
|
||||
&fun.sig.ident.to_string().to_case(Case::Snake),
|
||||
fun.sig.ident.span(),
|
||||
);
|
||||
|
||||
quote! {
|
||||
#[cfg_attr(feature = "split", wasm_split::wasm_split(#converted_name))]
|
||||
#fun
|
||||
}
|
||||
.into()
|
||||
}
|
|
@ -23,6 +23,7 @@ mod params;
|
|||
mod view;
|
||||
use crate::component::unmodified_fn_name_from_fn_name;
|
||||
mod component;
|
||||
mod lazy;
|
||||
mod memo;
|
||||
mod slice;
|
||||
mod slot;
|
||||
|
@ -1002,3 +1003,17 @@ pub fn slice(input: TokenStream) -> TokenStream {
|
|||
pub fn memo(input: TokenStream) -> TokenStream {
|
||||
memo::memo_impl(input)
|
||||
}
|
||||
|
||||
/// The `#[lazy]` macro marks an `async` function as a function that can be lazy-loaded from a
|
||||
/// separate (WebAssembly) binary.
|
||||
///
|
||||
/// The first time the function is called, calling the function will first load that other binary,
|
||||
/// then call the function. On subsequent call it will be called immediately, but still return
|
||||
/// asynchronously to maintain the same API.
|
||||
///
|
||||
/// All parameters and output types should be concrete types, with no generics.
|
||||
#[proc_macro_attribute]
|
||||
#[proc_macro_error]
|
||||
pub fn lazy(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
lazy::lazy_impl(args, s)
|
||||
}
|
||||
|
|
|
@ -16,9 +16,11 @@ proc-macro = true
|
|||
proc-macro-error2 = { version = "2.0", default-features = false }
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
leptos_router = { path = "../router" }
|
||||
leptos_macro = { path = "../leptos_macro" }
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
//! A macro to make path definitions easier with [`leptos_router`].
|
||||
//!
|
||||
//! [`leptos_router`]: https://docs.rs/leptos_router/latest/leptos_router/components/fn.Route.html
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use proc_macro::{TokenStream, TokenTree};
|
||||
use proc_macro2::Span;
|
||||
use proc_macro_error2::abort;
|
||||
use proc_macro_error2::{abort, proc_macro_error};
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{
|
||||
spanned::Spanned, Block, Ident, ImplItem, ItemImpl, Path, Type, TypePath,
|
||||
};
|
||||
|
||||
const RFC3986_UNRESERVED: [char; 4] = ['-', '.', '_', '~'];
|
||||
const RFC3986_PCHAR_OTHER: [char; 1] = ['@'];
|
||||
|
||||
/// Constructs a path for use in a [`leptos_router::Route`] definition.
|
||||
/// Constructs a path for use in a [`Route`] definition.
|
||||
///
|
||||
/// Note that this is an optional convenience. Manually defining route segments
|
||||
/// is equivalent.
|
||||
|
@ -33,6 +38,7 @@ const RFC3986_PCHAR_OTHER: [char; 1] = ['@'];
|
|||
///
|
||||
/// assert_eq!(path, output);
|
||||
/// ```
|
||||
/// [`Route`]: https://docs.rs/leptos_router/latest/leptos_router/components/fn.Route.html
|
||||
#[proc_macro_error2::proc_macro_error]
|
||||
#[proc_macro]
|
||||
pub fn path(tokens: TokenStream) -> TokenStream {
|
||||
|
@ -187,3 +193,79 @@ impl ToTokens for Segments {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// When added to an [`impl LazyRoute`] implementation block, this will automatically
|
||||
/// add a [`lazy`] annotation to the `view` method, which will cause the code for the view
|
||||
/// to lazy-load concurrently with the `data` being loaded for the route.
|
||||
///
|
||||
/// [`impl LazyRoute`]: https://docs.rs/leptos_router/latest/leptos_router/trait.LazyRoute.html
|
||||
/// [`lazy`]: https://docs.rs/leptos_macro/latest/leptos_macro/macro.lazy.html
|
||||
#[proc_macro_attribute]
|
||||
#[proc_macro_error]
|
||||
pub fn lazy_route(
|
||||
args: proc_macro::TokenStream,
|
||||
s: TokenStream,
|
||||
) -> TokenStream {
|
||||
lazy_route_impl(args, s)
|
||||
}
|
||||
|
||||
fn lazy_route_impl(
|
||||
_args: proc_macro::TokenStream,
|
||||
s: TokenStream,
|
||||
) -> TokenStream {
|
||||
let mut im = syn::parse::<ItemImpl>(s).unwrap_or_else(|e| {
|
||||
abort!(e.span(), "`lazy_route` can only be used on an `impl` block")
|
||||
});
|
||||
if im.trait_.is_none() {
|
||||
abort!(
|
||||
im.span(),
|
||||
"`lazy_route` can only be used on an `impl LazyRoute for ...` \
|
||||
block"
|
||||
)
|
||||
}
|
||||
|
||||
let self_ty = im.self_ty.clone();
|
||||
let ty_name_to_snake = match &*self_ty {
|
||||
Type::Path(TypePath {
|
||||
path: Path { segments, .. },
|
||||
..
|
||||
}) => segments.last().unwrap().ident.to_string(),
|
||||
_ => abort!(self_ty.span(), "only path types are supported"),
|
||||
};
|
||||
let lazy_view_ident = Ident::new(&ty_name_to_snake, im.self_ty.span());
|
||||
|
||||
let item = im.items.iter_mut().find_map(|item| match item {
|
||||
ImplItem::Fn(inner) => {
|
||||
if inner.sig.ident.to_string().as_str() == "view" {
|
||||
Some(inner)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
match item {
|
||||
None => abort!(im.span(), "must contain a fn called `view`"),
|
||||
Some(fun) => {
|
||||
let body = fun.block.clone();
|
||||
let new_block = quote! {{
|
||||
#[cfg_attr(feature = "split", wasm_split::wasm_split(#lazy_view_ident))]
|
||||
async fn view(this: #self_ty) -> ::leptos::prelude::AnyView {
|
||||
#body
|
||||
}
|
||||
|
||||
view(self).await
|
||||
}};
|
||||
let block =
|
||||
syn::parse::<Block>(new_block.into()).unwrap_or_else(|e| {
|
||||
abort!(
|
||||
e.span(),
|
||||
"`lazy_route` can only be used on an `impl` block"
|
||||
)
|
||||
});
|
||||
fun.block = block;
|
||||
}
|
||||
}
|
||||
|
||||
quote! { #im }.into()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue