WIP: remove FC

This commit is contained in:
Jonathan Kelley committed 2021-03-07 21:28:20 -05:00
1 parent 3740f81383
commit 92d9521a73
24 files changed
+801 -451

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@ members = [
"packages/dioxus",
"packages/core-macro",
"packages/core",
"packages/web",
# "packages/web",
# "packages/router",
# "packages/ssr",
# "packages/webview",
+1
View File
@@ -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, || "...?");
+1
View File
@@ -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"
+3
View File
@@ -0,0 +1,3 @@
use dioxus_core_macro::fc;
fn main() {}
+120 -103
View File
@@ -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<TokenStream> {
// 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<Block>,
// The user's props type
props_type: Box<Type>,
// // The user's props type
// props_type: Box<Type>,
arg: FnArg,
vis: Visibility,
attrs: Vec<Attribute>,
@@ -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<ItemFn> {
@@ -228,7 +244,7 @@ pub fn ensure_return_type(output: ReturnType) -> syn::Result<Box<Type>> {
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<Signature> {
Ok(sig)
}
pub fn validate_context_arg(first_arg: &FnArg) -> syn::Result<Box<Type>> {
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<Box<Type>> {
// 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() {}
+27 -32
View File
@@ -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<TokenStream> {
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<TokenStream2, Token![.]> = segments
let segments: Punctuated<TokenStream, Token![.]> = 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...
+21 -17
View File
@@ -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::<fc::FunctionComponent>(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()
}
+183 -87
View File
@@ -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<Self> {
let fork = s.fork();
let fork1 = s.fork();
let fork2 = s.fork();
let ret = if let Ok(text) = fork.parse::<TextNode>() {
s.advance_to(&fork);
Ok(Self::Text(text))
} else if let Ok(el) = s.parse::<Element>() {
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::<TextNode>() {
s.advance_to(&fork1);
Self::Text(text)
} else if let Ok(element) = fork2.parse::<Element>() {
s.advance_to(&fork2);
Self::Element(element)
} else if let Ok(comp) = s.parse::<Component>() {
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::<Token![,]>();
}
ret
Ok(ret)
}
}
struct Component {
name: Ident,
body: Vec<Field>,
// attrs: Vec<Attr>,
children: MaybeExpr<Vec<Node>>,
}
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<Self> {
// 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<Field> = 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<Attr> = vec![];
let mut children: Vec<Node> = 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<Self> {
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<Attr> = vec![];
let mut children: Vec<Node> = 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<Node>
// [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::<Attr>() {
// 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::<Node>() {
// 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::<Token![<]>()?;
// s.parse::<Token![/]>()?;
// 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::<Token![>]>()?;
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<Attr>, children: &mut Vec<Node>) {
'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<Node>
// [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::<Attr>() {
// 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::<Node>() {
// 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
/// =======================================
+137
View File
@@ -0,0 +1,137 @@
// use lazy_static::lazy_static;
use once_cell::sync::Lazy;
use std::collections::hash_set::HashSet;
static VALID_TAGS: Lazy<HashSet<&'static str>> = 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)
}
+41
View File
@@ -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() {}
+40 -20
View File
@@ -3,33 +3,53 @@ use dioxus_core::prelude::*;
fn main() {}
trait SProps {}
trait Comp {
type Props;
}
impl<T> Comp for FC<T> {
type Props = T;
}
fn test() {
// let g: FC<ButtonProps> = 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"
+1 -1
View File
@@ -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(())
}
+21 -21
View File
@@ -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<P>) -> VNode<'a> {
todo!()
}
// fn test_static_fn<'a, P>(b: &'a Bump, r: FC<P>) -> VNode<'a> {
// todo!()
// }
static TestComponent: FC<()> = |ctx, props| {
//
// static TestComponent: FC<()> = |ctx, props| {
// //
ctx.render(html! {
<div>
// ctx.render(html! {
// <div>
</div>
})
};
// </div>
// })
// };
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);
// }
}
+10 -5
View File
@@ -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! { <div>"hello world" </div> })
});
#[derive(PartialEq)]
struct Creation {}
impl FC for Creation {
fn render(ctx: Context, props: &Self) -> DomTree {
ctx.render(html! { <div>"hello world" </div> })
}
}
let mut dom = VirtualDom::new_with_props(Creation {});
// dom.progress()?;
Ok(())
}
}
+9 -27
View File
@@ -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<ScopeIdx>,
pub need_to_diff: FxHashSet<ScopeIdx>,
}
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
+24 -24
View File
@@ -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)
// }
// }
// })
// };
}
}
+5 -4
View File
@@ -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<P> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
// pub type FC<P> = 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;
+3 -1
View File
@@ -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<T>) -> VNode<'a> {
// _f: crate::innerlude::FC<T>,
// _props: T
pub fn virtual_child<'a, T: crate::scope::FC>(ctx: &'a NodeCtx<'a>, p: T) -> VNode<'a> {
todo!()
// VNode::Component()
}
+43 -10
View File
@@ -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<dyn std::any::Any>,
pub(crate) props_type: TypeId,
pub(crate) comp: *const (),
pub(crate) caller: Caller,
// pub(crate) props: Box<dyn std::any::Any>,
// 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<dyn Fn(&dyn Any) -> bool>);
// pub struct Comparator<'src>(&'src dyn Fn(&dyn Any) -> bool);
pub struct Caller(Box<dyn Fn(Context) -> 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<P>(comp: FC<P>, props: P) -> Self {
pub fn new<P: FC + 'static>(comp: P) -> Self {
todo!()
// let p = Rc::new(props);
// let props_comparator = move |new_props: &dyn Any| -> bool {
// new_props
// .downcast_ref::<P>()
// .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,
+73 -77
View File
@@ -11,9 +11,9 @@ use std::{
ops::Deref,
};
pub trait Properties: PartialEq {}
// just for now
impl<T: PartialEq> 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<P: Properties> {
pub struct Scope<P: FC> {
// Map to the parent
pub parent: Option<ScopeIdx>,
// our own index
pub myidx: ScopeIdx,
pub caller: FC<P>,
pub props: P,
// ==========================
@@ -63,8 +61,7 @@ pub struct Scope<P: Properties> {
// 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<P: Properties + 'static>(
caller: FC<P>,
pub fn create_scoped<P: FC + 'static>(
props: P,
myidx: ScopeIdx,
parent: Option<ScopeIdx>,
@@ -90,7 +87,6 @@ pub fn create_scoped<P: Properties + 'static>(
myidx,
hook_arena,
hooks,
caller,
frames,
listeners,
parent,
@@ -98,7 +94,7 @@ pub fn create_scoped<P: Properties + 'static>(
})
}
impl<P: Properties + 'static> Scoped for Scope<P> {
impl<P: FC + 'static> Scoped for Scope<P> {
/// 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<P: Properties + 'static> Scoped for Scope<P> {
// 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! {
<div onclick={|_| println!("Hell owlrld")}>
"hello"
</div>
})
};
// static ListenerTest: FC<()> = |ctx, props| {
// ctx.render(html! {
// <div onclick={|_| println!("Hell owlrld")}>
// "hello"
// </div>
// })
// };
#[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::<ExampleProps>(
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<ExampleProps> = |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<EmptyProps> = |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 };
+19 -18
View File
@@ -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<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
let mut components = Arena::new();
pub fn new_with_props<P: FC + 'static>(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<dyn Properties>,
// },
}
+1 -1
View File
@@ -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;
+1
View File
@@ -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]
+16 -2
View File
@@ -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<Props> = |ctx, props| {
ctx.render(rsx!{
div {
h1 {"hello {props.name}"}
}
})
};