From 92d9521a73aefb620b354ae5954617109dd06e7e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 7 Mar 2021 21:28:20 -0500 Subject: [PATCH 1/5] WIP: remove FC --- Cargo.toml | 2 +- README.md | 1 + packages/core-macro/Cargo.toml | 1 + packages/core-macro/examples/fc.rs | 3 + packages/core-macro/src/fc.rs | 223 ++++++++++++----------- packages/core-macro/src/ifmt.rs | 59 +++--- packages/core-macro/src/lib.rs | 38 ++-- packages/core-macro/src/rsxt.rs | 270 +++++++++++++++++++--------- packages/core-macro/src/util.rs | 137 ++++++++++++++ packages/core/examples/fc.rs | 41 +++++ packages/core/examples/rsx_usage.rs | 60 ++++--- packages/core/examples/step.rs | 2 +- packages/core/src/component.rs | 42 ++--- packages/core/src/debug_renderer.rs | 15 +- packages/core/src/diff.rs | 36 +--- packages/core/src/hooks.rs | 48 ++--- packages/core/src/lib.rs | 9 +- packages/core/src/nodebuilder.rs | 4 +- packages/core/src/nodes.rs | 53 ++++-- packages/core/src/scope.rs | 150 ++++++++-------- packages/core/src/virtual_dom.rs | 37 ++-- packages/dioxus/src/lib.rs | 2 +- packages/web/Cargo.toml | 1 + packages/web/examples/infer.rs | 18 +- 24 files changed, 801 insertions(+), 451 deletions(-) create mode 100644 packages/core-macro/examples/fc.rs create mode 100644 packages/core-macro/src/util.rs create mode 100644 packages/core/examples/fc.rs diff --git a/Cargo.toml b/Cargo.toml index 44d161884..0841d0b62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ members = [ "packages/dioxus", "packages/core-macro", "packages/core", - "packages/web", + # "packages/web", # "packages/router", # "packages/ssr", # "packages/webview", diff --git a/README.md b/README.md index 60b7d80d2..83bb39720 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Dioxus is a portable, performant, and ergonomic framework for building cross-pla ```rust +#[fc] static Example: FC<()> = |ctx, props| { let (selection, set_selection) = use_state(&ctx, || "...?"); diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index fe1de60f3..7a2b6b574 100644 --- a/packages/core-macro/Cargo.toml +++ b/packages/core-macro/Cargo.toml @@ -12,6 +12,7 @@ description = "Core macro for Dioxus Virtual DOM" proc-macro = true [dependencies] +once_cell = "1.7.2" proc-macro-hack = "0.5.19" proc-macro2 = "1.0.6" quote = "1.0" diff --git a/packages/core-macro/examples/fc.rs b/packages/core-macro/examples/fc.rs new file mode 100644 index 000000000..033a454fb --- /dev/null +++ b/packages/core-macro/examples/fc.rs @@ -0,0 +1,3 @@ +use dioxus_core_macro::fc; + +fn main() {} diff --git a/packages/core-macro/src/fc.rs b/packages/core-macro/src/fc.rs index c7b87ce62..055efd38f 100644 --- a/packages/core-macro/src/fc.rs +++ b/packages/core-macro/src/fc.rs @@ -9,80 +9,13 @@ use syn::{ parse_macro_input, Attribute, Block, FnArg, Ident, Item, ItemFn, ReturnType, Type, Visibility, }; -pub fn function_component_impl( - // name: FunctionComponentName, - component: FunctionComponent, -) -> syn::Result { - // let FunctionComponentName { component_name } = name; - - let FunctionComponent { - block, - props_type, - arg, - vis, - attrs, - name: function_name, - return_type, - } = component; - - // if function_name == component_name { - // return Err(syn::Error::new_spanned( - // component_name, - // "the component must not have the same name as the function", - // )); - // } - - let quoted = quote! { - #[doc(hidden)] - #[allow(non_camel_case_types)] - mod __component_blah { - use super::*; - - #[derive(PartialEq)] - pub struct Props<'a> { - name: &'a str - } - - pub fn component<'a>(ctx: &'a Context<'a, Props>) -> VNode<'a> { - // Destructure the props into the parent scope - // todo: handle expansion of lifetimes - let Props { - name - } = ctx.props; - - #block - } - } - #[allow(non_snake_case)] - pub use __component_blah::component as #function_name; - }; - // let quoted = quote! { - // #[doc(hidden)] - // #[allow(non_camel_case_types)] - // #vis struct #function_name; - - // impl ::yew_functional::FunctionProvider for #function_name { - // type TProps = #props_type; - - // fn run(#arg) -> #ret_type { - // #block - // } - // } - - // #(#attrs)* - // #vis type #component_name = ::yew_functional::FunctionComponent<#function_name>; - // }; - Ok(quoted) -} - /// A parsed version of the user's input pub struct FunctionComponent { // The actual contents of the function block: Box, - // The user's props type - props_type: Box, - + // // The user's props type + // props_type: Box, arg: FnArg, vis: Visibility, attrs: Vec, @@ -117,7 +50,7 @@ impl Parse for FunctionComponent { .unwrap_or_else(|| syn::parse_quote! { _: &() }); // Extract the "context" object - let props_type = validate_context_arg(&first_arg)?; + // let props_type = validate_context_arg(&first_arg)?; /* Extract the rest of the function arguments into a struct body @@ -159,7 +92,7 @@ impl Parse for FunctionComponent { let name = sig.ident; Ok(Self { - props_type, + // props_type, block, arg: first_arg, vis, @@ -169,6 +102,89 @@ impl Parse for FunctionComponent { }) } } +impl ToTokens for FunctionComponent { + fn to_tokens(&self, tokens: &mut TokenStream) { + // let FunctionComponentName { component_name } = name; + + let FunctionComponent { + block, + // props_type, + arg, + vis, + attrs, + name: function_name, + return_type, + } = self; + + // if function_name == component_name { + // return Err(syn::Error::new_spanned( + // component_name, + // "the component must not have the same name as the function", + // )); + // } + + let quoted = quote! { + + + #[doc(hidden)] + #[allow(non_camel_case_types)] + #[derive(PartialEq)] + pub struct #function_name<'a> { + // and some other attrs + ___p: std::marker::PhantomData<&'a ()> + } + + impl<'a> FC for #function_name<'a> { + fn render(ctx: Context<'_>, props: &#function_name<'a>) -> DomTree { + let #function_name { + .. + } = props; + + #block + } + } + + // mod __component_blah { + // use super::*; + + // #[derive(PartialEq)] + // pub struct Props<'a> { + // name: &'a str + // } + + // pub fn component<'a>(ctx: &'a Context<'a, Props>) -> VNode<'a> { + // // Destructure the props into the parent scope + // // todo: handle expansion of lifetimes + // let Props { + // name + // } = ctx.props; + + // #block + // } + // } + // #[allow(non_snake_case)] + // pub use __component_blah::component as #function_name; + }; + + quoted.to_tokens(tokens); + // let quoted = quote! { + // #[doc(hidden)] + // #[allow(non_camel_case_types)] + // #vis struct #function_name; + + // impl ::yew_functional::FunctionProvider for #function_name { + // type TProps = #props_type; + + // fn run(#arg) -> #ret_type { + // #block + // } + // } + + // #(#attrs)* + // #vis type #component_name = ::yew_functional::FunctionComponent<#function_name>; + // }; + } +} /// Ensure the user's input is actually a functional component pub fn ensure_fn_block(item: Item) -> syn::Result { @@ -228,7 +244,7 @@ pub fn ensure_return_type(output: ReturnType) -> syn::Result> { match output { ReturnType::Default => Err(syn::Error::new_spanned( output, - "function components must return `dioxus::VNode`", + "function components must return a `DomTree`", )), ReturnType::Type(_, ty) => Ok(ty), } @@ -268,39 +284,40 @@ pub fn validate_signature(sig: Signature) -> syn::Result { Ok(sig) } -pub fn validate_context_arg(first_arg: &FnArg) -> syn::Result> { - if let FnArg::Typed(arg) = first_arg { - // Input arg is a reference to an &mut Context - if let Type::Reference(ty) = &*arg.ty { - if ty.lifetime.is_some() { - return Err(syn::Error::new_spanned( - &ty.lifetime, - "reference must not have a lifetime", - )); - } +// pub fn validate_context_arg(first_arg: &FnArg) -> syn::Result> { +// if let FnArg::Typed(arg) = first_arg { +// // if let Type::R +// // Input arg is a reference to an &mut Context +// // if let Type::Reference(ty) = &*arg.ty { +// // if ty.lifetime.is_some() { +// // return Err(syn::Error::new_spanned( +// // &ty.lifetime, +// // "reference must not have a lifetime", +// // )); +// // } - if ty.mutability.is_some() { - return Err(syn::Error::new_spanned( - &ty.mutability, - "reference must not be mutable", - )); - } +// // if ty.mutability.is_some() { +// // return Err(syn::Error::new_spanned( +// // &ty.mutability, +// // "reference must not be mutable", +// // )); +// // } - Ok(ty.elem.clone()) - } else { - let msg = format!( - "expected a reference to a `Context` object (try: `&mut {}`)", - arg.ty.to_token_stream() - ); - return Err(syn::Error::new_spanned(arg.ty.clone(), msg)); - } - } else { - return Err(syn::Error::new_spanned( - first_arg, - "function components can't accept a receiver", - )); - } -} +// // Ok(ty.elem.clone()) +// // } else { +// // let msg = format!( +// // "expected a reference to a `Context` object (try: `&mut {}`)", +// // arg.ty.to_token_stream() +// // ); +// // return Err(syn::Error::new_spanned(arg.ty.clone(), msg)); +// // } +// } else { +// return Err(syn::Error::new_spanned( +// first_arg, +// "function components can't accept a receiver", +// )); +// } +// } pub fn collect_inline_args() {} diff --git a/packages/core-macro/src/ifmt.rs b/packages/core-macro/src/ifmt.rs index d0e341eb3..816c1f400 100644 --- a/packages/core-macro/src/ifmt.rs +++ b/packages/core-macro/src/ifmt.rs @@ -1,4 +1,3 @@ -use ::proc_macro::TokenStream; use ::quote::{quote, ToTokens}; use ::std::ops::Not; use ::syn::{ @@ -6,10 +5,7 @@ use ::syn::{ punctuated::Punctuated, *, }; -use proc_macro2::TokenStream as TokenStream2; - -// #[macro_use] -// mod macros { +use proc_macro2::TokenStream; // #[cfg(not(feature = "verbose-expansions"))] macro_rules! debug_input { @@ -18,18 +14,6 @@ macro_rules! debug_input { }; } -// #[cfg(feature = "verbose-expansions")] -macro_rules! debug_input { - ($expr:expr) => { - match $expr { - expr => { - eprintln!("-------------------\n{} ! ( {} )", FUNCTION_NAME, expr); - expr - } - } - }; -} - // #[cfg(not(feature = "verbose-expansions"))] macro_rules! debug_output { ($expr:expr) => { @@ -38,19 +22,30 @@ macro_rules! debug_output { } // #[cfg(feature = "verbose-expansions")] -macro_rules! debug_output { - ($expr:expr) => { - match $expr { - expr => { - eprintln!("=>\n{}\n-------------------\n", expr); - expr - } - } - }; -} +// macro_rules! debug_input { +// ($expr:expr) => { +// match $expr { +// expr => { +// eprintln!("-------------------\n{} ! ( {} )", FUNCTION_NAME, expr); +// expr +// } +// } +// }; // } -pub fn format_args_f_impl(input: IfmtInput) -> TokenStream { +// #[cfg(feature = "verbose-expansions")] +// macro_rules! debug_output { +// ($expr:expr) => { +// match $expr { +// expr => { +// eprintln!("=>\n{}\n-------------------\n", expr); +// expr +// } +// } +// }; +// } + +pub fn format_args_f_impl(input: IfmtInput) -> Result { let IfmtInput { mut format_literal, mut positional_args, @@ -121,7 +116,7 @@ pub fn format_args_f_impl(input: IfmtInput) -> TokenStream { arg, ) { Ok(segments) => segments.into_iter().collect(), - Err(err) => return err.to_compile_error().into(), + Err(err) => return Err(err), } }; match segments.len() { @@ -151,7 +146,7 @@ pub fn format_args_f_impl(input: IfmtInput) -> TokenStream { format_args!("{}", positional_args.len()), ) .expect("`usize` or `char` Display impl cannot panic"); - let segments: Punctuated = segments + let segments: Punctuated = segments .into_iter() .map(|it| match it { Segment::Ident(ident) => ident.into_token_stream(), @@ -172,13 +167,13 @@ pub fn format_args_f_impl(input: IfmtInput) -> TokenStream { }); format_literal = LitStr::new(out_format_literal, format_literal.span()); - TokenStream::from(debug_output!(quote! { + Ok(TokenStream::from(debug_output!(quote! { format_args!( #format_literal #(, #positional_args)* #(, #named_args)* ) - })) + }))) } #[allow(dead_code)] // dumb compiler does not see the struct being used... diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index e9b24c0be..67669f873 100644 --- a/packages/core-macro/src/lib.rs +++ b/packages/core-macro/src/lib.rs @@ -6,6 +6,7 @@ mod fc; mod htm; mod ifmt; mod rsxt; +mod util; /// The html! macro makes it easy for developers to write jsx-style markup in their components. /// We aim to keep functional parity with html templates. @@ -27,31 +28,34 @@ pub fn rsx(s: TokenStream) -> TokenStream { } } +// #[proc_macro_attribute] +// pub fn fc(attr: TokenStream, item: TokenStream) -> TokenStream { + /// Label a function or static closure as a functional component. /// This macro reduces the need to create a separate properties struct. +/// +/// Using this macro is fun and simple +/// +/// ```ignore +/// +/// #[fc] +/// fn Example(ctx: Context, name: &str) -> DomTree { +/// ctx.render(rsx! { h1 {"hello {name}"} }) +/// } +/// ``` #[proc_macro_attribute] pub fn fc(attr: TokenStream, item: TokenStream) -> TokenStream { - use fc::{function_component_impl, FunctionComponent}; - - let item = parse_macro_input!(item as FunctionComponent); - - function_component_impl(item) - .unwrap_or_else(|err| err.to_compile_error()) - .into() + match syn::parse::(item) { + Err(e) => e.to_compile_error().into(), + Ok(s) => s.to_token_stream().into(), + } } #[proc_macro] pub fn format_args_f(input: TokenStream) -> TokenStream { use ifmt::*; - let item = parse_macro_input!(input as IfmtInput); - - // #[allow(unused)] - // const FUNCTION_NAME: &str = "format_args_f"; - - // debug_input!(&input); - - ifmt::format_args_f_impl(item) - // .unwrap_or_else(|err| err.to_compile_error()) - // .into() + format_args_f_impl(item) + .unwrap_or_else(|err| err.to_compile_error()) + .into() } diff --git a/packages/core-macro/src/rsxt.rs b/packages/core-macro/src/rsxt.rs index 61be92ef4..b5dd31cab 100644 --- a/packages/core-macro/src/rsxt.rs +++ b/packages/core-macro/src/rsxt.rs @@ -88,6 +88,7 @@ impl ToTokens for RsxRender { enum Node { Element(Element), Text(TextNode), + Component(Component), } impl ToTokens for ToToksCtx<&Node> { @@ -95,28 +96,29 @@ impl ToTokens for ToToksCtx<&Node> { match &self.inner { Node::Element(el) => self.recurse(el).to_tokens(tokens), Node::Text(txt) => self.recurse(txt).to_tokens(tokens), + Node::Component(c) => self.recurse(c).to_tokens(tokens), } } } -// impl Node { -// fn peek(s: ParseStream) -> bool { -// (s.peek(Token![<]) && !s.peek2(Token![/])) || s.peek(token::Brace) || s.peek(LitStr) -// } -// } - impl Parse for Node { fn parse(s: ParseStream) -> Result { - let fork = s.fork(); + let fork1 = s.fork(); + let fork2 = s.fork(); - let ret = if let Ok(text) = fork.parse::() { - s.advance_to(&fork); - Ok(Self::Text(text)) - } else if let Ok(el) = s.parse::() { - Ok(Self::Element(el)) + // todo: map issues onto the second fork if any arise + // it's unlikely that textnodes or components would fail? + + let ret = if let Ok(text) = fork1.parse::() { + s.advance_to(&fork1); + Self::Text(text) + } else if let Ok(element) = fork2.parse::() { + s.advance_to(&fork2); + Self::Element(element) + } else if let Ok(comp) = s.parse::() { + Self::Component(comp) } else { - // TODO: Span information - panic!("Not a valid child node"); + return Err(Error::new(s.span(), "Failed to parse as a valid child")); }; // consume comma if it exists @@ -124,7 +126,120 @@ impl Parse for Node { if s.peek(Token![,]) { let _ = s.parse::(); } - ret + Ok(ret) + } +} + +struct Component { + name: Ident, + body: Vec, + // attrs: Vec, + children: MaybeExpr>, +} + +impl ToTokens for ToToksCtx<&Component> { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let name = &self.inner.name; + + // let mut toks = quote! {}; + + // for attr in self.inner.attrs.iter() { + // self.recurse(attr).to_tokens(&mut toks); + // } + + let body = &self.inner.body; + // panic!("tokens are {:#?}", toks); + + // tokens.append_all(quote! { + // dioxus::builder::virtual_child(ctx, #name{ #body, ___p: PhantomData{} }) + // // dioxus::builder::ElementBuilder::new(ctx, #name) + // }); + + // no children right now + + // match &self.inner.children { + // MaybeExpr::Expr(expr) => tokens.append_all(quote! { + // .children(#expr) + // }), + // MaybeExpr::Literal(nodes) => { + // let mut children = nodes.iter(); + // if let Some(child) = children.next() { + // let mut inner_toks = TokenStream2::new(); + // self.recurse(child).to_tokens(&mut inner_toks); + // while let Some(child) = children.next() { + // quote!(,).to_tokens(&mut inner_toks); + // self.recurse(child).to_tokens(&mut inner_toks); + // } + // tokens.append_all(quote! { + // .children([#inner_toks]) + // }); + // } + // } + // } + // tokens.append_all(quote! { + // .finish() + // }); + } +} + +impl Parse for Component { + fn parse(s: ParseStream) -> Result { + // TODO: reject anything weird/nonstandard + // we want names *only* + let name = Ident::parse_any(s)?; + + if crate::util::is_valid_tag(&name.to_string()) { + return Err(Error::new( + name.span(), + "Components cannot share names with valid HTML tags", + )); + } + + // parse the guts + let content: ParseBuffer; + syn::braced!(content in s); + let mut fields: Vec = Vec::new(); + + 'parsing: loop { + // [1] Break if empty + if content.is_empty() { + break 'parsing; + } + } + + // let body = content.parse()?; + + // eventually we'll parse the attrs + // let mut attrs: Vec = vec![]; + let mut children: Vec = vec![]; + // parse_element_content(content, &mut attrs, &mut children); + + let children = MaybeExpr::Literal(children); + + Ok(Self { + name, + body, + // attrs, + children, + }) + } +} + +// the struct's fields info +pub struct Field { + name: Ident, + content: Ident, +} + +impl Parse for Field { + fn parse(input: ParseStream) -> Result { + todo!() + } +} + +impl ToTokens for ToToksCtx<&Field> { + fn to_tokens(&self, tokens: &mut TokenStream2) { + todo!() } } @@ -142,12 +257,12 @@ struct Element { impl ToTokens for ToToksCtx<&Element> { fn to_tokens(&self, tokens: &mut TokenStream2) { - // todo!() - // // let ctx = self.ctx; let name = &self.inner.name.to_string(); + tokens.append_all(quote! { dioxus::builder::ElementBuilder::new(ctx, #name) }); + for attr in self.inner.attrs.iter() { self.recurse(attr).to_tokens(tokens); } @@ -182,84 +297,20 @@ impl Parse for Element { // we want names *only* let name = Ident::parse_any(s)?; + if !crate::util::is_valid_tag(&name.to_string()) { + return Err(Error::new(name.span(), "Not a valid Html tag")); + } + // parse the guts let content: ParseBuffer; syn::braced!(content in s); - let mut attrs = vec![]; + let mut attrs: Vec = vec![]; let mut children: Vec = vec![]; - - 'parsing: loop { - // todo move this around into a more functional style - // [1] Break if empty - // [2] Try to consume an attr (with comma) - // [3] Try to consume a child node (with comma) - // [4] Try to consume brackets as anything thats Into - // [last] Fail if none worked - - // [1] Break if empty - if content.is_empty() { - break 'parsing; - } - - // [2] Try to consume an attr - let fork = content.fork(); - if let Ok(attr) = fork.parse::() { - // make sure to advance or your computer will become a spaceheater :) - content.advance_to(&fork); - attrs.push(attr); - continue 'parsing; - } - - // [3] Try to consume a child node - let fork = content.fork(); - if let Ok(node) = fork.parse::() { - // make sure to advance or your computer will become a spaceheater :) - content.advance_to(&fork); - children.push(node); - continue 'parsing; - } - - // [4] TODO: Parsing brackets - // let fork = content.fork(); - // if let Ok(el) = fork.parse() { - // children.push(el); - // continue 'parsing; - // } - - // todo: pass a descriptive error onto the offending tokens - panic!("Entry is not an attr or element\n {}", content) - } + parse_element_content(content, &mut attrs, &mut children); let children = MaybeExpr::Literal(children); - // let children = MaybeExpr::Literal(Vec::new()); - // // Contents of an element can either be a brace (in which case we just copy verbatim), or a - // // sequence of nodes. - // let children = if s.peek(token::Brace) { - // // expr - // let content; - // syn::braced!(content in s); - // MaybeExpr::Expr(content.parse()?) - // } else { - // // nodes - // let mut children = vec![]; - // while !(s.peek(Token![<]) && s.peek2(Token![/])) { - // children.push(s.parse()?); - // } - // MaybeExpr::Literal(children) - // }; - // // closing element - // s.parse::()?; - // s.parse::()?; - // let close = Ident::parse_any(s)?; - // if close.to_string() != name.to_string() { - // return Err(Error::new_spanned( - // close, - // "closing element does not match opening", - // )); - // } - // s.parse::]>()?; Ok(Self { name, attrs, @@ -268,6 +319,51 @@ impl Parse for Element { } } +// used by both vcomponet and velement to parse the contents of the elements into attras and children +fn parse_element_content(content: ParseBuffer, attrs: &mut Vec, children: &mut Vec) { + 'parsing: loop { + // todo move this around into a more functional style + // [1] Break if empty + // [2] Try to consume an attr (with comma) + // [3] Try to consume a child node (with comma) + // [4] Try to consume brackets as anything thats Into + // [last] Fail if none worked + + // [1] Break if empty + if content.is_empty() { + break 'parsing; + } + + // [2] Try to consume an attr + let fork = content.fork(); + if let Ok(attr) = fork.parse::() { + // make sure to advance or your computer will become a spaceheater :) + content.advance_to(&fork); + attrs.push(attr); + continue 'parsing; + } + + // [3] Try to consume a child node + let fork = content.fork(); + if let Ok(node) = fork.parse::() { + // make sure to advance or your computer will become a spaceheater :) + content.advance_to(&fork); + children.push(node); + continue 'parsing; + } + + // [4] TODO: Parsing brackets + // let fork = content.fork(); + // if let Ok(el) = fork.parse() { + // children.push(el); + // continue 'parsing; + // } + + // todo: pass a descriptive error onto the offending tokens + panic!("Entry is not an attr or element\n {}", content) + } +} + /// ======================================= /// Parse a VElement's Attributes /// ======================================= diff --git a/packages/core-macro/src/util.rs b/packages/core-macro/src/util.rs new file mode 100644 index 000000000..f3d26e1b9 --- /dev/null +++ b/packages/core-macro/src/util.rs @@ -0,0 +1,137 @@ +// use lazy_static::lazy_static; +use once_cell::sync::Lazy; +use std::collections::hash_set::HashSet; + +static VALID_TAGS: Lazy> = Lazy::new(|| { + [ + "a", + "abbr", + "address", + "area", + "article", + "aside", + "audio", + "b", + "base", + "bdi", + "bdo", + "big", + "blockquote", + "body", + "br", + "button", + "canvas", + "caption", + "cite", + "code", + "col", + "colgroup", + "command", + "data", + "datalist", + "dd", + "del", + "details", + "dfn", + "dialog", + "div", + "dl", + "dt", + "em", + "embed", + "fieldset", + "figcaption", + "figure", + "footer", + "form", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "head", + "header", + "hr", + "html", + "i", + "iframe", + "img", + "input", + "ins", + "kbd", + "keygen", + "label", + "legend", + "li", + "link", + "main", + "map", + "mark", + "menu", + "menuitem", + "meta", + "meter", + "nav", + "noscript", + "object", + "ol", + "optgroup", + "option", + "output", + "p", + "param", + "picture", + "pre", + "progress", + "q", + "rp", + "rt", + "ruby", + "s", + "samp", + "script", + "section", + "select", + "small", + "source", + "span", + "strong", + "style", + "sub", + "summary", + "sup", + "table", + "tbody", + "td", + "textarea", + "tfoot", + "th", + "thead", + "time", + "title", + "tr", + "track", + "u", + "ul", + "var", + "video", + "wbr", + ] + .iter() + .cloned() + .collect() +}); + +/// Whether or not this tag is valid +/// +/// ``` +/// use html_validation::is_valid_tag; +/// +/// assert_eq!(is_valid_tag("br"), true); +/// +/// assert_eq!(is_valid_tag("random"), false); +/// ``` +pub fn is_valid_tag(tag: &str) -> bool { + VALID_TAGS.contains(tag) +} diff --git a/packages/core/examples/fc.rs b/packages/core/examples/fc.rs new file mode 100644 index 000000000..4e0bd8f95 --- /dev/null +++ b/packages/core/examples/fc.rs @@ -0,0 +1,41 @@ +use dioxus_core::prelude::*; +use dioxus_core::scope::FC; +use dioxus_core_macro::fc; + +use std::marker::PhantomData; +// #[derive(PartialEq)] +// pub struct Example<'a> { +// b: &'a str, +// ___p: std::marker::PhantomData<&'a ()>, +// } + +// impl<'a> FC for Example<'a> { +// fn render(ctx: Context<'_>, props: &Example<'a>) -> DomTree { +// let Example { b, .. } = props; +// { +// ctx.render(rsx! { +// div { "abcd {b}" } +// }) +// } +// } +// } + +// always try to fill in with Default + +#[fc] +fn Example(ctx: Context, a: &str, b: &str, c: &str) -> DomTree { + ctx.render(rsx! { + div { + SomeComponent { + a: "123" + } + } + }) +} + +#[fc] +fn SomeComponent(ctx: Context, a: &str, b: &str) -> DomTree { + todo!() +} + +fn main() {} diff --git a/packages/core/examples/rsx_usage.rs b/packages/core/examples/rsx_usage.rs index 7ed471a69..4001fc29c 100644 --- a/packages/core/examples/rsx_usage.rs +++ b/packages/core/examples/rsx_usage.rs @@ -3,33 +3,53 @@ use dioxus_core::prelude::*; fn main() {} +trait SProps {} + +trait Comp { + type Props; +} + +impl Comp for FC { + type Props = T; +} + +fn test() { + // let g: FC = CustomButton; +} + +trait Render { + fn render(ctx: Context, props: &Self) -> DomTree; +} // include as much as you might accept -struct ButtonProps<'a> { +struct Button<'a> { onhover: Option<&'a dyn Fn()>, - // // A list of any attrs - // attrs: AttrList<'a>, } -fn CustomButton(ctx: Context, props: ButtonProps) -> DomTree { - let onfocus = move |evt: ()| log::debug!("Focused"); +impl Render for Button<'_> { + fn render(ctx: Context, props: &Self) -> DomTree { + let onfocus = move |evt: ()| log::debug!("Focused"); - // todo!() - ctx.render(rsx! { - button { - // ..props.attrs, - class: "abc123", - // style: { a: 2, b: 3, c: 4 }, - onclick: move |evt| { - // log::info("hello world"); - }, - // Custom1 { a: 123 } - // Custom2 { a: 456, "abc", h1 {"1"}, h2 {"2"} } - // Custom3 { a: "sometext goes here" } - // Custom4 { onclick: |evt| log::info("click") } - } - }) + // todo!() + ctx.render(rsx! { + button { + // ..props.attrs, + class: "abc123", + // style: { a: 2, b: 3, c: 4 }, + onclick: move |evt| { + // log::info("hello world"); + }, + // Custom1 { a: 123 } + // Custom2 { a: 456, "abc", h1 {"1"}, h2 {"2"} } + // Custom3 { a: "sometext goes here" } + // Custom4 { onclick: |evt| log::info("click") } + } + }) + } } +// #[fc] +// fn Button(ctx: Context, onhover: Option<&dyn Fn()>) -> DomTree {} + // h1 { // tag: "type", abc: 123, class: "big small wide short", // "title1" diff --git a/packages/core/examples/step.rs b/packages/core/examples/step.rs index a80951f6a..2b173345e 100644 --- a/packages/core/examples/step.rs +++ b/packages/core/examples/step.rs @@ -4,7 +4,7 @@ fn main() -> Result<(), ()> { let p1 = Props { name: "bob".into() }; let mut vdom = VirtualDom::new_with_props(Example, p1); - vdom.update_props(|p: &mut Props| {}); + // vdom.update_props(|p: &mut Props| {}); Ok(()) } diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index 8dc0b69a1..6b8fe0173 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -39,31 +39,31 @@ mod tests { use crate::prelude::bumpalo::Bump; use crate::prelude::*; - fn test_static_fn<'a, P>(b: &'a Bump, r: FC

) -> VNode<'a> { - todo!() - } + // fn test_static_fn<'a, P>(b: &'a Bump, r: FC

) -> VNode<'a> { + // todo!() + // } - static TestComponent: FC<()> = |ctx, props| { - // + // static TestComponent: FC<()> = |ctx, props| { + // // - ctx.render(html! { -

+ // ctx.render(html! { + //
-
- }) - }; + //
+ // }) + // }; - static TestComponent2: FC<()> = |ctx, props| { - // - ctx.render(|ctx| VNode::text("blah")) - }; + // static TestComponent2: FC<()> = |ctx, props| { + // // + // ctx.render(|ctx| VNode::text("blah")) + // }; - #[test] - fn ensure_types_work() { - let bump = Bump::new(); + // #[test] + // fn ensure_types_work() { + // let bump = Bump::new(); - // Happiness! The VNodes are now allocated onto the bump vdom - let _ = test_static_fn(&bump, TestComponent); - let _ = test_static_fn(&bump, TestComponent2); - } + // // Happiness! The VNodes are now allocated onto the bump vdom + // let _ = test_static_fn(&bump, TestComponent); + // let _ = test_static_fn(&bump, TestComponent2); + // } } diff --git a/packages/core/src/debug_renderer.rs b/packages/core/src/debug_renderer.rs index 0d08e8db7..5acc1543d 100644 --- a/packages/core/src/debug_renderer.rs +++ b/packages/core/src/debug_renderer.rs @@ -25,15 +25,20 @@ impl DebugRenderer { mod tests { use super::*; use crate::prelude::*; + use crate::scope::FC; #[test] fn ensure_creation() -> Result<(), ()> { - let mut dom = VirtualDom::new(|ctx, props| { - // - ctx.render(html! {
"hello world"
}) - }); + #[derive(PartialEq)] + struct Creation {} + impl FC for Creation { + fn render(ctx: Context, props: &Self) -> DomTree { + ctx.render(html! {
"hello world"
}) + } + } + + let mut dom = VirtualDom::new_with_props(Creation {}); - // dom.progress()?; Ok(()) } } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 2199f7755..936568145 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -53,23 +53,19 @@ use std::{cell::RefCell, cmp::Ordering, collections::VecDeque, rc::Rc}; /// The order of these re-entrances is stored in the DiffState itself. The DiffState comes pre-loaded with a set of components /// that were modified by the eventtrigger. This prevents doubly evaluating components if they wereboth updated via /// subscriptions and props changes. -pub struct DiffMachine<'a, 'b> { +pub struct DiffMachine<'a> { pub change_list: EditMachine<'a>, - pub vdom: &'b VirtualDom, - pub cur_idx: ScopeIdx, pub diffed: FxHashSet, pub need_to_diff: FxHashSet, } -impl<'a, 'b> DiffMachine<'a, 'b> { - pub fn new(vdom: &'b VirtualDom, bump: &'a Bump, idx: ScopeIdx) -> Self { +impl<'a> DiffMachine<'a> { + pub fn new(bump: &'a Bump) -> Self { Self { - cur_idx: idx, change_list: EditMachine::new(bump), diffed: FxHashSet::default(), need_to_diff: FxHashSet::default(), - vdom, } } @@ -118,10 +114,10 @@ impl<'a, 'b> DiffMachine<'a, 'b> { } (VNode::Component(cold), VNode::Component(cnew)) => { - if cold.comp != cnew.comp { - // queue an event to mount this new component - return; - } + // if cold.comp != cnew.comp { + // // queue an event to mount this new component + // return; + // } // compare props.... somehow..... @@ -843,23 +839,9 @@ impl<'a, 'b> DiffMachine<'a, 'b> { } listeners.iter().enumerate().for_each(|(id, listener)| { - // if let Some(index) = self.current_idx { - self.change_list.new_event_listener( - listener.event, - listener.scope, - listener.id, - ); - // } else { - // Don't panic - // Used for testing - // log::trace!("Failed to set listener, create was not called in the context of the virtual dom"); - // } + self.change_list + .new_event_listener(listener.event, listener.scope, listener.id) }); - // for l in listeners { - // unsafe { - // registry.add(l); - // } - // } for attr in attributes { self.change_list diff --git a/packages/core/src/hooks.rs b/packages/core/src/hooks.rs index 3bd36db23..c03c99bff 100644 --- a/packages/core/src/hooks.rs +++ b/packages/core/src/hooks.rs @@ -183,30 +183,30 @@ mod use_reducer_def { Decr, } - #[allow(unused)] - static Example: FC<()> = |ctx, props| { - let (count, reduce) = use_reducer( - &ctx, - || 0, - |count, action| match action { - Actions::Incr => *count += 1, - Actions::Decr => *count -= 1, - }, - ); + // #[allow(unused)] + // static Example: FC<()> = |ctx, props| { + // let (count, reduce) = use_reducer( + // &ctx, + // || 0, + // |count, action| match action { + // Actions::Incr => *count += 1, + // Actions::Decr => *count -= 1, + // }, + // ); - ctx.render(rsx! { - div { - h1 {"Count: {count}"} - button { - "Increment" - onclick: move |_| reduce(Actions::Incr) - } - button { - "Decrement" - onclick: move |_| reduce(Actions::Decr) - } - } - }) - }; + // ctx.render(rsx! { + // div { + // h1 {"Count: {count}"} + // button { + // "Increment" + // onclick: move |_| reduce(Actions::Incr) + // } + // button { + // "Decrement" + // onclick: move |_| reduce(Actions::Decr) + // } + // } + // }) + // }; } } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 8748a0146..bbaf4a160 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -106,7 +106,7 @@ pub(crate) mod innerlude { // pub use nodes::iterables::IterableNodes; /// This type alias is an internal way of abstracting over the static functions that represent components. - pub type FC

= for<'scope> fn(Context<'scope>, &'scope P) -> DomTree; + // pub type FC

= for<'scope> fn(Context<'scope>, &'scope P) -> DomTree; mod fc2 {} // pub type FC<'a, P: 'a> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree; @@ -122,7 +122,8 @@ pub(crate) mod innerlude { // Re-export the FC macro pub use crate as dioxus; pub use crate::nodebuilder as builder; - pub use dioxus_core_macro::{fc, html, rsx}; + pub use dioxus_core_macro::{html, rsx}; + // pub use dioxus_core_macro::{fc, html, rsx}; } /// Re-export common types for ease of development use. @@ -136,7 +137,7 @@ pub mod prelude { // pub use nodes::iterables::IterableNodes; /// This type alias is an internal way of abstracting over the static functions that represent components. - pub use crate::innerlude::FC; + // pub use crate::innerlude::FC; // TODO @Jon, fix this // hack the VNode type until VirtualNode is fixed in the macro crate @@ -151,7 +152,7 @@ pub mod prelude { pub use crate::nodebuilder as builder; // pub use dioxus_core_macro::fc; pub use dioxus_core_macro::format_args_f; - pub use dioxus_core_macro::{fc, html, rsx}; + pub use dioxus_core_macro::{html, rsx}; pub use crate::component::ScopeIdx; pub use crate::diff::DiffMachine; diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index 6cba5f135..22b72645e 100644 --- a/packages/core/src/nodebuilder.rs +++ b/packages/core/src/nodebuilder.rs @@ -524,7 +524,9 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> { // } // } -pub fn virtual_child<'a, T>(_bump: &'a Bump, _props: T, _f: crate::innerlude::FC) -> VNode<'a> { +// _f: crate::innerlude::FC, +// _props: T +pub fn virtual_child<'a, T: crate::scope::FC>(ctx: &'a NodeCtx<'a>, p: T) -> VNode<'a> { todo!() // VNode::Component() } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 8b04e95ae..9075e3b06 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -296,8 +296,16 @@ mod vtext { /// Virtual Components for custom user-defined components /// Only supports the functional syntax mod vcomponent { - use crate::innerlude::{Context, ScopeIdx, FC}; - use std::{any::TypeId, cell::RefCell, marker::PhantomData, rc::Rc}; + use crate::{ + innerlude::{Context, ScopeIdx}, + scope::FC, + }; + use std::{ + any::{Any, TypeId}, + cell::RefCell, + marker::PhantomData, + rc::Rc, + }; use super::DomTree; @@ -306,16 +314,18 @@ mod vcomponent { #[derive(Debug)] pub struct VComponent<'src> { _p: PhantomData<&'src ()>, - pub(crate) props: Box, - pub(crate) props_type: TypeId, - pub(crate) comp: *const (), - pub(crate) caller: Caller, - + // pub(crate) props: Box, + // pub(crate) props_type: TypeId, + // pub(crate) comp: *const (), + // pub(crate) caller: Caller, + pub(crate) comparator: Comparator, // once a component gets mounted, its parent gets a stable address. // this way we can carry the scope index from between renders // genius, really! - pub assigned_scope: StableScopeAddres, + // pub assigned_scope: StableScopeAddres, } + pub struct Comparator(Box bool>); + // pub struct Comparator<'src>(&'src dyn Fn(&dyn Any) -> bool); pub struct Caller(Box DomTree>); impl std::fmt::Debug for Caller { @@ -324,12 +334,35 @@ mod vcomponent { } } + impl std::fmt::Debug for Comparator { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } + } + impl<'a> VComponent<'a> { // use the type parameter on props creation and move it into a portable context // this lets us keep scope generic *and* downcast its props when we need to: // - perform comparisons when diffing (memoization) // - - pub fn new

(comp: FC

, props: P) -> Self { + pub fn new(comp: P) -> Self { + todo!() + // let p = Rc::new(props); + + // let props_comparator = move |new_props: &dyn Any| -> bool { + // new_props + // .downcast_ref::

() + // .map(|new_p| p.as_ref() == new_p) + // .unwrap_or_else(|| { + // log::debug!("downcasting failed, this receovered but SHOULD NOT FAIL"); + // false + // }) + // }; + + // Self { + // _p: PhantomData, + // comparator: Comparator(Box::new(props_comparator)), + // } // let caller = move |ctx: Context| { // let t = comp(ctx, &props); // t @@ -355,7 +388,7 @@ mod vcomponent { // } // } - todo!() + // todo!() // Self { // _p: PhantomData {}, // props, diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 7de06a73e..a7e43aff7 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -11,9 +11,9 @@ use std::{ ops::Deref, }; -pub trait Properties: PartialEq {} -// just for now -impl Properties for T {} +pub trait FC: PartialEq { + fn render(ctx: Context, props: &Self) -> DomTree; +} pub trait Scoped { fn run(&mut self); @@ -30,15 +30,13 @@ pub trait Scoped { /// /// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components. /// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently. -pub struct Scope { +pub struct Scope { // Map to the parent pub parent: Option, // our own index pub myidx: ScopeIdx, - pub caller: FC

, - pub props: P, // ========================== @@ -63,8 +61,7 @@ pub struct Scope { // instead of having it as a trait method, we use a single function // todo: do the unsafety magic stuff to erase the type of p -pub fn create_scoped( - caller: FC

, +pub fn create_scoped( props: P, myidx: ScopeIdx, parent: Option, @@ -90,7 +87,6 @@ pub fn create_scoped( myidx, hook_arena, hooks, - caller, frames, listeners, parent, @@ -98,7 +94,7 @@ pub fn create_scoped( }) } -impl Scoped for Scope

{ +impl Scoped for Scope

{ /// Create a new context and run the component with references from the Virtual Dom /// This function downcasts the function pointer based on the stored props_type /// @@ -125,7 +121,8 @@ impl Scoped for Scope

{ // Note that the actual modification of the vnode head element occurs during this call // let _: DomTree = caller(ctx, props); - let _: DomTree = (self.caller)(ctx, &self.props); + let _: DomTree = P::render(ctx, &self.props); + // let _: DomTree = (self.caller)(ctx, &self.props); /* SAFETY ALERT @@ -264,90 +261,89 @@ mod tests { use super::*; use crate::prelude::*; - static ListenerTest: FC<()> = |ctx, props| { - ctx.render(html! { -

- "hello" -
- }) - }; + // static ListenerTest: FC<()> = |ctx, props| { + // ctx.render(html! { + //
+ // "hello" + //
+ // }) + // }; #[test] fn test_scope() { - let example: FC<()> = |ctx, props| { - use crate::builder::*; - ctx.render(|ctx| { - builder::ElementBuilder::new(ctx, "div") - .child(text("a")) - .finish() - }) - }; + #[derive(PartialEq)] + struct Example {} + impl FC for Example { + fn render(ctx: Context<'_>, _: &Self) -> DomTree { + use crate::builder::*; + ctx.render(|ctx| { + builder::ElementBuilder::new(ctx, "div") + .child(text("a")) + .finish() + }) + } + } - let props = (); - let parent = None; let mut nodes = generational_arena::Arena::new(); nodes.insert_with(|myidx| { - let scope = create_scoped(example, props, myidx, parent); + let scope = create_scoped(Example {}, myidx, None); }); } - #[derive(Debug)] - struct ExampleProps<'src> { - name: &'src String, - } - - #[derive(Debug)] - struct EmptyProps<'src> { - name: &'src String, - } - use crate::{builder::*, hooks::use_ref}; - fn example_fc<'a>(ctx: Context<'a>, props: &'a EmptyProps) -> DomTree { - let (content, _): (&'a String, _) = crate::hooks::use_state(&ctx, || "abcd".to_string()); - - let childprops: ExampleProps<'a> = ExampleProps { name: content }; - ctx.render(move |c| { - builder::ElementBuilder::new(c, "div") - .child(text(props.name)) - .child(virtual_child::( - c.bump, - childprops, - child_example, - )) - .finish() - }) + #[derive(Debug, PartialEq)] + struct EmptyProps<'src> { + name: &'src String, } - fn child_example<'b>(ctx: Context<'b>, props: &'b ExampleProps) -> DomTree { - ctx.render(move |ctx| { - builder::ElementBuilder::new(ctx, "div") - .child(text(props.name)) - .finish() - }) + impl FC for EmptyProps<'_> { + fn render(ctx: Context, props: &Self) -> DomTree { + let (content, _): (&String, _) = crate::hooks::use_state(&ctx, || "abcd".to_string()); + + let childprops: ExampleProps<'_> = ExampleProps { name: content }; + todo!() + // ctx.render(move |c| { + // builder::ElementBuilder::new(c, "div") + // .child(text(props.name)) + // .child(virtual_child(c, childprops)) + // .finish() + // }) + } } - static CHILD: FC = |ctx, props: &'_ ExampleProps| { - ctx.render(move |ctx| { - builder::ElementBuilder::new(ctx, "div") - .child(text(props.name)) - .finish() - }) - }; + #[derive(Debug, PartialEq)] + struct ExampleProps<'src> { + name: &'src String, + } + + impl FC for ExampleProps<'_> { + fn render(ctx: Context, props: &Self) -> DomTree { + ctx.render(move |ctx| { + builder::ElementBuilder::new(ctx, "div") + .child(text(props.name)) + .finish() + }) + } + } #[test] fn test_borrowed_scope() { - let example: FC = |ctx, props| { - ctx.render(move |b| { - builder::ElementBuilder::new(b, "div") - .child(virtual_child( - b.bump, - ExampleProps { name: props.name }, - CHILD, - )) - .finish() - }) - }; + #[derive(Debug, PartialEq)] + struct Example { + name: String, + } + + impl FC for Example { + fn render(ctx: Context, props: &Self) -> DomTree { + todo!() + // ctx.render(move |c| { + // builder::ElementBuilder::new(c, "div") + // .child(virtual_child(c, ExampleProps { name: &props.name })) + // .finish() + // }) + } + } let source_text = "abcd123".to_string(); let props = ExampleProps { name: &source_text }; diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 10b1d2d95..909193d28 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -1,6 +1,6 @@ // use crate::{changelist::EditList, nodes::VNode}; -use crate::{innerlude::*, scope::Properties}; +use crate::{innerlude::*, scope::FC}; use crate::{ patch::Edit, scope::{create_scoped, Scoped}, @@ -60,8 +60,9 @@ impl VirtualDom { /// /// This means that the root component must either consumes its own context, or statics are used to generate the page. /// The root component can access things like routing in its context. - pub fn new(root: FC<()>) -> Self { - Self::new_with_props(root, ()) + pub fn new(root: impl Fn(Context) -> DomTree) -> Self { + todo!() + // Self::new_with_props(root) } /// Start a new VirtualDom instance with a dependent props. @@ -69,13 +70,13 @@ impl VirtualDom { /// /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive /// to toss out the entire tree. - pub fn new_with_props(root: FC

, root_props: P) -> Self { - let mut components = Arena::new(); + pub fn new_with_props(root: P) -> Self { + // let mut components = Arena::new(); // Create a reference to the component in the arena // Note: we are essentially running the "Mount" lifecycle event manually while the vdom doesnt yet exist // This puts the dom in a usable state on creation, rather than being potentially invalid - let base_scope = components.insert_with(|id| create_scoped(root, root_props, id, None)); + // let base_scope = components.insert_with(|id| create_scoped(root, root_props, id, None)); todo!() // Self { @@ -106,19 +107,16 @@ impl VirtualDom { let b = Bump::new(); - let mut diff_machine = DiffMachine::new(self, &b, self.base_scope); + let mut diff_machine = DiffMachine::new(&self.diff_bump); // let mut diff_machine = DiffMachine::new(self, &self.diff_bump, self.base_scope); - todo!() - // let component = self.components.get(self.base_scope).unwrap(); - - // diff_machine.diff_node(component.old_frame(), component.new_frame()); - - // let edits = diff_machine.consume(); + // todo!() + let component = self.components.get(self.base_scope).unwrap(); + diff_machine.diff_node(component.old_frame(), component.new_frame()); + let edits = diff_machine.consume(); // self.diff_bump = b; - - // Ok(edits) + Ok(edits) } /// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered. @@ -187,6 +185,9 @@ impl VirtualDom { } } -// struct LockedEdits<'src> { -// edits: -// } +enum LifeCycleEvent { + // Mount { +// props: &dyn Properties, +// // f: FC, +// }, +} diff --git a/packages/dioxus/src/lib.rs b/packages/dioxus/src/lib.rs index cb1b61434..5706e406b 100644 --- a/packages/dioxus/src/lib.rs +++ b/packages/dioxus/src/lib.rs @@ -3,7 +3,7 @@ pub mod prelude { pub use dioxus_core_macro::fc; } -use dioxus_core::prelude::FC; +// use dioxus_core::prelude::FC; // Re-export core completely pub use dioxus_core as core; diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index b5f276723..23f183621 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -22,6 +22,7 @@ pretty_env_logger = "0.4.0" console_error_panic_hook = "0.1.6" generational-arena = "0.2.8" wasm-bindgen-test = "0.3.21" +once_cell = "1.7.2" # html-validation = { path = "../html-validation", version = "0.1.1" } [dependencies.web-sys] diff --git a/packages/web/examples/infer.rs b/packages/web/examples/infer.rs index b1eb91ad0..fa9cd1529 100644 --- a/packages/web/examples/infer.rs +++ b/packages/web/examples/infer.rs @@ -9,6 +9,7 @@ fn main() { wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example)); } +// this is a component static Example: FC<()> = |ctx, _props| { let (event, set_event) = use_state(&ctx, || None); let event = format!("{:#?}", event); @@ -21,14 +22,12 @@ static Example: FC<()> = |ctx, _props| { div { class: "py-12 px-4 w-full max-w-2xl mx-auto bg-red-100" - // class: "py-12 px-4 text-center w-full max-w-2xl mx-auto bg-red-100" span { class: "text-sm font-semibold" "Dioxus Example: Synthetic Events" } button { class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow" - "press me" } pre { @@ -36,6 +35,21 @@ static Example: FC<()> = |ctx, _props| { id: "json" "{event}" } + Example2 { name: "{event}" } + } + }) +}; + + +#[derive(Debug, PartialEq)] +struct Props { + name: String +} + +static Example2: FC = |ctx, props| { + ctx.render(rsx!{ + div { + h1 {"hello {props.name}"} } }) }; From 55a5541dba8c35f63bb49b7ec5c0f1a28dc40b55 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 8 Mar 2021 14:09:18 -0500 Subject: [PATCH 2/5] WIP: some docs --- notes/SOLVEDPROBLEMS.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/notes/SOLVEDPROBLEMS.md b/notes/SOLVEDPROBLEMS.md index 5329ffb74..8c471eb81 100644 --- a/notes/SOLVEDPROBLEMS.md +++ b/notes/SOLVEDPROBLEMS.md @@ -360,3 +360,37 @@ The DomTree object purely represents a viewable "key". It also forces components ## Events Events are finally in! To do events properly, we are abstracting over the event source with synthetic events. This forces 3rd party renderers to create the appropriate cross-platform event + + +## Optional Props on Components + +A major goal here is ergonomics. Any field that is Option should default to none. + +```rust + +rsx! { + Example { /* args go here */ a: 10, b: 20 } +} + + +``` + +```rust +#[derive(Properties)] +struct Props { + +} + +static Component: FC = |ctx, props| { + +} +``` + +or + +```rust +#[fc] +static Component: FC = |ctx, name: &str| { + +} +``` From 7158bc3575e180dbe8641549b040e74ae3baf80b Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 9 Mar 2021 00:58:20 -0500 Subject: [PATCH 3/5] Feat: revert FC changes (like the old style). This commit reverts to the old style of props + FC. The old style is desirable because people comfortable with react can automatically be comfortable with dioxus. It's also nice in that the same props can be used to drive two different components - something the trait version couldn't do. Now, our trait bound forces implementations to have the #[derive(Props)] flag. This will need to implement the Properties trait as well as PartialEq (using ptr::equal for closure fields). --- packages/core-macro/src/lib.rs | 10 + packages/core-macro/src/props/mod.rs | 1198 ++++++++++++++++++++++++++ packages/core-macro/src/rsxt.rs | 19 +- packages/core/examples/borrowed.rs | 35 +- packages/core/examples/fc.rs | 6 +- packages/core/examples/props.rs | 38 +- packages/core/examples/step.rs | 19 +- packages/core/src/component.rs | 90 +- packages/core/src/context.rs | 2 +- packages/core/src/debug_renderer.rs | 3 +- packages/core/src/lib.rs | 9 +- packages/core/src/nodebuilder.rs | 8 +- packages/core/src/nodes.rs | 7 +- packages/core/src/scope.rs | 21 +- packages/core/src/virtual_dom.rs | 11 +- 15 files changed, 1339 insertions(+), 137 deletions(-) create mode 100644 packages/core-macro/src/props/mod.rs diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index 67669f873..48fb5c12d 100644 --- a/packages/core-macro/src/lib.rs +++ b/packages/core-macro/src/lib.rs @@ -5,6 +5,7 @@ use syn::parse_macro_input; mod fc; mod htm; mod ifmt; +mod props; mod rsxt; mod util; @@ -59,3 +60,12 @@ pub fn format_args_f(input: TokenStream) -> TokenStream { .unwrap_or_else(|err| err.to_compile_error()) .into() } + +#[proc_macro_derive(Props, attributes(builder))] +pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as syn::DeriveInput); + match props::impl_my_derive(&input) { + Ok(output) => output.into(), + Err(error) => error.to_compile_error().into(), + } +} diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs new file mode 100644 index 000000000..af7497394 --- /dev/null +++ b/packages/core-macro/src/props/mod.rs @@ -0,0 +1,1198 @@ +//! This code mostly comes from idanarye/rust-typed-builder +//! +//! However, it has been adopted to fit the Dioxus Props builder pattern. +//! +//! For dioxus, we make a few changes: +//! - automatically implement Into

"hello world!"

@@ -24,3 +23,11 @@ static Example: FC = |ctx, _props| {
}) }; + +// toodo: derive this +impl Properties for SomeProps { + type Builder = SomePropsBuilder<((),)>; + fn builder() -> Self::Builder { + SomeProps::builder() + } +} diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index 6b8fe0173..13698a37f 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -1,69 +1,33 @@ //! This file handles the supporting infrastructure for the `Component` trait and `Properties` which makes it possible //! for components to be used within Nodes. +//! +//! Note - using the builder pattern does not required the Properties trait to be implemented - the only thing that matters is +//! if the type suppports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder +//! that ensures compile-time required and optional fields on props. +use crate::innerlude::FC; pub type ScopeIdx = generational_arena::Index; -/// The `Component` trait refers to any struct or funciton that can be used as a component -/// We automatically implement Component for FC -// pub trait Component { -// type Props: Properties<'static>; -// fn builder(&'static self) -> Self::Props; -// } - -// // Auto implement component for a FC -// // Calling the FC is the same as "rendering" it -// impl> Component for FC

{ -// type Props = P; - -// fn builder(&self) -> Self::Props { -// todo!() -// } -// } - -/// The `Properties` trait defines any struct that can be constructed using a combination of default / optional fields. -/// Components take a "properties" object -// pub trait Properties<'a> -// where -// Self: Debug, -// { -// fn call(&self, ptr: *const ()) {} -// } - -// // Auto implement for no-prop components -// impl<'a> Properties<'a> for () { -// fn call(&self, ptr: *const ()) {} -// } - -#[cfg(test)] -mod tests { - use crate::prelude::bumpalo::Bump; - use crate::prelude::*; - - // fn test_static_fn<'a, P>(b: &'a Bump, r: FC

) -> VNode<'a> { - // todo!() - // } - - // static TestComponent: FC<()> = |ctx, props| { - // // - - // ctx.render(html! { - //

- - //
- // }) - // }; - - // static TestComponent2: FC<()> = |ctx, props| { - // // - // ctx.render(|ctx| VNode::text("blah")) - // }; - - // #[test] - // fn ensure_types_work() { - // let bump = Bump::new(); - - // // Happiness! The VNodes are now allocated onto the bump vdom - // let _ = test_static_fn(&bump, TestComponent); - // let _ = test_static_fn(&bump, TestComponent2); - // } +pub trait Properties: PartialEq { + type Builder; + fn builder() -> Self::Builder; +} + +pub struct EmptyBuilder; +impl EmptyBuilder { + pub fn build() -> () { + () + } +} + +impl Properties for () { + type Builder = EmptyBuilder; + + fn builder() -> Self::Builder { + EmptyBuilder {} + } +} + +pub fn fc_to_builder(f: FC) -> T::Builder { + T::builder() } diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index 4b6b46343..f716c080b 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -99,7 +99,7 @@ impl<'a> Context<'a> { /// ctx.render(lazy_tree) /// } ///``` - pub fn render(self, lazy_nodes: impl FnOnce(&NodeCtx<'a>) -> VNode<'a> + 'a) -> DomTree { + pub fn render(self, lazy_nodes: impl FnOnce(&'_ NodeCtx<'a>) -> VNode<'a> + 'a) -> DomTree { let ctx = NodeCtx { bump: self.bump, scope: self.scope, diff --git a/packages/core/src/debug_renderer.rs b/packages/core/src/debug_renderer.rs index 5acc1543d..0b692a594 100644 --- a/packages/core/src/debug_renderer.rs +++ b/packages/core/src/debug_renderer.rs @@ -21,11 +21,12 @@ impl DebugRenderer { pub fn log_dom(&self) {} } +#[cfg(old)] #[cfg(test)] mod tests { use super::*; use crate::prelude::*; - use crate::scope::FC; + use crate::scope::Properties; #[test] fn ensure_creation() -> Result<(), ()> { diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index bbaf4a160..972097218 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -90,6 +90,7 @@ pub mod builder { pub(crate) mod innerlude { // pub(crate) use crate::component::Properties; + pub(crate) use crate::component::Properties; pub(crate) use crate::context::Context; pub(crate) use crate::error::{Error, Result}; use crate::nodes; @@ -106,7 +107,7 @@ pub(crate) mod innerlude { // pub use nodes::iterables::IterableNodes; /// This type alias is an internal way of abstracting over the static functions that represent components. - // pub type FC

= for<'scope> fn(Context<'scope>, &'scope P) -> DomTree; + pub type FC

= for<'scope> fn(Context<'scope>, &'scope P) -> DomTree; mod fc2 {} // pub type FC<'a, P: 'a> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree; @@ -137,7 +138,7 @@ pub mod prelude { // pub use nodes::iterables::IterableNodes; /// This type alias is an internal way of abstracting over the static functions that represent components. - // pub use crate::innerlude::FC; + pub use crate::innerlude::FC; // TODO @Jon, fix this // hack the VNode type until VirtualNode is fixed in the macro crate @@ -151,8 +152,8 @@ pub mod prelude { pub use crate as dioxus; pub use crate::nodebuilder as builder; // pub use dioxus_core_macro::fc; - pub use dioxus_core_macro::format_args_f; - pub use dioxus_core_macro::{html, rsx}; + + pub use dioxus_core_macro::{format_args_f, html, rsx, Props}; pub use crate::component::ScopeIdx; pub use crate::diff::DiffMachine; diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index 22b72645e..7a5c88697 100644 --- a/packages/core/src/nodebuilder.rs +++ b/packages/core/src/nodebuilder.rs @@ -5,7 +5,7 @@ use std::{borrow::BorrowMut, ops::Deref}; use crate::{ context::NodeCtx, events::VirtualEvent, - innerlude::VComponent, + innerlude::{VComponent, FC}, nodes::{Attribute, Listener, NodeKey, VNode}, prelude::VElement, }; @@ -526,7 +526,11 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> { // _f: crate::innerlude::FC, // _props: T -pub fn virtual_child<'a, T: crate::scope::FC>(ctx: &'a NodeCtx<'a>, p: T) -> VNode<'a> { +pub fn virtual_child<'a, 'b, T: crate::innerlude::Properties>( + ctx: &'b NodeCtx<'a>, + p: T, + f: FC, +) -> VNode<'a> { todo!() // VNode::Component() } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 9075e3b06..a767dfce8 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -296,10 +296,7 @@ mod vtext { /// Virtual Components for custom user-defined components /// Only supports the functional syntax mod vcomponent { - use crate::{ - innerlude::{Context, ScopeIdx}, - scope::FC, - }; + use crate::innerlude::{Context, Properties, ScopeIdx, FC}; use std::{ any::{Any, TypeId}, cell::RefCell, @@ -345,7 +342,7 @@ mod vcomponent { // this lets us keep scope generic *and* downcast its props when we need to: // - perform comparisons when diffing (memoization) // - - pub fn new(comp: P) -> Self { + pub fn new(caller: FC

, comp: P) -> Self { todo!() // let p = Rc::new(props); diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index a7e43aff7..d6f42cba1 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -11,10 +11,6 @@ use std::{ ops::Deref, }; -pub trait FC: PartialEq { - fn render(ctx: Context, props: &Self) -> DomTree; -} - pub trait Scoped { fn run(&mut self); fn compare_props(&self, new: &dyn std::any::Any) -> bool; @@ -30,15 +26,19 @@ pub trait Scoped { /// /// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components. /// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently. -pub struct Scope { +pub struct Scope { // Map to the parent pub parent: Option, // our own index pub myidx: ScopeIdx, + // the props pub props: P, + // and the actual render function + pub caller: FC

, + // ========================== // slightly unsafe stuff // ========================== @@ -61,7 +61,8 @@ pub struct Scope { // instead of having it as a trait method, we use a single function // todo: do the unsafety magic stuff to erase the type of p -pub fn create_scoped( +pub fn create_scoped( + caller: FC

, props: P, myidx: ScopeIdx, parent: Option, @@ -84,6 +85,7 @@ pub fn create_scoped( let frames = ActiveFrame::from_frames(old_frame, new_frame); Box::new(Scope { + caller, myidx, hook_arena, hooks, @@ -94,7 +96,7 @@ pub fn create_scoped( }) } -impl Scoped for Scope

{ +impl Scoped for Scope

{ /// Create a new context and run the component with references from the Virtual Dom /// This function downcasts the function pointer based on the stored props_type /// @@ -121,8 +123,8 @@ impl Scoped for Scope

{ // Note that the actual modification of the vnode head element occurs during this call // let _: DomTree = caller(ctx, props); - let _: DomTree = P::render(ctx, &self.props); - // let _: DomTree = (self.caller)(ctx, &self.props); + // let _: DomTree = P::render (ctx, &self.props); + let _: DomTree = (self.caller)(ctx, &self.props); /* SAFETY ALERT @@ -256,6 +258,7 @@ impl ActiveFrame { } } +#[cfg(old)] #[cfg(test)] mod tests { use super::*; diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 909193d28..371a02491 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -1,6 +1,6 @@ // use crate::{changelist::EditList, nodes::VNode}; -use crate::{innerlude::*, scope::FC}; +use crate::innerlude::*; use crate::{ patch::Edit, scope::{create_scoped, Scoped}, @@ -60,17 +60,16 @@ impl VirtualDom { /// /// This means that the root component must either consumes its own context, or statics are used to generate the page. /// The root component can access things like routing in its context. - pub fn new(root: impl Fn(Context) -> DomTree) -> Self { - todo!() - // Self::new_with_props(root) + pub fn new(root: FC<()>) -> Self { + Self::new_with_props(root, ()) } - /// Start a new VirtualDom instance with a dependent props. /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders. /// /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive /// to toss out the entire tree. - pub fn new_with_props(root: P) -> Self { + pub fn new_with_props(root: FC

, root_props: P) -> Self { + // let mut components = Arena::new(); // let mut components = Arena::new(); // Create a reference to the component in the arena From 37f5a7ad33e272a9e210bf480304d54ff0df0d67 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 9 Mar 2021 14:45:52 -0500 Subject: [PATCH 4/5] Feat: wire up props macro --- packages/core-macro/src/rsxt.rs | 72 +++++++++++++++++++++--------- packages/core/examples/borrowed.rs | 19 ++++---- packages/core/examples/fc.rs | 48 +++++++++----------- packages/core/src/component.rs | 2 +- packages/core/src/lib.rs | 2 +- packages/core/src/nodebuilder.rs | 3 +- 6 files changed, 87 insertions(+), 59 deletions(-) diff --git a/packages/core-macro/src/rsxt.rs b/packages/core-macro/src/rsxt.rs index 839bd990b..524acc4ee 100644 --- a/packages/core-macro/src/rsxt.rs +++ b/packages/core-macro/src/rsxt.rs @@ -132,7 +132,7 @@ impl Parse for Node { struct Component { name: Ident, - body: Vec, + body: Vec, // attrs: Vec, children: MaybeExpr>, } @@ -147,13 +147,19 @@ impl ToTokens for ToToksCtx<&Component> { // self.recurse(attr).to_tokens(&mut toks); // } - let body = &self.inner.body; - // panic!("tokens are {:#?}", toks); + let mut builder = quote! { + fc_to_builder(#name) + }; - // tokens.append_all(quote! { - // dioxus::builder::virtual_child(ctx, #name{ #body, ___p: PhantomData{} }) - // // dioxus::builder::ElementBuilder::new(ctx, #name) - // }); + for field in &self.inner.body { + builder.append_all(quote! {#field}); + } + + builder.append_all(quote! { + .build() + }); + + // panic!("tokens are {:#?}", toks); // no children right now @@ -179,6 +185,9 @@ impl ToTokens for ToToksCtx<&Component> { // tokens.append_all(quote! { // .finish() // }); + let toks = tokens.append_all(quote! { + dioxus::builder::virtual_child(ctx, #name, #builder) + }); } } @@ -199,18 +208,21 @@ impl Parse for Component { let content: ParseBuffer; syn::braced!(content in s); - let mut body: Vec = Vec::new(); - let mut attrs = Vec::new(); + let mut body: Vec = Vec::new(); let mut children: Vec = Vec::new(); - parse_element_content(content, &mut attrs, &mut children); + // parse_element_content(content, &mut attrs, &mut children); - // 'parsing: loop { - // // [1] Break if empty - // if content.is_empty() { - // break 'parsing; - // } - // } + 'parsing: loop { + // [1] Break if empty + if content.is_empty() { + break 'parsing; + } + + if let Ok(field) = content.parse::() { + body.push(field); + } + } // let body = content.parse()?; @@ -231,20 +243,36 @@ impl Parse for Component { } // the struct's fields info -pub struct Field { +pub struct ComponentField { name: Ident, - content: Ident, + content: Expr, } -impl Parse for Field { +impl Parse for ComponentField { fn parse(input: ParseStream) -> Result { - todo!() + let mut name = Ident::parse_any(input)?; + let name_str = name.to_string(); + input.parse::()?; + let content = input.parse()?; + + // consume comma if it exists + // we don't actually care if there *are* commas between attrs + if input.peek(Token![,]) { + let _ = input.parse::(); + } + + Ok(Self { name, content }) } } -impl ToTokens for ToToksCtx<&Field> { +impl ToTokens for &ComponentField { + // impl ToTokens for ToToksCtx<&ComponentField> { fn to_tokens(&self, tokens: &mut TokenStream2) { - todo!() + let name = &self.name; + let content = &self.content; + tokens.append_all(quote! { + .#name(#content) + }) } } diff --git a/packages/core/examples/borrowed.rs b/packages/core/examples/borrowed.rs index 5054111a5..e203364a6 100644 --- a/packages/core/examples/borrowed.rs +++ b/packages/core/examples/borrowed.rs @@ -9,7 +9,7 @@ fn main() {} use std::fmt::Debug; -use dioxus_core::{component::Properties, prelude::*}; +use dioxus_core::prelude::*; struct Props { items: Vec, @@ -32,11 +32,12 @@ fn app(ctx: Context, props: &Props) -> DomTree { // root = root.child(builder::virtual_child( c, - ChildProps { - item: child, - item_handler: setter, - }, - child_item, + ChildItem, + // create the props with nothing but the fc + fc_to_builder(ChildItem) + .item(child) + .item_handler(setter) + .build(), )); } root.finish() @@ -64,7 +65,9 @@ struct ChildProps<'a> { impl PartialEq for ChildProps<'_> { fn eq(&self, other: &Self) -> bool { - todo!() + // assume the dyn fn is never stable - + // wrap with use_callback if it's an issue for you + false } } @@ -75,7 +78,7 @@ impl<'a> Properties for ChildProps<'a> { } } -fn child_item(ctx: Context, props: &ChildProps) -> DomTree { +fn ChildItem(ctx: Context, props: &ChildProps) -> DomTree { todo!() // ctx.render(rsx! { // div { diff --git a/packages/core/examples/fc.rs b/packages/core/examples/fc.rs index f3305cfd9..ed77d7558 100644 --- a/packages/core/examples/fc.rs +++ b/packages/core/examples/fc.rs @@ -1,41 +1,37 @@ +use dioxus_core::component::fc_to_builder; use dioxus_core::prelude::*; - use dioxus_core_macro::fc; use std::marker::PhantomData; -// #[derive(PartialEq)] -// pub struct Example<'a> { -// b: &'a str, -// ___p: std::marker::PhantomData<&'a ()>, -// } -// impl<'a> FC for Example<'a> { -// fn render(ctx: Context<'_>, props: &Example<'a>) -> DomTree { -// let Example { b, .. } = props; -// { -// ctx.render(rsx! { -// div { "abcd {b}" } -// }) -// } -// } -// } - -// always try to fill in with Default - -// #[fc] -fn Example(ctx: Context, a: &str, b: &str, c: &str) -> DomTree { +static BLAH: FC<()> = |ctx, props| { + let g = "asd".to_string(); ctx.render(rsx! { div { SomeComponent { - a: "123" + some_field: g + abc123: 10, } } }) +}; + +#[derive(PartialEq, Props)] +pub struct ExampleProps { + some_field: String, } -// #[fc] -fn SomeComponent(ctx: Context, a: &str, b: &str) -> DomTree { - todo!() -} +static SomeComponent: FC = |ctx, props| { + ctx.render(rsx! { + div { } + }) +}; fn main() {} + +impl Properties for ExampleProps { + type Builder = ExamplePropsBuilder<((),)>; + fn builder() -> Self::Builder { + ExampleProps::builder() + } +} diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index 13698a37f..45c6cf6a2 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -15,7 +15,7 @@ pub trait Properties: PartialEq { pub struct EmptyBuilder; impl EmptyBuilder { - pub fn build() -> () { + pub fn build(self) -> () { () } } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 972097218..92ea4ec7e 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -130,7 +130,7 @@ pub(crate) mod innerlude { /// Re-export common types for ease of development use. /// Essential when working with the html! macro pub mod prelude { - // pub use crate::component::Properties; + pub use crate::component::{fc_to_builder, Properties}; pub use crate::context::Context; use crate::nodes; pub use crate::virtual_dom::VirtualDom; diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index 7a5c88697..ee2aa2868 100644 --- a/packages/core/src/nodebuilder.rs +++ b/packages/core/src/nodebuilder.rs @@ -528,9 +528,10 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> { // _props: T pub fn virtual_child<'a, 'b, T: crate::innerlude::Properties>( ctx: &'b NodeCtx<'a>, - p: T, f: FC, + p: T, ) -> VNode<'a> { + // crate::nodes::VComponent todo!() // VNode::Component() } From 8fcd001677146d83dc8e89402495c4e2a0ccc2f8 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 9 Mar 2021 14:46:00 -0500 Subject: [PATCH 5/5] Feat: update component so build passes --- packages/core/examples/fc.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/examples/fc.rs b/packages/core/examples/fc.rs index ed77d7558..1c42a83a0 100644 --- a/packages/core/examples/fc.rs +++ b/packages/core/examples/fc.rs @@ -10,7 +10,6 @@ static BLAH: FC<()> = |ctx, props| { div { SomeComponent { some_field: g - abc123: 10, } } })