From b5af7b103f61f92f7a9512bfdccd9f864795fb4a Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 6 Mar 2022 14:31:51 +0900 Subject: [PATCH 01/30] Bring changes to this branch. --- packages/yew-macro/src/function_component.rs | 200 ++++++++++++------ packages/yew-macro/src/hook/body.rs | 39 +++- packages/yew-macro/src/hook/mod.rs | 98 ++++----- packages/yew-macro/src/hook/signature.rs | 22 +- .../yew-macro/src/html_tree/html_component.rs | 2 +- .../function_component_attr/generic-pass.rs | 19 +- .../generic-props-fail.stderr | 63 +++--- .../tests/hook_attr/hook_macro-fail.rs | 30 +++ .../tests/hook_attr/hook_macro-fail.stderr | 33 +++ .../tests/hook_attr/hook_macro-pass.rs | 27 +++ .../component-unimplemented-fail.stderr | 4 +- packages/yew/src/dom_bundle/app_handle.rs | 22 +- packages/yew/src/dom_bundle/bcomp.rs | 2 +- packages/yew/src/functional/mod.rs | 18 +- packages/yew/src/html/component/mod.rs | 34 ++- packages/yew/src/html/component/scope.rs | 39 ++-- packages/yew/src/html/mod.rs | 1 + packages/yew/src/lib.rs | 37 ++-- packages/yew/src/server_renderer.rs | 30 +-- packages/yew/src/virtual_dom/vcomp.rs | 36 ++-- packages/yew/src/virtual_dom/vnode.rs | 8 +- .../base_component_impl-fail.stderr | 6 +- .../sealed_base_component_impl-fail.rs | 2 +- .../sealed_base_component_impl-fail.stderr | 2 +- tools/process-benchmark-results/src/main.rs | 2 +- 25 files changed, 501 insertions(+), 275 deletions(-) create mode 100644 packages/yew-macro/tests/hook_attr/hook_macro-fail.rs create mode 100644 packages/yew-macro/tests/hook_attr/hook_macro-fail.stderr create mode 100644 packages/yew-macro/tests/hook_attr/hook_macro-pass.rs diff --git a/packages/yew-macro/src/function_component.rs b/packages/yew-macro/src/function_component.rs index d2b6c21d340..a2de56d13ec 100644 --- a/packages/yew-macro/src/function_component.rs +++ b/packages/yew-macro/src/function_component.rs @@ -1,10 +1,11 @@ use proc_macro2::{Span, TokenStream}; -use quote::{format_ident, quote, ToTokens}; +use quote::{quote, ToTokens}; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::token::{Comma, Fn}; use syn::{ - visit_mut, Attribute, Block, FnArg, Generics, Ident, Item, ItemFn, ReturnType, Type, Visibility, + parse_quote_spanned, visit_mut, Attribute, Block, FnArg, Generics, Ident, Item, ItemFn, + ReturnType, Type, Visibility, }; use crate::hook::BodyRewriter; @@ -20,6 +21,8 @@ pub struct FunctionComponent { name: Ident, return_type: Box, fn_token: Fn, + + component_name: Option, } impl Parse for FunctionComponent { @@ -144,10 +147,96 @@ impl Parse for FunctionComponent { name: sig.ident, return_type, fn_token: sig.fn_token, + component_name: None, }) } } +impl FunctionComponent { + /// Filters attributes that should be copied to component definition. + fn filter_attrs_for_component_struct(&self) -> Vec { + self.attrs + .iter() + .filter_map(|m| { + m.path + .get_ident() + .and_then(|ident| match ident.to_string().as_str() { + "doc" | "allow" => Some(m.clone()), + _ => None, + }) + }) + .collect() + } + + /// Filters attributes that should be copied to the component impl block. + fn filter_attrs_for_component_impl(&self) -> Vec { + self.attrs + .iter() + .filter_map(|m| { + m.path + .get_ident() + .and_then(|ident| match ident.to_string().as_str() { + "allow" => Some(m.clone()), + _ => None, + }) + }) + .collect() + } + + fn phantom_generics(&self) -> Punctuated { + self.generics + .type_params() + .map(|ty_param| ty_param.ident.clone()) // create a new Punctuated sequence without any type bounds + .collect::>() + } + + fn merge_component_name(&mut self, name: FunctionComponentName) -> syn::Result<()> { + if let Some(ref m) = name.component_name { + if m == &self.name { + return Err(syn::Error::new_spanned( + m, + "the component must not have the same name as the function", + )); + } + } + + self.component_name = name.component_name; + + Ok(()) + } + + fn inner_fn_ident(&self) -> Ident { + if self.component_name.is_some() { + self.name.clone() + } else { + Ident::new("inner", Span::mixed_site()) + } + } + + fn component_name(&self) -> Ident { + self.component_name + .clone() + .unwrap_or_else(|| self.name.clone()) + } + + // We need to cast 'static on all generics for into component. + fn create_into_component_generics(&self) -> Generics { + let mut generics = self.generics.clone(); + + let where_clause = generics.make_where_clause(); + for ty_generic in self.generics.type_params() { + let ident = &ty_generic.ident; + let bound = parse_quote_spanned! { ident.span() => + #ident: 'static + }; + + where_clause.predicates.push(bound); + } + + generics + } +} + pub struct FunctionComponentName { component_name: Option, } @@ -168,34 +257,30 @@ impl Parse for FunctionComponentName { } } -fn print_fn(func_comp: FunctionComponent, use_fn_name: bool) -> TokenStream { +fn print_fn(func_comp: &FunctionComponent) -> TokenStream { + let name = func_comp.inner_fn_ident(); let FunctionComponent { - fn_token, - name, - attrs, - mut block, - return_type, - generics, - arg, + ref fn_token, + ref attrs, + ref block, + ref return_type, + ref generics, + ref arg, .. } = func_comp; + let mut block = *block.clone(); + let (impl_generics, _ty_generics, where_clause) = generics.split_for_impl(); - let (_impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - - let name = if use_fn_name { - name - } else { - Ident::new("inner", Span::mixed_site()) - }; - - let ctx_ident = Ident::new("ctx", Span::mixed_site()); + // We use _ctx here so if the component does not use any hooks, the usused_vars lint will not + // be triggered. + let ctx_ident = Ident::new("_ctx", Span::mixed_site()); - let mut body_rewriter = BodyRewriter::default(); - visit_mut::visit_block_mut(&mut body_rewriter, &mut *block); + let mut body_rewriter = BodyRewriter::new(ctx_ident.clone()); + visit_mut::visit_block_mut(&mut body_rewriter, &mut block); quote! { #(#attrs)* - #fn_token #name #ty_generics (#ctx_ident: &mut ::yew::functional::HookContext, #arg) -> #return_type + #fn_token #name #impl_generics (#ctx_ident: &mut ::yew::functional::HookContext, #arg) -> #return_type #where_clause { #block @@ -205,74 +290,63 @@ fn print_fn(func_comp: FunctionComponent, use_fn_name: bool) -> TokenStream { pub fn function_component_impl( name: FunctionComponentName, - component: FunctionComponent, + mut component: FunctionComponent, ) -> syn::Result { - let FunctionComponentName { component_name } = name; + component.merge_component_name(name)?; - let has_separate_name = component_name.is_some(); + let func = print_fn(&component); - let func = print_fn(component.clone(), has_separate_name); + let into_comp_generics = component.create_into_component_generics(); + let component_attrs = component.filter_attrs_for_component_struct(); + let component_impl_attrs = component.filter_attrs_for_component_impl(); + let phantom_generics = component.phantom_generics(); + let component_name = component.component_name(); + let fn_name = component.inner_fn_ident(); let FunctionComponent { props_type, generics, vis, - name: function_name, .. } = component; - let component_name = component_name.unwrap_or_else(|| function_name.clone()); - let provider_name = format_ident!( - "{}FunctionProvider", - component_name, - span = Span::mixed_site() - ); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + let fn_generics = ty_generics.as_turbofish(); - if has_separate_name && function_name == component_name { - return Err(syn::Error::new_spanned( - component_name, - "the component must not have the same name as the function", - )); - } - - let phantom_generics = generics - .type_params() - .map(|ty_param| ty_param.ident.clone()) // create a new Punctuated sequence without any type bounds - .collect::>(); - - let provider_props = Ident::new("props", Span::mixed_site()); + let component_props = Ident::new("props", Span::mixed_site()); + let ctx_ident = Ident::new("ctx", Span::mixed_site()); - let fn_generics = ty_generics.as_turbofish(); + let into_comp_impl = { + let (impl_generics, ty_generics, where_clause) = into_comp_generics.split_for_impl(); - let fn_name = if has_separate_name { - function_name - } else { - Ident::new("inner", Span::mixed_site()) + quote! { + impl #impl_generics ::yew::html::IntoComponent for #component_name #ty_generics #where_clause { + type Properties = #props_type; + type Component = ::yew::functional::FunctionComponent; + } + } }; - let ctx_ident = Ident::new("ctx", Span::mixed_site()); - let quoted = quote! { - #[doc(hidden)] - #[allow(non_camel_case_types)] + #(#component_attrs)* #[allow(unused_parens)] - #vis struct #provider_name #ty_generics { + #vis struct #component_name #generics #where_clause { _marker: ::std::marker::PhantomData<(#phantom_generics)>, } - #[automatically_derived] - impl #impl_generics ::yew::functional::FunctionProvider for #provider_name #ty_generics #where_clause { - type TProps = #props_type; + // we cannot disable any lints here because it will be applied to the function body + // as well. + #(#component_impl_attrs)* + impl #impl_generics ::yew::functional::FunctionProvider for #component_name #ty_generics #where_clause { + type Properties = #props_type; - fn run(#ctx_ident: &mut ::yew::functional::HookContext, #provider_props: &Self::TProps) -> ::yew::html::HtmlResult { + fn run(#ctx_ident: &mut ::yew::functional::HookContext, #component_props: &Self::Properties) -> ::yew::html::HtmlResult { #func - ::yew::html::IntoHtmlResult::into_html_result(#fn_name #fn_generics (#ctx_ident, #provider_props)) + ::yew::html::IntoHtmlResult::into_html_result(#fn_name #fn_generics (#ctx_ident, #component_props)) } } - #[allow(type_alias_bounds)] - #vis type #component_name #generics = ::yew::functional::FunctionComponent<#provider_name #ty_generics>; + #into_comp_impl }; Ok(quoted) diff --git a/packages/yew-macro/src/hook/body.rs b/packages/yew-macro/src/hook/body.rs index ce24934ed20..13ea1dbdd5d 100644 --- a/packages/yew-macro/src/hook/body.rs +++ b/packages/yew-macro/src/hook/body.rs @@ -1,4 +1,3 @@ -use proc_macro2::Span; use proc_macro_error::emit_error; use std::sync::{Arc, Mutex}; use syn::spanned::Spanned; @@ -8,12 +7,20 @@ use syn::{ ExprMatch, ExprWhile, Ident, Item, }; -#[derive(Debug, Default)] +#[derive(Debug)] pub struct BodyRewriter { branch_lock: Arc>, + ctx_ident: Ident, } impl BodyRewriter { + pub fn new(ctx_ident: Ident) -> Self { + Self { + branch_lock: Arc::default(), + ctx_ident, + } + } + fn is_branched(&self) -> bool { self.branch_lock.try_lock().is_err() } @@ -30,7 +37,7 @@ impl BodyRewriter { impl VisitMut for BodyRewriter { fn visit_expr_call_mut(&mut self, i: &mut ExprCall) { - let ctx_ident = Ident::new("ctx", Span::mixed_site()); + let ctx_ident = &self.ctx_ident; // Only rewrite hook calls. if let Expr::Path(ref m) = &*i.func { @@ -55,6 +62,32 @@ impl VisitMut for BodyRewriter { visit_mut::visit_expr_call_mut(self, i); } + fn visit_expr_mut(&mut self, i: &mut Expr) { + let ctx_ident = &self.ctx_ident; + + match &mut *i { + Expr::Macro(m) => { + if let Some(ident) = m.mac.path.segments.last().as_ref().map(|m| &m.ident) { + if ident.to_string().starts_with("use_") { + if self.is_branched() { + emit_error!( + ident, + "hooks cannot be called at this position."; + help = "move hooks to the top-level of your function."; + note = "see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks" + ); + } else { + *i = parse_quote_spanned! { i.span() => ::yew::functional::Hook::run(#i, #ctx_ident) }; + } + } else { + visit_mut::visit_expr_macro_mut(self, m); + } + } + } + _ => visit_mut::visit_expr_mut(self, i), + } + } + fn visit_expr_closure_mut(&mut self, i: &mut ExprClosure) { self.with_branch(move |m| visit_mut::visit_expr_closure_mut(m, i)) } diff --git a/packages/yew-macro/src/hook/mod.rs b/packages/yew-macro/src/hook/mod.rs index 2e8a90223d5..79b094b663c 100644 --- a/packages/yew-macro/src/hook/mod.rs +++ b/packages/yew-macro/src/hook/mod.rs @@ -1,9 +1,10 @@ use proc_macro2::{Span, TokenStream}; use proc_macro_error::emit_error; -use quote::{quote, ToTokens}; +use quote::quote; use syn::parse::{Parse, ParseStream}; -use syn::visit_mut; -use syn::{parse_file, GenericParam, Ident, ItemFn, LitStr, ReturnType, Signature}; +use syn::{ + parse_file, parse_quote, visit_mut, Attribute, Ident, ItemFn, LitStr, ReturnType, Signature, +}; mod body; mod lifetime; @@ -47,27 +48,22 @@ impl Parse for HookFn { } } -pub fn hook_impl(component: HookFn) -> syn::Result { - let HookFn { inner: original_fn } = component; +impl HookFn { + fn doc_attr(&self) -> Attribute { + let vis = &self.inner.vis; + let sig = &self.inner.sig; - let ItemFn { - vis, - sig, - mut block, - attrs, - } = original_fn.clone(); - - let sig_s = quote! { #vis #sig { - __yew_macro_dummy_function_body__ - } } - .to_string(); - - let sig_file = parse_file(&sig_s).unwrap(); - let sig_formatted = prettyplease::unparse(&sig_file); - - let doc_text = LitStr::new( - &format!( - r#" + let sig_s = quote! { #vis #sig { + __yew_macro_dummy_function_body__ + } } + .to_string(); + + let sig_file = parse_file(&sig_s).unwrap(); + let sig_formatted = prettyplease::unparse(&sig_file); + + let literal = LitStr::new( + &format!( + r#" # Note When used in function components and hooks, this hook is equivalent to: @@ -76,15 +72,32 @@ When used in function components and hooks, this hook is equivalent to: {} ``` "#, - sig_formatted.replace( - "__yew_macro_dummy_function_body__", - "/* implementation omitted */" - ) - ), - Span::mixed_site(), - ); + sig_formatted.replace( + "__yew_macro_dummy_function_body__", + "/* implementation omitted */" + ) + ), + Span::mixed_site(), + ); + + parse_quote!(#[doc = #literal]) + } +} - let hook_sig = HookSignature::rewrite(&sig); +pub fn hook_impl(hook: HookFn) -> syn::Result { + let doc_attr = hook.doc_attr(); + + let HookFn { inner: original_fn } = hook; + + let ItemFn { + ref vis, + ref sig, + ref block, + ref attrs, + } = original_fn; + let mut block = *block.clone(); + + let hook_sig = HookSignature::rewrite(sig); let Signature { ref fn_token, @@ -98,24 +111,13 @@ When used in function components and hooks, this hook is equivalent to: let output_type = &hook_sig.output_type; let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - let call_generics = { - let mut generics = generics.clone(); - - // We need to filter out lifetimes. - generics.params = generics - .params - .into_iter() - .filter(|m| !matches!(m, GenericParam::Lifetime(_))) - .collect(); - - let (_impl_generics, ty_generics, _where_clause) = generics.split_for_impl(); - ty_generics.as_turbofish().to_token_stream() - }; + let call_generics = hook_sig.call_generics(); - let ctx_ident = Ident::new("ctx", Span::mixed_site()); + // We use _ctx so that if a hook does not use other hooks, it will not trigger unused_vars. + let ctx_ident = Ident::new("_ctx", Span::mixed_site()); - let mut body_rewriter = BodyRewriter::default(); - visit_mut::visit_block_mut(&mut body_rewriter, &mut *block); + let mut body_rewriter = BodyRewriter::new(ctx_ident.clone()); + visit_mut::visit_block_mut(&mut body_rewriter, &mut block); let inner_fn_ident = Ident::new("inner_fn", Span::mixed_site()); let input_args = hook_sig.input_args(); @@ -188,7 +190,7 @@ When used in function components and hooks, this hook is equivalent to: let output = quote! { #[cfg(not(doctest))] #(#attrs)* - #[doc = #doc_text] + #doc_attr #vis #fn_token #ident #generics (#inputs) #hook_return_type #where_clause { #inner_fn diff --git a/packages/yew-macro/src/hook/signature.rs b/packages/yew-macro/src/hook/signature.rs index a2031585e2f..62f3e8ee2cb 100644 --- a/packages/yew-macro/src/hook/signature.rs +++ b/packages/yew-macro/src/hook/signature.rs @@ -1,11 +1,11 @@ -use proc_macro2::Span; +use proc_macro2::{Span, TokenStream}; use proc_macro_error::emit_error; -use quote::quote; +use quote::{quote, ToTokens}; use syn::spanned::Spanned; use syn::visit_mut::VisitMut; use syn::{ - parse_quote, parse_quote_spanned, token, visit_mut, FnArg, Ident, Lifetime, Pat, Receiver, - ReturnType, Signature, Type, TypeImplTrait, TypeReference, WhereClause, + parse_quote, parse_quote_spanned, token, visit_mut, FnArg, GenericParam, Ident, Lifetime, Pat, + Receiver, ReturnType, Signature, Type, TypeImplTrait, TypeReference, WhereClause, }; use super::lifetime; @@ -180,4 +180,18 @@ impl HookSignature { }) .collect() } + + pub fn call_generics(&self) -> TokenStream { + let mut generics = self.sig.generics.clone(); + + // We need to filter out lifetimes. + generics.params = generics + .params + .into_iter() + .filter(|m| !matches!(m, GenericParam::Lifetime(_))) + .collect(); + + let (_impl_generics, ty_generics, _where_clause) = generics.split_for_impl(); + ty_generics.as_turbofish().to_token_stream() + } } diff --git a/packages/yew-macro/src/html_tree/html_component.rs b/packages/yew-macro/src/html_tree/html_component.rs index 53a97236fdf..20758495f73 100644 --- a/packages/yew-macro/src/html_tree/html_component.rs +++ b/packages/yew-macro/src/html_tree/html_component.rs @@ -92,7 +92,7 @@ impl ToTokens for HtmlComponent { children, } = self; - let props_ty = quote_spanned!(ty.span()=> <#ty as ::yew::html::BaseComponent>::Properties); + let props_ty = quote_spanned!(ty.span()=> <#ty as ::yew::html::IntoComponent>::Properties); let children_renderer = if children.is_empty() { None } else { diff --git a/packages/yew-macro/tests/function_component_attr/generic-pass.rs b/packages/yew-macro/tests/function_component_attr/generic-pass.rs index 3672a4475cb..29df3007d46 100644 --- a/packages/yew-macro/tests/function_component_attr/generic-pass.rs +++ b/packages/yew-macro/tests/function_component_attr/generic-pass.rs @@ -58,21 +58,20 @@ fn comp1(_props: &()) -> ::yew::Html { } } -// no longer possible? -// #[::yew::function_component(ConstGenerics)] -// fn const_generics() -> ::yew::Html { -// ::yew::html! { -//
-// { N } -//
-// } -// } +#[::yew::function_component(ConstGenerics)] +fn const_generics() -> ::yew::Html { + ::yew::html! { +
+ { N } +
+ } +} fn compile_pass() { ::yew::html! { a=10 /> }; ::yew::html! { /> }; - // ::yew::html! { /> }; + ::yew::html! { /> }; } fn main() {} diff --git a/packages/yew-macro/tests/function_component_attr/generic-props-fail.stderr b/packages/yew-macro/tests/function_component_attr/generic-props-fail.stderr index aea648dd1f2..46be45e6d7b 100644 --- a/packages/yew-macro/tests/function_component_attr/generic-props-fail.stderr +++ b/packages/yew-macro/tests/function_component_attr/generic-props-fail.stderr @@ -19,54 +19,49 @@ error[E0599]: no method named `build` found for struct `PropsBuilder` -error[E0277]: the trait bound `FunctionComponent>: BaseComponent` is not satisfied +error[E0277]: the trait bound `Comp: IntoComponent` is not satisfied --> tests/function_component_attr/generic-props-fail.rs:27:14 | 27 | html! { /> }; - | ^^^^ the trait `BaseComponent` is not implemented for `FunctionComponent>` + | ^^^^ the trait `IntoComponent` is not implemented for `Comp` | = help: the following implementations were found: - as BaseComponent> + as IntoComponent> -error[E0599]: the function or associated item `new` exists for struct `VChild>>`, but its trait bounds were not satisfied - --> tests/function_component_attr/generic-props-fail.rs:27:14 - | -27 | html! { /> }; - | ^^^^ function or associated item cannot be called on `VChild>>` due to unsatisfied trait bounds - | - ::: $WORKSPACE/packages/yew/src/functional/mod.rs - | - | pub struct FunctionComponent { - | ----------------------------------------------------------- doesn't satisfy `_: BaseComponent` - | - = note: the following trait bounds were not satisfied: - `FunctionComponent>: BaseComponent` +error[E0599]: the function or associated item `new` exists for struct `VChild>`, but its trait bounds were not satisfied + --> tests/function_component_attr/generic-props-fail.rs:27:14 + | +8 | #[function_component(Comp)] + | --------------------------- doesn't satisfy `Comp: IntoComponent` +... +27 | html! { /> }; + | ^^^^ function or associated item cannot be called on `VChild>` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `Comp: IntoComponent` error[E0277]: the trait bound `MissingTypeBounds: yew::Properties` is not satisfied - --> tests/function_component_attr/generic-props-fail.rs:27:14 - | -27 | html! { /> }; - | ^^^^ the trait `yew::Properties` is not implemented for `MissingTypeBounds` - | -note: required because of the requirements on the impl of `FunctionProvider` for `CompFunctionProvider` - --> tests/function_component_attr/generic-props-fail.rs:8:1 - | -8 | #[function_component(Comp)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -note: required by a bound in `FunctionComponent` - --> $WORKSPACE/packages/yew/src/functional/mod.rs - | - | pub struct FunctionComponent { - | ^^^^^^^^^^^^^^^^ required by this bound in `FunctionComponent` - = note: this error originates in the attribute macro `function_component` (in Nightly builds, run with -Z macro-backtrace for more info) + --> tests/function_component_attr/generic-props-fail.rs:27:14 + | +27 | html! { /> }; + | ^^^^ the trait `yew::Properties` is not implemented for `MissingTypeBounds` + | +note: required by a bound in `Comp` + --> tests/function_component_attr/generic-props-fail.rs:11:8 + | +8 | #[function_component(Comp)] + | ---- required by a bound in this +... +11 | P: Properties + PartialEq, + | ^^^^^^^^^^ required by this bound in `Comp` -error[E0107]: missing generics for type alias `Comp` +error[E0107]: missing generics for struct `Comp` --> tests/function_component_attr/generic-props-fail.rs:30:14 | 30 | html! { }; | ^^^^ expected 1 generic argument | -note: type alias defined here, with 1 generic parameter: `P` +note: struct defined here, with 1 generic parameter: `P` --> tests/function_component_attr/generic-props-fail.rs:8:22 | 8 | #[function_component(Comp)] diff --git a/packages/yew-macro/tests/hook_attr/hook_macro-fail.rs b/packages/yew-macro/tests/hook_attr/hook_macro-fail.rs new file mode 100644 index 00000000000..7180602f539 --- /dev/null +++ b/packages/yew-macro/tests/hook_attr/hook_macro-fail.rs @@ -0,0 +1,30 @@ +use yew::prelude::*; + +#[hook] +pub fn use_some_macro_inner(val: &str) -> String { + use_state(|| val.to_owned()).to_string() +} + +macro_rules! use_some_macro { + () => { + use_some_macro_inner("default str") + }; + ($t: tt) => { + use_some_macro_inner($t) + }; +} + +#[function_component] +fn Comp() -> Html { + let content = if true { + use_some_macro!() + } else { + use_some_macro!("b") + }; + + html! { +
{content}
+ } +} + +fn main() {} diff --git a/packages/yew-macro/tests/hook_attr/hook_macro-fail.stderr b/packages/yew-macro/tests/hook_attr/hook_macro-fail.stderr new file mode 100644 index 00000000000..ca7561ea43c --- /dev/null +++ b/packages/yew-macro/tests/hook_attr/hook_macro-fail.stderr @@ -0,0 +1,33 @@ +error: hooks cannot be called at this position. + + = help: move hooks to the top-level of your function. + = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks + + --> tests/hook_attr/hook_macro-fail.rs:20:9 + | +20 | use_some_macro!() + | ^^^^^^^^^^^^^^ + +error: hooks cannot be called at this position. + + = help: move hooks to the top-level of your function. + = note: see: https://yew.rs/docs/next/concepts/function-components/introduction#hooks + + --> tests/hook_attr/hook_macro-fail.rs:22:9 + | +22 | use_some_macro!("b") + | ^^^^^^^^^^^^^^ + +warning: unused macro definition + --> tests/hook_attr/hook_macro-fail.rs:8:1 + | +8 | / macro_rules! use_some_macro { +9 | | () => { +10 | | use_some_macro_inner("default str") +11 | | }; +... | +14 | | }; +15 | | } + | |_^ + | + = note: `#[warn(unused_macros)]` on by default diff --git a/packages/yew-macro/tests/hook_attr/hook_macro-pass.rs b/packages/yew-macro/tests/hook_attr/hook_macro-pass.rs new file mode 100644 index 00000000000..9a053d353bb --- /dev/null +++ b/packages/yew-macro/tests/hook_attr/hook_macro-pass.rs @@ -0,0 +1,27 @@ +use yew::prelude::*; + +#[hook] +pub fn use_some_macro_inner(val: &str) -> String { + use_state(|| val.to_owned()).to_string() +} + +macro_rules! use_some_macro { + () => { + use_some_macro_inner("default str") + }; + ($t: tt) => { + use_some_macro_inner($t) + }; +} + +#[function_component] +fn Comp() -> Html { + let a = use_some_macro!(); + let b = use_some_macro!("b"); + + html! { +
{a}{b}
+ } +} + +fn main() {} diff --git a/packages/yew-macro/tests/html_macro/component-unimplemented-fail.stderr b/packages/yew-macro/tests/html_macro/component-unimplemented-fail.stderr index 4b062ceeda5..ed4828b53b3 100644 --- a/packages/yew-macro/tests/html_macro/component-unimplemented-fail.stderr +++ b/packages/yew-macro/tests/html_macro/component-unimplemented-fail.stderr @@ -10,10 +10,10 @@ error[E0599]: the function or associated item `new` exists for struct `VChild tests/html_macro/component-unimplemented-fail.rs:6:14 | 3 | struct Unimplemented; - | --------------------- doesn't satisfy `Unimplemented: BaseComponent` + | --------------------- doesn't satisfy `Unimplemented: IntoComponent` ... 6 | html! { }; | ^^^^^^^^^^^^^ function or associated item cannot be called on `VChild` due to unsatisfied trait bounds | = note: the following trait bounds were not satisfied: - `Unimplemented: BaseComponent` + `Unimplemented: IntoComponent` diff --git a/packages/yew/src/dom_bundle/app_handle.rs b/packages/yew/src/dom_bundle/app_handle.rs index 771eb1b9d8e..7cd49e94ee1 100644 --- a/packages/yew/src/dom_bundle/app_handle.rs +++ b/packages/yew/src/dom_bundle/app_handle.rs @@ -1,27 +1,27 @@ //! [AppHandle] contains the state Yew keeps to bootstrap a component in an isolated scope. use super::{ComponentRenderState, Scoped}; -use crate::html::{BaseComponent, Scope}; -use crate::NodeRef; -use std::{ops::Deref, rc::Rc}; +use crate::html::{IntoComponent, NodeRef, Scope}; +use std::ops::Deref; +use std::rc::Rc; use web_sys::Element; /// An instance of an application. #[derive(Debug)] -pub struct AppHandle { +pub struct AppHandle { /// `Scope` holder - scope: Scope, + pub(crate) scope: Scope<::Component>, } -impl AppHandle +impl AppHandle where - COMP: BaseComponent, + ICOMP: IntoComponent, { /// The main entry point of a Yew program which also allows passing properties. It works /// similarly to the `program` function in Elm. You should provide an initial model, `update` /// function which will update the state of the model and a `view` function which /// will render the model to a virtual DOM tree. - pub(crate) fn mount_with_props(element: Element, props: Rc) -> Self { + pub(crate) fn mount_with_props(element: Element, props: Rc) -> Self { clear_element(&element); let app = Self { scope: Scope::new(None), @@ -41,11 +41,11 @@ where } } -impl Deref for AppHandle +impl Deref for AppHandle where - COMP: BaseComponent, + ICOMP: IntoComponent, { - type Target = Scope; + type Target = Scope<::Component>; fn deref(&self) -> &Self::Target { &self.scope diff --git a/packages/yew/src/dom_bundle/bcomp.rs b/packages/yew/src/dom_bundle/bcomp.rs index fe2d1ccabf0..c68db83af9a 100644 --- a/packages/yew/src/dom_bundle/bcomp.rs +++ b/packages/yew/src/dom_bundle/bcomp.rs @@ -175,7 +175,7 @@ impl Mountable for PropsWrapper { } fn reuse(self: Box, node_ref: NodeRef, scope: &dyn Scoped, next_sibling: NodeRef) { - let scope: Scope = scope.to_any().downcast(); + let scope: Scope = scope.to_any().downcast::(); scope.reuse(self.props, node_ref, next_sibling); } diff --git a/packages/yew/src/functional/mod.rs b/packages/yew/src/functional/mod.rs index 447e74e32ff..35226a536d0 100644 --- a/packages/yew/src/functional/mod.rs +++ b/packages/yew/src/functional/mod.rs @@ -31,7 +31,7 @@ pub use hooks::*; use crate::html::Context; -use crate::html::SealedBaseComponent; +use crate::html::sealed::SealedBaseComponent; /// This attribute creates a function component from a normal Rust function. /// @@ -131,21 +131,27 @@ impl fmt::Debug for HookContext { /// Trait that allows a struct to act as Function Component. pub trait FunctionProvider { /// Properties for the Function Component. - type TProps: Properties + PartialEq; + type Properties: Properties + PartialEq; /// Render the component. This function returns the [`Html`](crate::Html) to be rendered for the component. /// /// Equivalent of [`Component::view`](crate::html::Component::view). - fn run(ctx: &mut HookContext, props: &Self::TProps) -> HtmlResult; + fn run(ctx: &mut HookContext, props: &Self::Properties) -> HtmlResult; } /// Wrapper that allows a struct implementing [`FunctionProvider`] to be consumed as a component. -pub struct FunctionComponent { +pub struct FunctionComponent +where + T: FunctionProvider + 'static, +{ _never: std::marker::PhantomData, hook_ctx: RefCell, } -impl fmt::Debug for FunctionComponent { +impl fmt::Debug for FunctionComponent +where + T: FunctionProvider + 'static, +{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("FunctionComponent<_>") } @@ -156,7 +162,7 @@ where T: FunctionProvider + 'static, { type Message = (); - type Properties = T::TProps; + type Properties = T::Properties; fn create(ctx: &Context) -> Self { let scope = AnyScope::from(ctx.link().clone()); diff --git a/packages/yew/src/html/component/mod.rs b/packages/yew/src/html/component/mod.rs index 74443781962..b6efe5cc8b9 100644 --- a/packages/yew/src/html/component/mod.rs +++ b/packages/yew/src/html/component/mod.rs @@ -70,9 +70,11 @@ impl Context { } } -/// A Sealed trait that prevents direct implementation of -/// [BaseComponent]. -pub trait SealedBaseComponent {} +pub(crate) mod sealed { + /// A Sealed trait that prevents direct implementation of + /// [BaseComponent]. + pub trait SealedBaseComponent {} +} /// The common base of both function components and struct components. /// @@ -80,11 +82,11 @@ pub trait SealedBaseComponent {} /// [`#[function_component]`](crate::functional::function_component). /// /// We provide a blanket implementation of this trait for every member that implements [`Component`]. -pub trait BaseComponent: SealedBaseComponent + Sized + 'static { +pub trait BaseComponent: sealed::SealedBaseComponent + Sized + 'static { /// The Component's Message. type Message: 'static; - /// The Component's properties. + /// The Component's Properties. type Properties: Properties; /// Creates a component. @@ -201,4 +203,24 @@ where } } -impl SealedBaseComponent for T where T: Sized + Component + 'static {} +impl sealed::SealedBaseComponent for T where T: Sized + Component + 'static {} + +/// A trait that indicates a type is able to be converted into a component. +/// +/// You may want to use this trait if you want to accept both function components and struct +/// components as a generic parameter. +pub trait IntoComponent { + /// The Component's Properties. + type Properties: Properties; + + /// The Component Type. + type Component: BaseComponent + 'static; +} + +impl IntoComponent for T +where + T: BaseComponent + 'static, +{ + type Properties = T::Properties; + type Component = T; +} diff --git a/packages/yew/src/html/component/scope.rs b/packages/yew/src/html/component/scope.rs index 89529c9d1e2..02557af8fbf 100644 --- a/packages/yew/src/html/component/scope.rs +++ b/packages/yew/src/html/component/scope.rs @@ -10,9 +10,10 @@ use super::{ use crate::callback::Callback; use crate::context::{ContextHandle, ContextProvider}; use crate::dom_bundle::{ComponentRenderState, Scoped}; +use crate::html::IntoComponent; use crate::html::NodeRef; use crate::scheduler::{self, Shared}; -use std::any::TypeId; +use std::any::{Any, TypeId}; use std::cell::{Ref, RefCell}; use std::marker::PhantomData; use std::ops::Deref; @@ -63,7 +64,7 @@ impl Clone for MsgQueue { pub struct AnyScope { type_id: TypeId, parent: Option>, - state: Shared>, + typed_scope: Rc, } impl fmt::Debug for AnyScope { @@ -76,8 +77,8 @@ impl From> for AnyScope { fn from(scope: Scope) -> Self { AnyScope { type_id: TypeId::of::(), - parent: scope.parent, - state: scope.state, + parent: scope.parent.clone(), + typed_scope: Rc::new(scope), } } } @@ -88,7 +89,7 @@ impl AnyScope { Self { type_id: TypeId::of::<()>(), parent: None, - state: Rc::new(RefCell::new(None)), + typed_scope: Rc::new(()), } } @@ -107,37 +108,25 @@ impl AnyScope { /// # Panics /// /// If the self value can't be cast into the target type. - pub fn downcast(self) -> Scope { - self.try_downcast::().unwrap() + pub fn downcast(&self) -> Scope { + self.try_downcast::().unwrap() } /// Attempts to downcast into a typed scope /// /// Returns [`None`] if the self value can't be cast into the target type. - pub fn try_downcast(self) -> Option> { - let state = self.state.borrow(); - - state.as_ref().map(|m| { - m.inner - .as_any() - .downcast_ref::>() - .unwrap() - .context - .link() - .clone() - }) + pub fn try_downcast(&self) -> Option> { + self.typed_scope + .downcast_ref::>() + .cloned() } /// Attempts to find a parent scope of a certain type /// /// Returns [`None`] if no parent scope with the specified type was found. - pub fn find_parent_scope(&self) -> Option> { - let expected_type_id = TypeId::of::(); + pub fn find_parent_scope(&self) -> Option> { iter::successors(Some(self), |scope| scope.get_parent()) - .filter(|scope| scope.get_type_id() == &expected_type_id) - .cloned() - .map(AnyScope::downcast::) - .next() + .find_map(AnyScope::try_downcast::) } /// Accesses a value provided by a parent `ContextProvider` component of the diff --git a/packages/yew/src/html/mod.rs b/packages/yew/src/html/mod.rs index ae290b1e67f..7422cb9eca9 100644 --- a/packages/yew/src/html/mod.rs +++ b/packages/yew/src/html/mod.rs @@ -7,6 +7,7 @@ mod error; mod listener; pub use classes::*; +pub(crate) use component::sealed; pub use component::*; pub use conversion::*; pub use error::*; diff --git a/packages/yew/src/lib.rs b/packages/yew/src/lib.rs index e614ff2a77e..9c3be9f316b 100644 --- a/packages/yew/src/lib.rs +++ b/packages/yew/src/lib.rs @@ -300,7 +300,7 @@ pub mod events { pub use crate::dom_bundle::AppHandle; use web_sys::Element; -use crate::html::BaseComponent; +use crate::html::IntoComponent; thread_local! { static PANIC_HOOK_IS_SET: Cell = Cell::new(false); @@ -322,44 +322,44 @@ fn set_default_panic_hook() { /// The main entry point of a Yew application. /// If you would like to pass props, use the `start_app_with_props_in_element` method. -pub fn start_app_in_element(element: Element) -> AppHandle +pub fn start_app_in_element(element: Element) -> AppHandle where - COMP: BaseComponent, - COMP::Properties: Default, + ICOMP: IntoComponent, + ICOMP::Properties: Default, { - start_app_with_props_in_element::(element, COMP::Properties::default()) + start_app_with_props_in_element(element, ICOMP::Properties::default()) } /// Starts an yew app mounted to the body of the document. /// Alias to start_app_in_element(Body) -pub fn start_app() -> AppHandle +pub fn start_app() -> AppHandle where - COMP: BaseComponent, - COMP::Properties: Default, + ICOMP: IntoComponent, + ICOMP::Properties: Default, { - start_app_with_props::(COMP::Properties::default()) + start_app_with_props(ICOMP::Properties::default()) } /// The main entry point of a Yew application. This function does the /// same as `start_app_in_element(...)` but allows to start an Yew application with properties. -pub fn start_app_with_props_in_element( +pub fn start_app_with_props_in_element( element: Element, - props: COMP::Properties, -) -> AppHandle + props: ICOMP::Properties, +) -> AppHandle where - COMP: BaseComponent, + ICOMP: IntoComponent, { set_default_panic_hook(); - AppHandle::::mount_with_props(element, Rc::new(props)) + AppHandle::::mount_with_props(element, Rc::new(props)) } /// The main entry point of a Yew application. /// This function does the same as `start_app(...)` but allows to start an Yew application with properties. -pub fn start_app_with_props(props: COMP::Properties) -> AppHandle +pub fn start_app_with_props(props: ICOMP::Properties) -> AppHandle where - COMP: BaseComponent, + ICOMP: IntoComponent, { - start_app_with_props_in_element::( + start_app_with_props_in_element( gloo_utils::document() .body() .expect("no body node found") @@ -383,10 +383,11 @@ pub mod prelude { pub use crate::events::*; pub use crate::html::{ create_portal, BaseComponent, Children, ChildrenWithProps, Classes, Component, Context, - Html, HtmlResult, NodeRef, Properties, + Html, HtmlResult, IntoComponent, NodeRef, Properties, }; pub use crate::macros::{classes, html, html_nested}; pub use crate::suspense::Suspense; + pub use crate::virtual_dom::AttrValue; pub use crate::functional::*; } diff --git a/packages/yew/src/server_renderer.rs b/packages/yew/src/server_renderer.rs index 91bf4c95212..b7277340693 100644 --- a/packages/yew/src/server_renderer.rs +++ b/packages/yew/src/server_renderer.rs @@ -3,29 +3,29 @@ use super::*; use crate::html::Scope; /// A Yew Server-side Renderer. -#[derive(Debug)] #[cfg_attr(documenting, doc(cfg(feature = "ssr")))] -pub struct ServerRenderer +#[derive(Debug)] +pub struct ServerRenderer where - COMP: BaseComponent, + ICOMP: IntoComponent, { - props: COMP::Properties, + props: ICOMP::Properties, } -impl Default for ServerRenderer +impl Default for ServerRenderer where - COMP: BaseComponent, - COMP::Properties: Default, + ICOMP: IntoComponent, + ICOMP::Properties: Default, { fn default() -> Self { - Self::with_props(COMP::Properties::default()) + Self::with_props(ICOMP::Properties::default()) } } -impl ServerRenderer +impl ServerRenderer where - COMP: BaseComponent, - COMP::Properties: Default, + ICOMP: IntoComponent, + ICOMP::Properties: Default, { /// Creates a [ServerRenderer] with default properties. pub fn new() -> Self { @@ -33,12 +33,12 @@ where } } -impl ServerRenderer +impl ServerRenderer where - COMP: BaseComponent, + ICOMP: IntoComponent, { /// Creates a [ServerRenderer] with custom properties. - pub fn with_props(props: COMP::Properties) -> Self { + pub fn with_props(props: ICOMP::Properties) -> Self { Self { props } } @@ -53,7 +53,7 @@ where /// Renders Yew Application to a String. pub async fn render_to_string(self, w: &mut String) { - let scope = Scope::::new(None); + let scope = Scope::<::Component>::new(None); scope.render_to_string(w, self.props.into()).await; } } diff --git a/packages/yew/src/virtual_dom/vcomp.rs b/packages/yew/src/virtual_dom/vcomp.rs index 76b098523a2..76d1e4f3a05 100644 --- a/packages/yew/src/virtual_dom/vcomp.rs +++ b/packages/yew/src/virtual_dom/vcomp.rs @@ -2,7 +2,7 @@ use super::Key; use crate::dom_bundle::{Mountable, PropsWrapper}; -use crate::html::{BaseComponent, NodeRef}; +use crate::html::{BaseComponent, IntoComponent, NodeRef}; use std::any::TypeId; use std::fmt; use std::rc::Rc; @@ -41,15 +41,15 @@ impl Clone for VComp { } /// A virtual child component. -pub struct VChild { +pub struct VChild { /// The component properties - pub props: Rc, + pub props: Rc, /// Reference to the mounted node node_ref: NodeRef, key: Option, } -impl Clone for VChild { +impl Clone for VChild { fn clone(&self) -> Self { VChild { props: Rc::clone(&self.props), @@ -59,21 +59,21 @@ impl Clone for VChild { } } -impl PartialEq for VChild +impl PartialEq for VChild where - COMP::Properties: PartialEq, + ICOMP::Properties: PartialEq, { - fn eq(&self, other: &VChild) -> bool { + fn eq(&self, other: &VChild) -> bool { self.props == other.props } } -impl VChild +impl VChild where - COMP: BaseComponent, + ICOMP: IntoComponent, { /// Creates a child component that can be accessed and modified by its parent. - pub fn new(props: COMP::Properties, node_ref: NodeRef, key: Option) -> Self { + pub fn new(props: ICOMP::Properties, node_ref: NodeRef, key: Option) -> Self { Self { props: Rc::new(props), node_ref, @@ -82,25 +82,25 @@ where } } -impl From> for VComp +impl From> for VComp where - COMP: BaseComponent, + ICOMP: IntoComponent, { - fn from(vchild: VChild) -> Self { - VComp::new::(vchild.props, vchild.node_ref, vchild.key) + fn from(vchild: VChild) -> Self { + VComp::new::(vchild.props, vchild.node_ref, vchild.key) } } impl VComp { /// Creates a new `VComp` instance. - pub fn new(props: Rc, node_ref: NodeRef, key: Option) -> Self + pub fn new(props: Rc, node_ref: NodeRef, key: Option) -> Self where - COMP: BaseComponent, + ICOMP: IntoComponent, { VComp { - type_id: TypeId::of::(), + type_id: TypeId::of::(), node_ref, - mountable: Box::new(PropsWrapper::::new(props)), + mountable: Box::new(PropsWrapper::::new(props)), key, } } diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index 3b98a0e7ae4..975f3cb84db 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -1,7 +1,7 @@ //! This module contains the implementation of abstract virtual node. use super::{Key, VChild, VComp, VList, VPortal, VSuspense, VTag, VText}; -use crate::html::BaseComponent; +use crate::html::IntoComponent; use std::cmp::PartialEq; use std::fmt; use std::iter::FromIterator; @@ -93,11 +93,11 @@ impl From for VNode { } } -impl From> for VNode +impl From> for VNode where - COMP: BaseComponent, + ICOMP: IntoComponent, { - fn from(vchild: VChild) -> Self { + fn from(vchild: VChild) -> Self { VNode::VComp(VComp::from(vchild)) } } diff --git a/packages/yew/tests/failed_tests/base_component_impl-fail.stderr b/packages/yew/tests/failed_tests/base_component_impl-fail.stderr index 82e0f9a46f8..563ac8e341e 100644 --- a/packages/yew/tests/failed_tests/base_component_impl-fail.stderr +++ b/packages/yew/tests/failed_tests/base_component_impl-fail.stderr @@ -4,9 +4,9 @@ error[E0277]: the trait bound `Comp: yew::Component` is not satisfied 6 | impl BaseComponent for Comp { | ^^^^^^^^^^^^^ the trait `yew::Component` is not implemented for `Comp` | - = note: required because of the requirements on the impl of `SealedBaseComponent` for `Comp` + = note: required because of the requirements on the impl of `html::component::sealed::SealedBaseComponent` for `Comp` note: required by a bound in `BaseComponent` --> src/html/component/mod.rs | - | pub trait BaseComponent: SealedBaseComponent + Sized + 'static { - | ^^^^^^^^^^^^^^^^^^^ required by this bound in `BaseComponent` + | pub trait BaseComponent: sealed::SealedBaseComponent + Sized + 'static { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `BaseComponent` diff --git a/packages/yew/tests/failed_tests/sealed_base_component_impl-fail.rs b/packages/yew/tests/failed_tests/sealed_base_component_impl-fail.rs index 9127dec8719..dd7399b146a 100644 --- a/packages/yew/tests/failed_tests/sealed_base_component_impl-fail.rs +++ b/packages/yew/tests/failed_tests/sealed_base_component_impl-fail.rs @@ -23,6 +23,6 @@ impl BaseComponent for Comp { fn destroy(&mut self, _ctx: &Context) {} } -impl yew::html::component::SealedBaseComponent for Comp {} +impl yew::html::component::sealed::SealedBaseComponent for Comp {} fn main() {} diff --git a/packages/yew/tests/failed_tests/sealed_base_component_impl-fail.stderr b/packages/yew/tests/failed_tests/sealed_base_component_impl-fail.stderr index 322cc88b4dd..4bdf97cf6a6 100644 --- a/packages/yew/tests/failed_tests/sealed_base_component_impl-fail.stderr +++ b/packages/yew/tests/failed_tests/sealed_base_component_impl-fail.stderr @@ -1,7 +1,7 @@ error[E0603]: module `component` is private --> tests/failed_tests/sealed_base_component_impl-fail.rs:26:17 | -26 | impl yew::html::component::SealedBaseComponent for Comp {} +26 | impl yew::html::component::sealed::SealedBaseComponent for Comp {} | ^^^^^^^^^ private module | note: the module `component` is defined here diff --git a/tools/process-benchmark-results/src/main.rs b/tools/process-benchmark-results/src/main.rs index 586eaa8c618..286caa920b3 100644 --- a/tools/process-benchmark-results/src/main.rs +++ b/tools/process-benchmark-results/src/main.rs @@ -20,7 +20,7 @@ fn main() -> Result<()> { let transformed_benchmarks: Vec = input_json .into_iter() .map(|v| GhActionBenchmark { - name: format!("{} {}", v["framework"], v["benchmark"]).replace('\"', ""), + name: format!("{} {}", v["framework"], v["benchmark"]).replace('"', ""), unit: String::default(), value: v["median"].to_string(), }) From 5cbff1daf0d7c7581242ee3f273e395fe6dbe0d7 Mon Sep 17 00:00:00 2001 From: Kaede Hoshikawa Date: Sun, 6 Mar 2022 14:58:01 +0900 Subject: [PATCH 02/30] Bring changes to this branch. --- Cargo.toml | 1 + examples/function_router/Cargo.toml | 23 ++ examples/function_router/README.md | 49 +++ examples/function_router/data/keywords.txt | 34 ++ examples/function_router/data/syllables.txt | 20 ++ examples/function_router/data/yew.txt | 317 ++++++++++++++++++ examples/function_router/index.html | 17 + examples/function_router/index.scss | 27 ++ examples/function_router/src/app.rs | 118 +++++++ .../src/components/author_card.rs | 75 +++++ .../function_router/src/components/mod.rs | 5 + .../function_router/src/components/nav.rs | 57 ++++ .../src/components/pagination.rs | 157 +++++++++ .../src/components/post_card.rs | 67 ++++ .../src/components/progress_delay.rs | 116 +++++++ examples/function_router/src/content.rs | 128 +++++++ examples/function_router/src/generator.rs | 161 +++++++++ examples/function_router/src/lib.rs | 24 ++ examples/function_router/src/main.rs | 13 + examples/function_router/src/pages/author.rs | 68 ++++ .../function_router/src/pages/author_list.rs | 64 ++++ examples/function_router/src/pages/home.rs | 67 ++++ examples/function_router/src/pages/mod.rs | 6 + .../src/pages/page_not_found.rs | 19 ++ examples/function_router/src/pages/post.rs | 157 +++++++++ .../function_router/src/pages/post_list.rs | 52 +++ 26 files changed, 1842 insertions(+) create mode 100644 examples/function_router/Cargo.toml create mode 100644 examples/function_router/README.md create mode 100644 examples/function_router/data/keywords.txt create mode 100644 examples/function_router/data/syllables.txt create mode 100644 examples/function_router/data/yew.txt create mode 100644 examples/function_router/index.html create mode 100644 examples/function_router/index.scss create mode 100644 examples/function_router/src/app.rs create mode 100644 examples/function_router/src/components/author_card.rs create mode 100644 examples/function_router/src/components/mod.rs create mode 100644 examples/function_router/src/components/nav.rs create mode 100644 examples/function_router/src/components/pagination.rs create mode 100644 examples/function_router/src/components/post_card.rs create mode 100644 examples/function_router/src/components/progress_delay.rs create mode 100644 examples/function_router/src/content.rs create mode 100644 examples/function_router/src/generator.rs create mode 100644 examples/function_router/src/lib.rs create mode 100644 examples/function_router/src/main.rs create mode 100644 examples/function_router/src/pages/author.rs create mode 100644 examples/function_router/src/pages/author_list.rs create mode 100644 examples/function_router/src/pages/home.rs create mode 100644 examples/function_router/src/pages/mod.rs create mode 100644 examples/function_router/src/pages/page_not_found.rs create mode 100644 examples/function_router/src/pages/post.rs create mode 100644 examples/function_router/src/pages/post_list.rs diff --git a/Cargo.toml b/Cargo.toml index a8a25de90ac..3fc5039774f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "examples/dyn_create_destroy_apps", "examples/file_upload", "examples/function_memory_game", + "examples/function_router", "examples/function_todomvc", "examples/futures", "examples/game_of_life", diff --git a/examples/function_router/Cargo.toml b/examples/function_router/Cargo.toml new file mode 100644 index 00000000000..0593e73ca34 --- /dev/null +++ b/examples/function_router/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "function_router" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] +lipsum = "0.8" +log = "0.4" +rand = { version = "0.8", features = ["small_rng"] } +yew = { path = "../../packages/yew" } +yew-router = { path = "../../packages/yew-router" } +serde = { version = "1.0", features = ["derive"] } +lazy_static = "1.4.0" +gloo-timers = "0.2" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +getrandom = { version = "0.2", features = ["js"] } +instant = { version = "0.1", features = ["wasm-bindgen"] } +wasm-logger = "0.2" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +instant = { version = "0.1" } diff --git a/examples/function_router/README.md b/examples/function_router/README.md new file mode 100644 index 00000000000..2a78b7ba732 --- /dev/null +++ b/examples/function_router/README.md @@ -0,0 +1,49 @@ +# Function Router Example + +This is identical to the router example, but written in function +components. + +[![Demo](https://img.shields.io/website?label=demo&url=https%3A%2F%2Fexamples.yew.rs%2Ffunction_router)](https://examples.yew.rs/function_router) + +A blog all about yew. +The best way to figure out what this example is about is to just open it up. +It's mobile friendly too! + +## Running + +While not strictly necessary, this example should be built in release mode: + +```bash +trunk serve --release +``` + +Content generation can take up quite a bit of time in debug builds. + +## Concepts + +This example involves many different parts, here are just the Yew specific things: + +- Uses [`yew-router`] to render and switch between multiple pages. + +The example automatically adapts to the `--public-url` value passed to Trunk. +This allows it to be hosted on any path, not just at the root. +For example, our demo is hosted at [/router](https://examples.yew.rs/router). + +This is achieved by adding `` to the [index.html](index.html) file. +Trunk rewrites this tag to contain the value passed to `--public-url` which can then be retrieved at runtime. +Take a look at [`Route`](src/main.rs) for the implementation. + +## Improvements + +- Use a special image component which shows a progress bar until the image is loaded. +- Scroll back to the top after switching route +- Run content generation in a dedicated web worker +- Use longer Markov chains to achieve more coherent results +- Make images deterministic (the same seed should produce the same images) +- Show posts by the author on their page + (this is currently impossible because we need to find post seeds which in turn generate the author's seed) +- Show other posts at the end of a post ("continue reading") +- Home (`/`) should include links to the post list and the author introduction +- Detect sub-path from `--public-url` value passed to Trunk. See: thedodd/trunk#51 + +[`yew-router`]: https://docs.rs/yew-router/latest/yew_router/ diff --git a/examples/function_router/data/keywords.txt b/examples/function_router/data/keywords.txt new file mode 100644 index 00000000000..874d1e2c302 --- /dev/null +++ b/examples/function_router/data/keywords.txt @@ -0,0 +1,34 @@ +allergenics +archaeology +austria +berries +birds +color +conservation +cosmology +culture +europe +evergreens +fleshy +france +guides +horticulture +ireland +landscaping +medicine +music +poison +religion +rome +rust +scotland +seeds +spain +taxonomy +toxics +tradition +trees +wasm +wood +woodworking +yew diff --git a/examples/function_router/data/syllables.txt b/examples/function_router/data/syllables.txt new file mode 100644 index 00000000000..c6e97aabb8c --- /dev/null +++ b/examples/function_router/data/syllables.txt @@ -0,0 +1,20 @@ +ald +ber +fe +ger +jo +jus +kas +lix +lu +mon +mour +nas +ridge +ry +si +star +tey +tim +tin +yew diff --git a/examples/function_router/data/yew.txt b/examples/function_router/data/yew.txt new file mode 100644 index 00000000000..8e3a2d9f4f0 --- /dev/null +++ b/examples/function_router/data/yew.txt @@ -0,0 +1,317 @@ +Taxonomy and naming + +The word yew is from Proto-Germanic, possibly originally a loanword from Gaulish. +In German it is known as Eibe. Baccata is Latin for bearing berries. +The word yew as it was originally used seems to refer to the color brown. +The yew was known to Theophrastus, who noted its preference for mountain coolness and shade, +its evergreen character and its slow growth. + +Most Romance languages, with the notable exception of French, +kept a version of the Latin word taxus from the same root as toxic. +In Slavic languages, the same root is preserved. + +In Iran, the tree is known as sorkhdār. + +The common yew was one of the many species first described by Linnaeus. +It is one of around 30 conifer species in seven genera in the family Taxaceae, which is placed in the order Pinales. + + +Description + +It is a small to medium-sized evergreen tree, growing 10 to 20 metres tall, with a trunk up to 2 metres in diameter. +The bark is thin, scaly brown, coming off in small flakes aligned with the stem. +The leaves are flat, dark green, 1 to 4 centimetres long and 2 to 3 millimetres broad, arranged spirally on the stem, +but with the leaf bases twisted to align the leaves in two flat rows either side of the stem, +except on erect leading shoots where the spiral arrangement is more obvious. +The leaves are poisonous. + +The seed cones are modified, each cone containing a single seed, which is 4 to 7 millimetres long, +and partly surrounded by a fleshy scale which develops into a soft, bright red berry-like structure called an aril. +The aril is 8 to 15 millimetres long and wide and open at the end. +The arils mature 6 to 9 months after pollination, and with the seed contained, +are eaten by thrushes, waxwings and other birds, which disperse the hard seeds undamaged in their droppings. +Maturation of the arils is spread over 2 to 3 months, increasing the chances of successful seed dispersal. +The seeds themselves are poisonous and bitter, but are opened and eaten by some bird species including hawfinches, +greenfinches and great tits. +The aril is not poisonous, it is gelatinous and very sweet tasting. The male cones are globose, +3–6 millimetres in diameter, and shed their pollen in early spring. +The yew is mostly dioecious, but occasional individuals can be variably monoecious, or change sex with time. + + +Longevity + +Taxus baccata can reach 400 to 600 years of age. +Some specimens live longer but the age of yews is often overestimated. +Ten yews in Britain are believed to predate the 10th century. +The potential age of yews is impossible to determine accurately and is subject to much dispute. +There is rarely any wood as old as the entire tree, while the boughs themselves often become hollow with age, +making ring counts impossible. +Evidence based on growth rates and archaeological work of surrounding structures suggests the oldest yews, +such as the Fortingall Yew in Perthshire, Scotland, may be in the range of 2,000 years, +placing them among the oldest plants in Europe. +One characteristic contributing to yew's longevity is that it is able to split under the weight of advanced growth +without succumbing to disease in the fracture, as do most other trees. Another is its ability to give rise to new +epicormic and basal shoots from cut surfaces and low on its trunk, even at an old age. + + +Significant trees + +The Fortingall Yew in Perthshire, Scotland, +has the largest recorded trunk girth in Britain and experts estimate it to be 2,000 to 3,000 years old, +although it may be a remnant of a post-Roman Christian site and around 1,500 years old. +The Llangernyw Yew in Clwyd, Wales, can be found at an early saint site and is about 1,500 years old. +Other well known yews include the Ankerwycke Yew, the Balderschwang Yew, the Caesarsboom, the Florence Court Yew, +and the Borrowdale Fraternal Four, of which poet William Wordsworth wrote. +The Kingley Vale National Nature Reserve in West Sussex has one of Europe's largest yew woodlands. + +The oldest specimen in Spain is located in Bermiego, Asturias. It is known as Teixu l'Iglesia in the Asturian language. +It stands 15m tall with a trunk diameter of 7m and a crown diameter of 15m. +It was declared a Natural Monument on April 27, +1995 by the Asturian Government and is protected by the Plan of Natural Resources. + +A unique forest formed by Taxus baccata and European box lies within the city of Sochi, in the Western Caucasus. + +The oldest Irish Yew, the Florence Court Yew, still stands in the grounds of Florence Court estate in County Fermanagh, +Northern Ireland. +The Irish Yew has become ubiquitous in cemeteries across the world and it is believed that all known examples are from +cuttings from this tree. + + +Toxicity + +The entire yew bush, except the aril, is poisonous. +It is toxic due to a group of chemicals called taxine alkaloids. +Their cardiotoxicity is well known and act via calcium and sodium channel antagonism, causing an increase in +cytoplasmic calcium currents of the myocardial cells. +The seeds contain the highest concentrations of these alkaloids. If any leaves or seeds of the plant are ingested, +urgent medical advice is recommended as well as observation for at least 6 hours after the point of ingestion. +The most cardiotoxic taxine is Taxine B followed by Taxine A. +Taxine B also happens to be the most common alkaloid in the Taxus species. + +Yew poisonings are relatively common in both domestic and wild animals who consume the plant accidentally, +resulting in countless fatalities in livestock. +The taxine alkaloids are absorbed quickly from the intestine and in high enough quantities can cause death due to +cardiac arrest or respiratory failure. +Taxines are also absorbed efficiently via the skin and Taxus species should thus be handled with care and preferably +with gloves. +Taxus baccata leaves contain approximately 5mg of taxines per 1g of leaves. + +The estimated lethal dose of taxine alkaloids is approximately 3.0mg/kg body weight for humans. +The lethal dose for an adult is reported to be 50g of yew needles. +Patients who ingest a lethal dose frequently die due to cardiogenic shock, in spite of resuscitation efforts. +There are currently no known antidotes for yew poisoning, +but drugs such as atropine have been used to treat the symptoms. +Taxine remains in the plant all year, with maximal concentrations appearing during the winter. +Dried yew plant material retains its toxicity for several months and even increases its toxicity as the water is removed. +Fallen leaves should therefore also be considered toxic. +Poisoning usually occurs when leaves of yew trees are eaten, +but in at least one case a victim inhaled sawdust from a yew tree. + +It is difficult to measure taxine alkaloids and this is a major reason as to why different studies show different results. + +Several studies have found taxine LD50 values under 20mg/kg in mice and rats. + +Male and monoecious yews in this genus release toxic pollen, which can cause the mild symptoms. +The pollen is also a trigger for asthma. +These pollen grains are only 15 microns in size, and can easily pass through most window screens. + + +Allergenic potential + +Yews in this genus are primarily separate-sexed, and males are extremely allergenic, +with an OPALS allergy scale rating of 10 out of 10. +Completely female yews have an OPALS rating of 1, and are considered allergy-fighting. +Male yews bloom and release abundant amounts of pollen in the spring; +completely female yews only trap pollen while producing none. + + +Uses and traditions + +In the ancient Celtic world, the yew tree had extraordinary importance; a passage by Caesar narrates that Cativolcus, +chief of the Eburones poisoned himself with yew rather than submit to Rome. +Similarly, Florus notes that when the Cantabrians were under siege by the legate Gaius Furnius in 22 BC, +most of them took their lives either by the sword, by fire, or by a poison extracted ex arboribus taxeis, that is, +from the yew tree. +In a similar way, Orosius notes that when the Astures were besieged at Mons Medullius, +they preferred to die by their own swords or by the yew tree poison rather than surrender. + +The word York is derived from the Brittonic name Eburākon, +a combination of eburos "yew-tree" and a suffix of appurtenance meaning either "place of the yew trees"; +or alternatively, "the settlement of Eburos". + +The name Eboracum became the Anglian Eoforwic in the 7th century. +When the Danish army conquered the city in 866, its name became Jórvík. + +The Old French and Norman name of the city following the Norman Conquest was recorded as Everwic in works such as +Wace's Roman de Rou. +Jórvík, meanwhile, gradually reduced to York in the centuries after the Conquest, +moving from the Middle English Yerk in the 14th century through Yourke in the 16th century to Yarke in the 17th century. +The form York was first recorded in the 13th century. Many company and place names, such as the Ebor race meeting, +refer to the Latinised Brittonic, Roman name. + +The 12th‑century chronicler Geoffrey of Monmouth, in his fictional account of the prehistoric kings of Britain, +Historia Regum Britanniae, suggests the name derives from that of a pre-Roman city founded by the legendary king Ebraucus. + +The Archbishop of York uses Ebor as his surname in his signature. + +The area of Ydre in the South Swedish highlands is interpreted to mean place of yews. +Two localities in particular, Idhult and Idebo, appear to be further associated with yews. + + +Religion + +The yew is traditionally and regularly found in churchyards in England, Wales, Scotland, Ireland and Northern France. +Some examples can be found in La Haye-de-Routot or La Lande-Patry. +It is said up to 40 people could stand inside one of the La-Haye-de-Routot yew trees, +and the Le Ménil-Ciboult yew is probably the largest at 13m diameter. +Yews may grow to become exceptionally large and may live to be over 2,000 years old. +Sometimes monks planted yews in the middle of their cloister, as at Muckross Abbey or abbaye de Jumièges. +Some ancient yew trees are located at St. Mary the Virgin Church, Overton-on-Dee in Wales. + +In Asturian tradition and culture, the yew tree was considered to be linked with the land, people, +ancestors and ancient religion. It was tradition on All Saints' Day to bring a branch of a yew tree to the tombs of +those who had died recently so they would be guided in their return to the Land of Shadows. +The yew tree has been found near chapels, +churches and cemeteries since ancient times as a symbol of the transcendence of death. +They are often found in the main squares of villages where people celebrated the open councils that served as a way of +general assembly to rule village affairs. + +It has been suggested that the sacred tree at the Temple at Uppsala was an ancient yew tree. +The Christian church commonly found it expedient to take over existing pre-Christian sacred sites for churches. +It has also been suggested that yews were planted at religious sites as their long life was suggestive of eternity, +or because, being toxic when ingested, they were seen as trees of death. +Another suggested explanation is that yews were planted to discourage farmers and drovers from letting animals wander +onto the burial grounds, the poisonous foliage being the disincentive. +A further possible reason is that fronds and branches of yew were often used as a substitute for palms on Palm Sunday. + +Some yew trees were actually native to the sites before the churches were built. +King Edward I of England ordered yew trees to be planted in churchyards to offer some protection to the buildings. +Yews are poisonous so by planting them in the churchyards cattle that were not allowed to graze on hallowed +ground were safe from eating yew. Yew branches touching the ground take root and sprout again; +this became a symbol of death, rebirth and therefore immortality. + +In interpretations of Norse cosmology, the tree Yggdrasil has traditionally been interpreted as a giant ash tree. +Some scholars now believe errors were made in past interpretations of the ancient writings, +and that the tree is most likely a European yew. + +In the Crann Ogham—the variation on the ancient Irish Ogham alphabet which consists of a list of trees—yew +is the last in the main list of 20 trees, primarily symbolizing death. +There are stories of people who have committed suicide by ingesting the foliage. +As the ancient Celts also believed in the transmigration of the soul, +there is in some cases a secondary meaning of the eternal soul that survives death to be reborn in a new form. + + +Medical + +Certain compounds found in the bark of yew trees were discovered by Wall and Wani in 1967 to have efficacy as +anti-cancer agents. +The precursors of the chemotherapy drug paclitaxel were later shown to be synthesized easily from extracts +of the leaves of European yew, which is a much more renewable source than the bark of the Pacific yew from which +they were initially isolated. +This ended a point of conflict in the early 1990s; many environmentalists, +including Al Gore, had opposed the destructive harvesting of Pacific yew for paclitaxel cancer treatments. +Docetaxel can then be obtained by semi-synthetic conversion from the precursors. + + +Woodworking and longbows + +Wood from the yew is classified as a closed-pore softwood, similar to cedar and pine. +Easy to work, yew is among the hardest of the softwoods; yet it possesses a remarkable elasticity, +making it ideal for products that require springiness, such as bows. +Due to all parts of the yew and its volatile oils being poisonous and cardiotoxic, +a mask should be worn if one comes in contact with sawdust from the wood. + +One of the world's oldest surviving wooden artifacts is a Clactonian yew spear head, found in 1911 at Clacton-on-Sea, +in Essex, UK. Known as the Clacton Spear, it is estimated to be over 400,000 years old. + +Yew is also associated with Wales and England because of the longbow, +an early weapon of war developed in northern Europe, +and as the English longbow the basis for a medieval tactical system. +The oldest surviving yew longbow was found at Rotten Bottom in Dumfries and Galloway, Scotland. +It has been given a calibrated radiocarbon date of 4040 BC to 3640 BC and is on display in the National Museum of +Scotland. Yew is the wood of choice for longbow making; +the heartwood is always on the inside of the bow with the sapwood on the outside. +This makes most efficient use of their properties as heartwood is best in compression whilst +sapwood is superior in tension. +However, much yew is knotty and twisted, and therefore unsuitable for bowmaking; +most trunks do not give good staves and even in a good trunk much wood has to be discarded. + +There was a tradition of planting yew trees in churchyards throughout Britain and Ireland, among other reasons, +as a resource for bows. +Ardchattan Priory whose yew trees, according to other accounts, +were inspected by Robert the Bruce and cut to make at least some of the longbows used at the Battle of Bannockburn. + +The trade of yew wood to England for longbows was so robust that it depleted the stocks of good-quality, +mature yew over a vast area. +The first documented import of yew bowstaves to England was in 1294. +In 1423 the Polish king commanded protection of yews in order to cut exports, +facing nearly complete destruction of local yew stock. In 1470 compulsory archery practice was renewed, and hazel, ash, +and laburnum were specifically allowed for practice bows. +Supplies still proved insufficient, until by the Statute of Westminster in 1472, +every ship coming to an English port had to bring four bowstaves for every tun. +Richard III of England increased this to ten for every tun. This stimulated a vast network of extraction and supply, +which formed part of royal monopolies in southern Germany and Austria. +In 1483, the price of bowstaves rose from two to eight pounds per hundred, +and in 1510 the Venetians would only sell a hundred for sixteen pounds. +In 1507 the Holy Roman Emperor asked the Duke of Bavaria to stop cutting yew, but the trade was profitable, +and in 1532 the royal monopoly was granted for the usual quantity if there are that many. +In 1562, the Bavarian government sent a long plea to the Holy Roman Emperor asking him to stop the cutting of yew, +and outlining the damage done to the forests by its selective extraction, +which broke the canopy and allowed wind to destroy neighbouring trees. In 1568, despite a request from Saxony, +no royal monopoly was granted because there was no yew to cut, +and the next year Bavaria and Austria similarly failed to produce enough yew to justify a royal monopoly. +Forestry records in this area in the 17th century do not mention yew, and it seems that no mature trees were to be had. +The English tried to obtain supplies from the Baltic, but at this period bows were being replaced by guns in any case. + + +Horticulture + +Today European yew is widely used in landscaping and ornamental horticulture. +Due to its dense, dark green, mature foliage, and its tolerance of even very severe pruning, +it is used especially for formal hedges and topiary. +Its relatively slow growth rate means that in such situations it needs to be clipped only once per year. + +Well over 200 cultivars of T. baccata have been named. The most popular of these are the Irish yew, +a fastigiate cultivar of the European yew selected from two trees found growing in Ireland, +and the several cultivars with yellow leaves, collectively known as golden yew. In some locations, +when hemmed in by buildings or other trees, +an Irish yew can reach 20 feet in height without exceeding 2 feet in diameter at its thickest point, +although with age many Irish yews assume a fat cigar shape rather than being truly columnar. + +European yew will tolerate growing in a wide range of soils and situations, including shallow chalk soils and shade, +although in deep shade its foliage may be less dense. +However it cannot tolerate waterlogging, +and in poorly-draining situations is liable to succumb to the root-rotting pathogen Phytophthora cinnamomi. + +In Europe, Taxus baccata grows naturally north to Molde in southern Norway, but it is used in gardens further north. +It is also popular as a bonsai in many parts of Europe and makes a handsome small- to large-sized bonsai. + + +Privies + +In England, yew has historically been sometimes associated with privies, +possibly because the smell of the plant keeps insects away. + + +Musical instruments + +The late Robert Lundberg, a noted luthier who performed extensive research on historical lute-making methodology, +states in his 2002 book Historical Lute Construction that yew was historically a prized wood for lute construction. +European legislation establishing use limits and requirements for yew limited supplies available to luthiers, +but it was apparently as prized among medieval, renaissance, +and baroque lute builders as Brazilian rosewood is among contemporary guitar-makers for its quality of sound and beauty. + + +Conservation + +Clippings from ancient specimens in the UK, including the Fortingall Yew, +were taken to the Royal Botanic Gardens in Edinburgh to form a mile-long hedge. +The purpose of this project is to maintain the DNA of Taxus baccata. +The species is threatened by felling, partly due to rising demand from pharmaceutical companies, and disease. + +Another conservation programme was run in Catalonia in the early 2010s, by the Forest Sciences Centre of Catalonia, +in order to protect genetically endemic yew populations, and preserve them from overgrazing and forest fires. +In the framework of this programme, the 4th International Yew Conference was organised in the Poblet Monastery in 2014, +which proceedings are available. + +There has also been a conservation programme in northern Portugal and Northern Spain. diff --git a/examples/function_router/index.html b/examples/function_router/index.html new file mode 100644 index 00000000000..d7101222d8b --- /dev/null +++ b/examples/function_router/index.html @@ -0,0 +1,17 @@ + + + + + + + Yew • Function Router + + + + + + + diff --git a/examples/function_router/index.scss b/examples/function_router/index.scss new file mode 100644 index 00000000000..fafc9be01bc --- /dev/null +++ b/examples/function_router/index.scss @@ -0,0 +1,27 @@ +.hero { + &.has-background { + position: relative; + overflow: hidden; + } + + &-background { + position: absolute; + object-fit: cover; + object-position: bottom; + width: 100%; + height: 100%; + + &.is-transparent { + opacity: 0.3; + } + } +} + +.burger { + background-color: transparent; + border: none; +} + +.navbar-brand { + align-items: center; +} diff --git a/examples/function_router/src/app.rs b/examples/function_router/src/app.rs new file mode 100644 index 00000000000..ce581a59bf4 --- /dev/null +++ b/examples/function_router/src/app.rs @@ -0,0 +1,118 @@ +use yew::prelude::*; +use yew_router::prelude::*; + +use crate::components::nav::Nav; +use crate::pages::{ + author::Author, author_list::AuthorList, home::Home, page_not_found::PageNotFound, post::Post, + post_list::PostList, +}; + +#[derive(Routable, PartialEq, Clone, Debug)] +pub enum Route { + #[at("/posts/:id")] + Post { id: u32 }, + #[at("/posts")] + Posts, + #[at("/authors/:id")] + Author { id: u32 }, + #[at("/authors")] + Authors, + #[at("/")] + Home, + #[not_found] + #[at("/404")] + NotFound, +} + +#[function_component] +pub fn App() -> Html { + html! { + +