From 0f0f21d05f58f8f48a136dc479b9042e74c71a83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BC=8A=E6=AC=A7?= Date: Tue, 19 Dec 2023 02:50:11 +0800 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Rewrite=20the=20macros=20s?= =?UTF-8?q?tructure.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/boot/Cargo.toml | 3 + packages/boot/tests/register_routes.rs | 90 ++++++++++++-------- packages/macro-types/src/register.rs | 29 +++---- packages/macro/src/lib.rs | 10 ++- packages/macro/src/utils/app.rs | 58 +++++++++++++ packages/macro/src/utils/app_props.rs | 111 +++++++++++++++++++++++-- packages/macro/src/utils/app_states.rs | 2 +- packages/macro/src/utils/mod.rs | 1 + packages/macro/src/utils/routes.rs | 81 ++---------------- 9 files changed, 250 insertions(+), 135 deletions(-) create mode 100644 packages/macro/src/utils/app.rs diff --git a/packages/boot/Cargo.toml b/packages/boot/Cargo.toml index eba5d89..682a789 100644 --- a/packages/boot/Cargo.toml +++ b/packages/boot/Cargo.toml @@ -48,3 +48,6 @@ features = [ "CssStyleDeclaration", ] version = "^0.3" + +[dev-dependencies] +wasm-bindgen-test = "^0.3" diff --git a/packages/boot/tests/register_routes.rs b/packages/boot/tests/register_routes.rs index dcaabfb..d97a129 100644 --- a/packages/boot/tests/register_routes.rs +++ b/packages/boot/tests/register_routes.rs @@ -7,7 +7,9 @@ mod test { use yew::prelude::*; use yew_router::prelude::*; - use hikari_boot::{DeriveAppProps, DeriveAppStates, DeriveApplication, DeriveRoutes}; + use hikari_boot::{ + DeriveAppProps, DeriveAppStates, DeriveApplication, DeriveApplicationType, DeriveRoutes, + }; #[function_component] fn Portal() -> yew::Html { @@ -16,49 +18,71 @@ mod test { } } - // #[derive(yew::Properties, PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] - // pub struct ThreadProps { - // pub id: String, - // } - - // #[function_component] - // fn Thread(props: &ThreadProps) -> yew::Html { - // html! { - //
{format!("Thread {}", props.id)}
- // } - // } - - #[derive(Clone, Debug)] - pub struct Router; + #[derive(Properties, Clone, PartialEq, Debug, Serialize, Deserialize)] + pub struct ThreadProps { + pub id: String, + } - impl DeriveApplication for Router { - type Routes = Routes; - type AppProps = PageProps; - type AppStates = Theme; + #[function_component] + fn Thread(props: &ThreadProps) -> yew::Html { + html! { +
{format!("Thread {}", props.id)}
+ } } - #[derive(PartialEq, Eq, Clone, Debug, DeriveRoutes, Routable)] + #[derive(PartialEq, Clone, Debug, DeriveRoutes, Routable)] pub enum Routes { #[at("/")] - #[component(Portal)] Portal, - // - // #[at("/t/:id")] - // #[component(Thread)] - // Thread { id: String }, + + #[at("/t/:id")] + Thread { id: String }, } - #[derive(PartialEq, Eq, Clone, Debug, DeriveAppStates, Serialize, Deserialize)] - pub struct Theme { + #[derive(PartialEq, Clone, Debug, DeriveAppStates, Serialize, Deserialize)] + pub struct AppStates { pub color: String, } - #[derive(PartialEq, Eq, Clone, Debug, DeriveAppProps, Serialize, Deserialize)] - pub enum PageProps { + #[derive(PartialEq, Clone, Debug, DeriveAppProps, Serialize, Deserialize)] + pub enum AppProps { + #[component(Portal)] Portal, - // - // Thread { - // id: String, - // }, + + #[component(Thread)] + Thread(ThreadProps), + } + + #[derive(Clone, Debug, DeriveApplication)] + pub struct App; + + impl DeriveApplicationType for App { + type Routes = Routes; + type AppProps = AppProps; + type AppStates = AppStates; + } + + #[test] + fn render_on_server() { + let html = App.ServerApp("/", AppProps::Portal); + + assert_eq!( + html, + yew::html! { +
{"Portal"}
+ } + ); + } + + #[wasm_bindgen_test::wasm_bindgen_test] + fn render_on_client() { + let html = App.App(); + + assert_eq!( + html, + yew::html! { +
{"Portal"}
+ } + ); } } diff --git a/packages/macro-types/src/register.rs b/packages/macro-types/src/register.rs index 4f81390..66cd78c 100644 --- a/packages/macro-types/src/register.rs +++ b/packages/macro-types/src/register.rs @@ -1,21 +1,15 @@ #![allow(non_snake_case)] -pub trait DeriveRoutesTrait: yew_router::Routable { - fn switch(&self) -> yew::Html; -} +pub trait Routes: yew_router::Routable {} -pub trait DeriveAppPropsTrait { - type AppProps; -} +pub trait AppProps {} -pub trait DeriveAppStatesTrait { - type AppStates; -} +pub trait AppStates {} #[derive(Debug, PartialEq, Clone)] pub struct AppContext where - T: DeriveAppPropsTrait, + T: AppProps, { pub style_manager: stylist::manager::StyleManager, pub uri: String, @@ -23,16 +17,19 @@ where pub page_data: T, } -pub trait Application: DeriveApplication { +pub trait Application: DeriveApplicationType { + fn switch(&self) -> yew::Html; + fn App(&self) -> yew::Html; - fn ServerApp(&self, props: &AppContext<::AppProps>) -> yew::Html; + fn ServerApp(&self, props: &AppContext<::AppProps>) + -> yew::Html; } -pub trait DeriveApplication +pub trait DeriveApplicationType where - Self::Routes: DeriveRoutesTrait, - Self::AppProps: DeriveAppPropsTrait, - Self::AppStates: DeriveAppStatesTrait, + Self::Routes: Routes + yew_router::Routable, + Self::AppProps: AppProps, + Self::AppStates: AppStates, { type Routes; type AppProps; diff --git a/packages/macro/src/lib.rs b/packages/macro/src/lib.rs index 8b852ac..e2775c2 100644 --- a/packages/macro/src/lib.rs +++ b/packages/macro/src/lib.rs @@ -3,13 +3,19 @@ use syn::parse_macro_input; mod utils; -#[proc_macro_derive(DeriveRoutes, attributes(component))] +#[proc_macro_derive(DeriveApplication)] +pub fn app(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as utils::app::DeriveApp); + utils::app::root(input).into() +} + +#[proc_macro_derive(DeriveRoutes)] pub fn routes(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as utils::routes::DeriveRoutes); utils::routes::root(input).into() } -#[proc_macro_derive(DeriveAppProps)] +#[proc_macro_derive(DeriveAppProps, attributes(component))] pub fn app_props(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as utils::app_props::DeriveAppProps); utils::app_props::root(input).into() diff --git a/packages/macro/src/utils/app.rs b/packages/macro/src/utils/app.rs new file mode 100644 index 0000000..052ded6 --- /dev/null +++ b/packages/macro/src/utils/app.rs @@ -0,0 +1,58 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::parse::{Parse, ParseStream}; +use syn::spanned::Spanned; +use syn::{Data, DeriveInput, Ident}; + +pub struct DeriveApp { + ident: Ident, +} + +impl Parse for DeriveApp { + fn parse(input: ParseStream) -> syn::Result { + let DeriveInput { ident, data, .. } = input.parse()?; + + match data { + Data::Enum(e) => { + return Err(syn::Error::new( + e.enum_token.span(), + "expected struct, found enum", + )) + } + Data::Struct(data) => data, + Data::Union(u) => { + return Err(syn::Error::new( + u.union_token.span(), + "expected enum, found union", + )) + } + }; + + Ok(Self { ident }) + } +} + +pub fn root(input: DeriveApp) -> TokenStream { + let DeriveApp { ident, .. } = &input; + + quote! { + #[automatically_derived] + impl ::hikari_boot::Application for #ident { + #[allow(bindings_with_variant_name)] + fn switch() -> ::yew::Html { + todo!("implement switch") + } + + fn App() -> yew::Html { + todo!("implement App") + } + + fn ServerApp( + url: &String, + props: &::hikari_boot::AppContext<::AppProps> + ) -> yew::Html { + todo!("implement ServerApp") + } + } + } +} diff --git a/packages/macro/src/utils/app_props.rs b/packages/macro/src/utils/app_props.rs index 59d05a8..95ee8e5 100644 --- a/packages/macro/src/utils/app_props.rs +++ b/packages/macro/src/utils/app_props.rs @@ -1,27 +1,124 @@ +use std::collections::HashMap; + use proc_macro2::TokenStream; use quote::quote; -use syn::parse::{Parse, ParseStream}; -use syn::{DeriveInput, Ident}; +use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + spanned::Spanned, + Data, DeriveInput, Fields, Ident, Path, Type, Variant, +}; + +const COMPONENT_ATTR_IDENT: &str = "component"; + +#[derive(Debug, Clone, PartialEq)] +pub struct Component { + component: Path, + props: Option, +} + +pub type Components = HashMap; pub struct DeriveAppProps { ident: Ident, + components: Components, } impl Parse for DeriveAppProps { fn parse(input: ParseStream) -> syn::Result { - let DeriveInput { ident, .. } = input.parse()?; + let DeriveInput { ident, data, .. } = input.parse()?; + + let data = match data { + Data::Enum(data) => data, + Data::Struct(s) => { + return Err(syn::Error::new( + s.struct_token.span(), + "expected enum, found struct", + )) + } + Data::Union(u) => { + return Err(syn::Error::new( + u.union_token.span(), + "expected enum, found union", + )) + } + }; + + let components = parse_variants_attributes(&data.variants)?; + + Ok(Self { ident, components }) + } +} + +fn parse_variants_attributes( + variants: &Punctuated, +) -> syn::Result { + let mut components: Components = Default::default(); + + for variant in variants.iter() { + let attrs = &variant.attrs; + let at_attrs = attrs + .iter() + .filter(|attr| attr.path().is_ident(COMPONENT_ATTR_IDENT)) + .collect::>(); - Ok(Self { ident }) + let attr = match at_attrs.len() { + 1 => *at_attrs.first().unwrap(), + 0 => { + return Err(syn::Error::new( + variant.span(), + format!("{COMPONENT_ATTR_IDENT} attribute must be present on every variant"), + )) + } + _ => { + return Err(syn::Error::new_spanned( + quote! { #(#at_attrs)* }, + format!("only one {COMPONENT_ATTR_IDENT} attribute must be present"), + )) + } + }; + + let component_path = attr.parse_args::()?; + + let field = match &variant.fields { + Fields::Named(ref field) => { + return Err(syn::Error::new( + field.span(), + "only unnamed fields are supported", + )); + } + Fields::Unnamed(ref fields) => { + if fields.unnamed.len() > 1 { + return Err(syn::Error::new( + fields.span(), + "only one field is supported", + )); + } + Some(fields.unnamed.first().unwrap().ty.clone()) + } + Fields::Unit => None, + }; + + components.insert( + variant.ident.clone(), + Component { + component: component_path, + props: field, + }, + ); } + + Ok(components) } pub fn root(input: DeriveAppProps) -> TokenStream { - let DeriveAppProps { ident, .. } = &input; + let DeriveAppProps { + components, ident, .. + } = &input; quote! { #[automatically_derived] - impl ::hikari_boot::DeriveAppPropsTrait for #ident { - type AppProps = Self; + impl ::hikari_boot::AppProps for #ident { } } } diff --git a/packages/macro/src/utils/app_states.rs b/packages/macro/src/utils/app_states.rs index 397cbcd..c2fa5ca 100644 --- a/packages/macro/src/utils/app_states.rs +++ b/packages/macro/src/utils/app_states.rs @@ -20,7 +20,7 @@ pub fn root(input: DeriveAppStates) -> TokenStream { quote! { #[automatically_derived] - impl ::hikari_boot::DeriveAppStatesTrait for #ident { + impl ::hikari_boot::AppStates for #ident { type AppStates = Self; } } diff --git a/packages/macro/src/utils/mod.rs b/packages/macro/src/utils/mod.rs index 62f83ee..dd6d252 100644 --- a/packages/macro/src/utils/mod.rs +++ b/packages/macro/src/utils/mod.rs @@ -1,3 +1,4 @@ +pub mod app; pub mod app_props; pub mod app_states; pub mod routes; diff --git a/packages/macro/src/utils/routes.rs b/packages/macro/src/utils/routes.rs index 52fcbe1..ffd77d2 100644 --- a/packages/macro/src/utils/routes.rs +++ b/packages/macro/src/utils/routes.rs @@ -1,97 +1,26 @@ use proc_macro2::TokenStream; use quote::quote; use syn::parse::{Parse, ParseStream}; -use syn::punctuated::Punctuated; -use syn::spanned::Spanned; -use syn::{Data, DeriveInput, Fields, Ident, Path, Variant}; - -const COMPONENT_ATTR_IDENT: &str = "component"; +use syn::{DeriveInput, Ident}; pub struct DeriveRoutes { ident: Ident, - ats: Vec, } impl Parse for DeriveRoutes { fn parse(input: ParseStream) -> syn::Result { - let DeriveInput { ident, data, .. } = input.parse()?; - - let data = match data { - Data::Enum(data) => data, - Data::Struct(s) => { - return Err(syn::Error::new( - s.struct_token.span(), - "expected enum, found struct", - )) - } - Data::Union(u) => { - return Err(syn::Error::new( - u.union_token.span(), - "expected enum, found union", - )) - } - }; - - let ats = parse_variants_attributes(&data.variants)?; - - Ok(Self { ident, ats }) - } -} + let DeriveInput { ident, .. } = input.parse()?; -fn parse_variants_attributes( - variants: &Punctuated, -) -> syn::Result> { - let mut ats: Vec = vec![]; - - for variant in variants.iter() { - if let Fields::Unnamed(ref field) = variant.fields { - return Err(syn::Error::new( - field.span(), - "only named fields are supported", - )); - } - - let attrs = &variant.attrs; - let at_attrs = attrs - .iter() - .filter(|attr| attr.path().is_ident(COMPONENT_ATTR_IDENT)) - .collect::>(); - - let attr = match at_attrs.len() { - 1 => *at_attrs.first().unwrap(), - 0 => { - return Err(syn::Error::new( - variant.span(), - format!("{COMPONENT_ATTR_IDENT} attribute must be present on every variant"), - )) - } - _ => { - return Err(syn::Error::new_spanned( - quote! { #(#at_attrs)* }, - format!("only one {COMPONENT_ATTR_IDENT} attribute must be present"), - )) - } - }; - - let lit = attr.parse_args::()?; - ats.push(lit); + Ok(Self { ident }) } - - Ok(ats) } pub fn root(input: DeriveRoutes) -> TokenStream { - let DeriveRoutes { ats, ident, .. } = &input; + let DeriveRoutes { ident, .. } = &input; quote! { #[automatically_derived] - impl ::hikari_boot::DeriveRoutesTrait for #ident { - #[allow(bindings_with_variant_name)] - fn switch(&self) -> ::yew::Html { - match self { - #(#ats => ::yew::html! { <#ats /> }),*, - } - } + impl ::hikari_boot::Routes for #ident { } } }