From 1919f88f035fadadfc2b9bce6157f2effcd2a4b0 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 2 Jun 2021 11:07:30 -0400 Subject: [PATCH] Feat: some docs --- .vscode/spellright.dict | 2 + docs/posts/01-hello-world.md | 4 +- docs/posts/02-utilites.md | 2 +- docs/posts/03-vnode-macros.md | 72 ++++++++++-- docs/website/homepage/ex1.md | 56 +++++++++ packages/core/examples/component_child.rs | 1 + packages/core/examples/nested.rs | 131 +--------------------- packages/core/src/nodebuilder.rs | 17 +-- packages/core/src/nodes.rs | 46 ++++---- packages/core/src/virtual_dom.rs | 30 ++--- 10 files changed, 179 insertions(+), 182 deletions(-) create mode 100644 docs/website/homepage/ex1.md diff --git a/.vscode/spellright.dict b/.vscode/spellright.dict index 7730a4f6e..db3e9da9c 100644 --- a/.vscode/spellright.dict +++ b/.vscode/spellright.dict @@ -43,3 +43,5 @@ virtualdom namespaced namespacing impl +destructured +linting diff --git a/docs/posts/01-hello-world.md b/docs/posts/01-hello-world.md index 09b93c51d..dff2e3b0c 100644 --- a/docs/posts/01-hello-world.md +++ b/docs/posts/01-hello-world.md @@ -9,8 +9,8 @@ struct MyProps { } fn Example(ctx: Context) -> VNode { - ctx.render(html! { -
"Hello {ctx.props().name}!"
+ ctx.render(html! { +
"Hello {ctx.name}!"
}) } ``` diff --git a/docs/posts/02-utilites.md b/docs/posts/02-utilites.md index 51bb7b162..55758362d 100644 --- a/docs/posts/02-utilites.md +++ b/docs/posts/02-utilites.md @@ -35,7 +35,7 @@ struct ExampleProps { fn Example(ctx: &mut Context) -> VNode { let ExampleProps { name, pending, count - } = ctx.props(); + } = ctx.props; rsx! {
diff --git a/docs/posts/03-vnode-macros.md b/docs/posts/03-vnode-macros.md index 25f562a8c..88fad2121 100644 --- a/docs/posts/03-vnode-macros.md +++ b/docs/posts/03-vnode-macros.md @@ -4,10 +4,12 @@ Dioxus comes preloaded with two macros for creating VNodes. ## html! macro -The html! macro supports the html standard. This macro will happily accept a copy-paste from something like tailwind builder. Writing this one by hand is a bit tedious and doesn't come with much help from Rust IDE tools. +The html! macro supports a limited subset of the html standard. This macro will happily accept a copy-paste from something like tailwind builder. However, writing HTML by hand is a bit tedious - IDE tools for Rust don't support linting/autocomplete/syntax highlighting. RSX is much more natural for Rust programs and _does_ integrate well with Rust IDE tools. There is also limited support for dynamic handlers, but it will function similarly to JSX. +You'll want to write RSX where you can, and in a future release we'll have a tool that automatically converts HTML to RSX. + ```rust #[fc] fn Example(ctx: Context, name: &str, pending: bool, count: i32 ) -> VNode { @@ -23,9 +25,9 @@ fn Example(ctx: Context, name: &str, pending: bool, count: i32 ) -> VNode { ## rsx! macro -The rsx! macro is a VNode builder macro designed especially for Rust. Writing these should feel very natural, much like assembling a struct. VSCode also supports these with code folding, bracket-tabbing, bracket highlighting, and section selecting. +The rsx! macro is a VNode builder macro designed especially for Rust programs. Writing these should feel very natural, much like assembling a struct. VSCode also supports these with code folding, bracket-tabbing, bracket highlighting, and section selecting. -The Dioxus VSCode extension provides a function to convert a selection of html! template and turn it into rsx!, so you'll never need to transcribe templates by hand. +The Dioxus VSCode extension will eventually provide a macro to convert a selection of html! template and turn it into rsx!, so you'll never need to transcribe templates by hand. It's also a bit easier on the eyes 🙂. @@ -51,30 +53,55 @@ Each element takes a comma-separated list of expressions to build the node. Roug - `CustomTag {}` adds a new child component - `{expr}` pastes the `expr` tokens literally. They must be IntoCtx to work properly -Lists must include commas, much like how struct definitions work. +Commas are entirely optional, but might be useful to delineate between elements and attributes. + +The `render` function provides an **extremely efficient** allocator for VNodes and text, so try not to use the `format!` macro in your components. Rust's default `ToString` methods pass through the global allocator, but all text in components is allocated inside a manually-managed Bump arena. ```rust static Example: FC<()> = |ctx| { + let text = "example"; + ctx.render(rsx!{ div { h1 { "Example" }, + + // fstring interpolation + "{text}" + p { - // Props + // Attributes tag: "type", + + // Anything that implements display can be an attribute abc: 123, enabled: true, - class: "big small wide short", + + // attributes also supports interpolation + // `class` is not a restricted keyword unlike JS and ClassName + class: "big small wide short {text}", + + // bool-based classnames + classes: [("big", true), ("small", false)] + + // Bool-based props + // *must* be in the tuple form, cannot enter as a variable + tag: ("type", false) + + tag: {"these tokens are placed directly"} // Children a { "abcder" }, - // Children with props - h2 { "whatsup", class: "abc-123" }, + // Children with attributes + h2 { "hello", class: "abc-123" }, // Child components CustomComponent { a: 123, b: 456, key: "1" }, + // Child components with paths + crate::components::CustomComponent { a: 123, b: 456, key: "1" }, + // Iterators { 0..3.map(|i| rsx!{ h1 {"{:i}"} }) }, @@ -82,7 +109,34 @@ static Example: FC<()> = |ctx| { { rsx! { div { } } }, { html! {
} }, - // Any expression that is Into + // Matching + // Requires rendering the nodes first. + // rsx! is lazy, and the underlying closures cannot have the same type + // Rendering produces the VNode type + {match rand::gen_range::(1..3) { + 1 => rsx!(in ctx, h1 { "big" }) + 2 => rsx!(in ctx, h2 { "medium" }) + _ => rsx!(in ctx, h3 { "small" }) + }} + + // Optionals + {true.and_then(|f| rsx!{ h1 {"Conditional Rendering"} })} + + // Bool options + {(rsx!{ h1 {"Conditional Rendering"}, true)} + + // Child nodes + // Returns &[VNode] + {ctx.children()} + + // Duplicating nodes + // Clones the nodes by reference, so they are literally identical + {{ + let node = rsx!(in ctx, h1{ "TopNode" }); + (0..10).map(|_| node.clone()) + }} + + // Any expression that is `IntoVNode` {expr} } } diff --git a/docs/website/homepage/ex1.md b/docs/website/homepage/ex1.md new file mode 100644 index 000000000..582938964 --- /dev/null +++ b/docs/website/homepage/ex1.md @@ -0,0 +1,56 @@ +# A Simple Component + +```rust +#[derive(PartialEq, Properties)] +struct Props { name: &'static str } + +static HelloMessage: FC = |ctx| { + ctx.render(rsx!{ + div { "Hello {ctx.props.name}" } + }) +} +``` + +# Any syntax you like + +Choose from a close-to-html syntax or the standard rsx! syntax + +```rust +static HelloMessage: FC<()> = |ctx| { + ctx.render(html!{ +
Hello World!
+ }) +} +``` + +# A Stateful Component + +Store state with hooks! + +```rust +enum LightState { + Green + Yellow, + Red, +} +static HelloMessage: FC<()> = |ctx| { + let (color, set_color) = use_state(ctx, || LightState::Green); + + let title = match color { + Green => "Green means go", + Yellow => "Yellow means slow down", + Red => "Red means stop", + }; + + ctx.render(rsx!{ + h1 {"{title}"} + button { "tick" + onclick: move |_| set_color(match color { + Green => Yellow, + Yellow => Red, + Red => Green, + }) + } + }) +} +``` diff --git a/packages/core/examples/component_child.rs b/packages/core/examples/component_child.rs index 29d656431..ff0a7e840 100644 --- a/packages/core/examples/component_child.rs +++ b/packages/core/examples/component_child.rs @@ -22,6 +22,7 @@ static Example: FC<()> = |ctx| { // rsx! { in ctx, div { + h1 {"these are children nodes: "} {nodes} } } diff --git a/packages/core/examples/nested.rs b/packages/core/examples/nested.rs index be1aa2da9..472a8d02c 100644 --- a/packages/core/examples/nested.rs +++ b/packages/core/examples/nested.rs @@ -14,7 +14,11 @@ static Header: FC<()> = |ctx| { ctx.render(dioxus::prelude::LazyNodes::new(|nodectx| { builder::ElementBuilder::new(nodectx, "div") - .child(VNode::Component(VComponent::new(Bottom, (), None))) + .child(VNode::Component(nodectx.bump().alloc(VComponent::new( + Bottom, + (), + None, + )))) .finish() })) }; @@ -36,128 +40,3 @@ fn Top(ctx: Context<()>) -> VNode {
}) } - -struct Callback(Box); - -// impl O> From for Callback { -// fn from(_: T) -> Self { -// todo!() -// } -// } - -impl From<&dyn Fn(A) -> O> for Callback<&dyn Fn(A) -> O> { - fn from(_: &dyn Fn(A) -> O) -> Self { - todo!() - } -} - -impl From<&dyn Fn(A, B) -> O> for Callback<&dyn Fn(A, B) -> O> { - fn from(_: &dyn Fn(A, B) -> O) -> Self { - todo!() - } -} - -// compile time reordering of arguments -// Allows for transparently calling -#[derive(Default)] -pub struct Args { - pub a: CuOpt, - pub b: CuOpt, - pub c: CuOpt, -} - -pub enum CuOpt { - Some(T), - None, -} -impl Default for CuOpt { - fn default() -> Self { - CuOpt::None - } -} - -impl CuOpt { - fn unwrap(self) -> T { - match self { - CuOpt::Some(t) => t, - CuOpt::None => panic!(""), - } - } -} - -trait IsMemo { - fn memo(&self, other: &Self) -> bool { - false - } -} - -impl IsMemo for CuOpt { - fn memo(&self, other: &Self) -> bool { - self == other - } -} - -impl PartialEq for CuOpt { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (CuOpt::Some(a), CuOpt::Some(b)) => a == b, - (CuOpt::Some(_), CuOpt::None) => false, - (CuOpt::None, CuOpt::Some(_)) => false, - (CuOpt::None, CuOpt::None) => true, - } - } -} - -impl IsMemo for &CuOpt { - fn memo(&self, other: &Self) -> bool { - false - } -} - -// #[test] -#[test] -fn try_test() { - // test_poc() -} - -// fn test_poc(ctx: Context) { -// let b = Bump::new(); - -// let h = Args { -// a: CuOpt::Some("ASD"), -// b: CuOpt::Some(123), -// c: CuOpt::Some(|| {}), -// // c: CuOpt::Some(b.alloc(|| {})), -// // c: CuOpt::Some(Box::new(|| {}) as Box), -// }; - -// let h2 = Args { -// a: CuOpt::Some("ASD"), -// b: CuOpt::Some(123), -// c: CuOpt::Some(|| {}), -// // c: CuOpt::Some(b.alloc(|| {})), -// // c: CuOpt::Some(Box::new(|| {}) as Box), -// // c: CuOpt::Some(Box::new(|| {}) as Box), -// // c: CuOpt::Some(Box::new(|| {}) as Box), -// }; - -// // dbg!((&h.a).memo((&&h2.a))); -// // dbg!((&h.b).memo((&&h2.b))); -// // dbg!((&h.c).memo((&&h2.c))); -// // -// // ctx: Context -// Top(ctx, &h.a.unwrap(), &h.b.unwrap(), &h.c.unwrap()); -// } - -// fn test_realzies() { -// let h = Args { -// a: CuOpt::Some("ASD"), -// b: CuOpt::Some(123), -// c: CuOpt::Some(|| {}), -// }; - -// let g = |ctx: Context| { -// // -// Top(ctx, &h.a.unwrap(), &h.b.unwrap(), &h.c.unwrap()) -// }; -// } diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index 2b2495678..ec7efd9c9 100644 --- a/packages/core/src/nodebuilder.rs +++ b/packages/core/src/nodebuilder.rs @@ -486,14 +486,6 @@ where } } -// impl IntoIterator for VNode { -// type Item = VNode; -// type IntoIter = std::iter::Once; -// fn into_iter(self) -> Self::IntoIter { -// std::iter::once(self) -// } -// } - impl<'a> IntoIterator for VNode<'a> { type Item = VNode<'a>; type IntoIter = std::iter::Once; @@ -665,8 +657,9 @@ pub fn virtual_child<'a, T: Properties>( ) -> VNode<'a> { // currently concerned about if props have a custom drop implementation // might override it with the props macro - VNode::Component( - ctx.bump() - .alloc(crate::nodes::VComponent::new(f, props, key)), - ) + todo!() + // VNode::Component( + // ctx.bump() + // .alloc(crate::nodes::VComponent::new(f, props, key)), + // ) } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 25d95aada..6e8bd0715 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -235,7 +235,7 @@ pub struct VComponent<'src> { pub children: &'src [VNode<'src>], // a pointer into the bump arena (given by the 'src lifetime) - raw_props: Box, + // raw_props: Box, // raw_props: *const (), // a pointer to the raw fn typ @@ -243,13 +243,24 @@ pub struct VComponent<'src> { _p: PhantomData<&'src ()>, } +unsafe fn transmogrify<'a>(p: impl Properties + 'a) -> impl Properties + 'static { + 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) // TODO: lift the requirement that props need to be static // we want them to borrow references... maybe force implementing a "to_static_unsafe" trait - pub fn new(component: FC

, props: P, key: Option<&'a str>) -> Self { + + pub fn new( + component: FC

, + // props: bumpalo::boxed::Box<'a, P>, + props: P, + key: Option<&'a str>, + ) -> Self { + // pub fn new(component: FC

, props: P, key: Option<&'a str>) -> Self { + // let bad_props = unsafe { transmogrify(props) }; let caller_ref = component as *const (); // let raw_props = props as *const P as *const (); @@ -271,7 +282,19 @@ impl<'a> VComponent<'a> { // } // }; - let caller: Rc VNode + 'a> = Rc::new(move |scp| todo!()); + // let prref: &'a P = props.as_ref(); + + let caller: Rc VNode> = Rc::new(move |scope| { + // + // let props2 = bad_props; + // props.as_ref() + // let ctx = Context { + // props: prref, + // scope, + // }; + todo!() + // component(ctx) + }); // let caller = Rc::new(create_closure(component, raw_props)); let key = match key { @@ -283,7 +306,7 @@ impl<'a> VComponent<'a> { key, ass_scope: Rc::new(RefCell::new(None)), user_fc: caller_ref, - raw_props: Box::new(props), + // raw_props: Box::new(props), _p: PhantomData, children: &[], caller, @@ -292,18 +315,3 @@ impl<'a> VComponent<'a> { } } } - -// fn create_closure( -// component: FC

, -// raw_props: *const (), -// ) -> impl for<'r> Fn(&'r Scope) -> VNode<'r> { -// move |ctx| -> VNode { -// // cast back into the right lifetime -// let safe_props: &P = unsafe { &*(raw_props as *const P) }; -// component(Context { -// props: safe_props, -// scope: ctx, -// }) -// // component(ctx, safe_props) -// } -// } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 734c0bb98..c6eeb80fb 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -137,12 +137,11 @@ impl VirtualDom { // Normally, a component would be passed as a child in the RSX macro which automatically produces OpaqueComponents // Here, we need to make it manually, using an RC to force the Weak reference to stick around for the main scope. - let _root_caller: Rc = Rc::new(move |ctx| { - todo!() - // root(Context { - // props: &root_props, - // scope: ctx, - // }) + let _root_caller: Rc> = Rc::new(move |scope| { + // the lifetime of this closure is just as long as the lifetime on the scope reference + // this closure moves root props (which is static) into this closure + let props = unsafe { &*(&root_props as *const _) }; + root(Context { props, scope }) }); // Create a weak reference to the OpaqueComponent for the root scope to use as its render function @@ -618,18 +617,23 @@ impl Scope { .ok_or(Error::FatalInternal("Failed to get caller"))?; // Cast the caller ptr from static to one with our own reference - let new_head = unsafe { - std::mem::transmute::<&OpaqueComponent<'static>, &OpaqueComponent<'sel>>( - caller.as_ref(), - ) - }(&self); + let c2: &OpaqueComponent<'static> = caller.as_ref(); + let c3: &OpaqueComponent<'sel> = unsafe { std::mem::transmute(c2) }; - todo!(); - // self.frames.cur_frame_mut().head_node = new_head; + let unsafe_head = unsafe { self.own_vnodes(c3) }; + + self.frames.cur_frame_mut().head_node = unsafe_head; Ok(()) } + // this is its own function so we can preciesly control how lifetimes flow + unsafe fn own_vnodes<'a>(&'a self, f: &OpaqueComponent<'a>) -> VNode<'static> { + let new_head: VNode<'a> = f(self); + let out: VNode<'static> = std::mem::transmute(new_head); + out + } + // A safe wrapper around calling listeners // calling listeners will invalidate the list of listeners // The listener list will be completely drained because the next frame will write over previous listeners