mirror of
https://github.com/leptos-rs/leptos
synced 2024-09-20 06:21:57 +00:00
feat: custom_view
macro to allow implementing view types on arbitrary data
This commit is contained in:
parent
1f4c410f78
commit
2cc6ccaacb
2 changed files with 200 additions and 0 deletions
166
leptos_macro/src/custom_view.rs
Normal file
166
leptos_macro/src/custom_view.rs
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::{quote, ToTokens};
|
||||||
|
use syn::{parse::Parse, parse_macro_input, ImplItem, ItemImpl};
|
||||||
|
|
||||||
|
pub fn custom_view_impl(tokens: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(tokens as CustomViewMacroInput);
|
||||||
|
input.into_token_stream().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct CustomViewMacroInput {
|
||||||
|
impl_block: ItemImpl,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for CustomViewMacroInput {
|
||||||
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||||
|
let impl_block = input.parse()?;
|
||||||
|
Ok(CustomViewMacroInput { impl_block })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for CustomViewMacroInput {
|
||||||
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||||
|
let ItemImpl {
|
||||||
|
impl_token,
|
||||||
|
generics,
|
||||||
|
self_ty,
|
||||||
|
items,
|
||||||
|
..
|
||||||
|
} = &self.impl_block;
|
||||||
|
let impl_span = &impl_token;
|
||||||
|
let view_ty = items
|
||||||
|
.iter()
|
||||||
|
.find_map(|item| match item {
|
||||||
|
ImplItem::Type(ty) => (ty.ident == "View").then_some(&ty.ty),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
proc_macro_error::abort!(
|
||||||
|
impl_span,
|
||||||
|
"You must include `type View = ...;` to specify the type. \
|
||||||
|
In most cases, this will be `type View = AnyView<Rndr>;"
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let view_fn = items
|
||||||
|
.iter()
|
||||||
|
.find_map(|item| match item {
|
||||||
|
ImplItem::Fn(f) => {
|
||||||
|
(f.sig.ident == "into_view").then_some(&f.block)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
proc_macro_error::abort!(
|
||||||
|
impl_span,
|
||||||
|
"You must include `fn into_view(self) -> Self::View` to \
|
||||||
|
specify the view function."
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let generic_params = &generics.params;
|
||||||
|
let where_preds =
|
||||||
|
&generics.where_clause.as_ref().map(|wc| &wc.predicates);
|
||||||
|
|
||||||
|
tokens.extend(quote! {
|
||||||
|
#impl_token<#generic_params, Rndr> ::leptos::tachys::view::Render<Rndr> for #self_ty
|
||||||
|
where Rndr: ::leptos::tachys::renderer::Renderer, #where_preds {
|
||||||
|
type State = <#view_ty as ::leptos::tachys::view::Render<Rndr>>::State;
|
||||||
|
|
||||||
|
fn build(self) -> Self::State {
|
||||||
|
let view = #view_fn;
|
||||||
|
view.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rebuild(self, state: &mut Self::State) {
|
||||||
|
let view = #view_fn;
|
||||||
|
view.rebuild(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#impl_token<#generic_params, Rndr> ::leptos::tachys::view::add_attr::AddAnyAttr<Rndr> for #self_ty
|
||||||
|
where Rndr: ::leptos::tachys::renderer::Renderer, #where_preds {
|
||||||
|
type Output<SomeNewAttr: ::leptos::tachys::html::attribute::Attribute<Rndr>> =
|
||||||
|
<#view_ty as ::leptos::tachys::view::add_attr::AddAnyAttr<Rndr>>::Output<SomeNewAttr>;
|
||||||
|
|
||||||
|
fn add_any_attr<NewAttr: ::leptos::tachys::html::attribute::Attribute<Rndr>>(
|
||||||
|
self,
|
||||||
|
attr: NewAttr,
|
||||||
|
) -> Self::Output<NewAttr>
|
||||||
|
where
|
||||||
|
Self::Output<NewAttr>: ::leptos::tachys::view::RenderHtml<Rndr>,
|
||||||
|
{
|
||||||
|
let view = #view_fn;
|
||||||
|
view.add_any_attr(attr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#impl_token<#generic_params, Rndr> ::leptos::tachys::view::RenderHtml<Rndr> for #self_ty
|
||||||
|
where Rndr: ::leptos::tachys::renderer::Renderer, #where_preds {
|
||||||
|
type AsyncOutput = <#view_ty as ::leptos::tachys::view::RenderHtml<Rndr>>::AsyncOutput;
|
||||||
|
const MIN_LENGTH: usize = <#view_ty as ::leptos::tachys::view::RenderHtml<Rndr>>::MIN_LENGTH;
|
||||||
|
|
||||||
|
async fn resolve(self) -> Self::AsyncOutput {
|
||||||
|
let view = #view_fn;
|
||||||
|
::leptos::tachys::view::RenderHtml::<Rndr>::resolve(view).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dry_resolve(&mut self) {
|
||||||
|
// TODO... The problem is that view_fn expects to take self
|
||||||
|
// dry_resolve is the only one that takes &mut self
|
||||||
|
// this can only have an effect if walking over the view would read from
|
||||||
|
// resources that are not otherwise read synchronously, which is an interesting
|
||||||
|
// edge case to handle but probably (?) irrelevant for most actual use cases of
|
||||||
|
// this macro
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_html_with_buf(
|
||||||
|
self,
|
||||||
|
buf: &mut String,
|
||||||
|
position: &mut ::leptos::tachys::view::Position,
|
||||||
|
escape: bool,
|
||||||
|
mark_branches: bool,
|
||||||
|
) {
|
||||||
|
let view = #view_fn;
|
||||||
|
::leptos::tachys::view::RenderHtml::<Rndr>::to_html_with_buf(
|
||||||
|
view,
|
||||||
|
buf,
|
||||||
|
position,
|
||||||
|
escape,
|
||||||
|
mark_branches
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||||
|
self,
|
||||||
|
buf: &mut ::leptos::tachys::ssr::StreamBuilder,
|
||||||
|
position: &mut ::leptos::tachys::view::Position,
|
||||||
|
escape: bool,
|
||||||
|
mark_branches: bool,
|
||||||
|
) where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
let view = #view_fn;
|
||||||
|
::leptos::tachys::view::RenderHtml::<Rndr>::to_html_async_with_buf::<OUT_OF_ORDER>(
|
||||||
|
view,
|
||||||
|
buf,
|
||||||
|
position,
|
||||||
|
escape,
|
||||||
|
mark_branches
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hydrate<const FROM_SERVER: bool>(
|
||||||
|
self,
|
||||||
|
cursor: &::leptos::tachys::hydration::Cursor<Rndr>,
|
||||||
|
position: &::leptos::tachys::view::PositionState,
|
||||||
|
) -> Self::State {
|
||||||
|
let view = #view_fn;
|
||||||
|
::leptos::tachys::view::RenderHtml::<Rndr>::hydrate::<FROM_SERVER>(
|
||||||
|
view, cursor, position
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ mod params;
|
||||||
mod view;
|
mod view;
|
||||||
use crate::component::unmodified_fn_name_from_fn_name;
|
use crate::component::unmodified_fn_name_from_fn_name;
|
||||||
mod component;
|
mod component;
|
||||||
|
mod custom_view;
|
||||||
mod slice;
|
mod slice;
|
||||||
mod slot;
|
mod slot;
|
||||||
|
|
||||||
|
@ -857,3 +858,36 @@ pub fn params_derive(
|
||||||
pub fn slice(input: TokenStream) -> TokenStream {
|
pub fn slice(input: TokenStream) -> TokenStream {
|
||||||
slice::slice_impl(input)
|
slice::slice_impl(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implements the traits needed to make something [`IntoView`] on some type.
|
||||||
|
///
|
||||||
|
/// The renderer relies on the implementation of several traits, implementing which for a custom
|
||||||
|
/// struct involves significant boilerplate. This macro is intended to make it easier to implement
|
||||||
|
/// a view type for custom data, by allowing you to provide some custom view logic for the type.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use leptos::custom_view;
|
||||||
|
///
|
||||||
|
/// struct Foo<T>(T);
|
||||||
|
///
|
||||||
|
/// #[custom_view]
|
||||||
|
/// impl<T> CustomView for Foo<T>
|
||||||
|
/// where
|
||||||
|
/// T: ToString + Send + 'static,
|
||||||
|
/// {
|
||||||
|
/// // this will usually be `AnyView<Rndr>`, but for simple types
|
||||||
|
/// // you may be able to specify the output type easily
|
||||||
|
/// type View = String;
|
||||||
|
///
|
||||||
|
/// fn into_view(self) -> Self::View {
|
||||||
|
/// self.0.to_string().to_ascii_uppercase()
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
#[proc_macro_error::proc_macro_error]
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn custom_view(
|
||||||
|
_args: proc_macro::TokenStream,
|
||||||
|
s: TokenStream,
|
||||||
|
) -> TokenStream {
|
||||||
|
custom_view::custom_view_impl(s)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue