Przeglądaj źródła

Merge pull request #29 from DioxusLabs/jk/remove_node_safety

node safety
Jonathan Kelley 3 lat temu
rodzic
commit
b9b98dc7a5
100 zmienionych plików z 1027 dodań i 1192 usunięć
  1. 1 1
      README.md
  2. 0 15
      docs/blog/01-release.md
  3. 13 11
      docs/src/README.md
  4. 61 43
      docs/src/SUMMARY.md
  5. 0 0
      docs/src/advanced-guides/rsx-in-depth.md
  6. 0 1
      docs/src/cli/README.md
  7. 0 1
      docs/src/cli/build.md
  8. 0 1
      docs/src/cli/clean.md
  9. 0 1
      docs/src/cli/init.md
  10. 0 1
      docs/src/cli/serve.md
  11. 0 1
      docs/src/cli/test.md
  12. 0 1
      docs/src/cli/watch.md
  13. 0 16
      docs/src/concepts/04-hooks.md
  14. 0 45
      docs/src/concepts/05-context-api.md
  15. 0 0
      docs/src/concepts/09-interactivity.md
  16. 0 18
      docs/src/concepts/9901-hello-world.md
  17. 1 0
      docs/src/concepts/async.md
  18. 1 0
      docs/src/concepts/asynccallbacks.md
  19. 1 0
      docs/src/concepts/asynctasks.md
  20. 1 0
      docs/src/concepts/bundline.md
  21. 171 8
      docs/src/concepts/components.md
  22. 1 0
      docs/src/concepts/conditional_rendering.md
  23. 1 0
      docs/src/concepts/custom_elements.md
  24. 1 0
      docs/src/concepts/custom_renderer.md
  25. 1 0
      docs/src/concepts/effects.md
  26. 291 0
      docs/src/concepts/exporting_components.md
  27. 1 0
      docs/src/concepts/interactivity.md
  28. 1 0
      docs/src/concepts/lifecycles.md
  29. 1 0
      docs/src/concepts/managing_state.md
  30. 1 0
      docs/src/concepts/server_side_components.md
  31. 1 0
      docs/src/concepts/suspense.md
  32. 124 35
      docs/src/concepts/vnodes.md
  33. 1 0
      docs/src/depth/components.md
  34. 1 0
      docs/src/depth/memoization.md
  35. 1 0
      docs/src/depth/performance.md
  36. 1 0
      docs/src/depth/props.md
  37. 1 0
      docs/src/depth/rsx.md
  38. 1 0
      docs/src/depth/testing.md
  39. 1 0
      docs/src/depth/topics.md
  40. 0 1
      docs/src/format/README.md
  41. 0 1
      docs/src/format/configuration/README.md
  42. 0 1
      docs/src/format/configuration/environment-variables.md
  43. 0 1
      docs/src/format/configuration/general.md
  44. 0 1
      docs/src/format/configuration/preprocessors.md
  45. 0 1
      docs/src/format/configuration/renderers.md
  46. 0 1
      docs/src/format/mathjax.md
  47. 0 1
      docs/src/format/mdbook.md
  48. 0 1
      docs/src/format/summary.md
  49. 0 1
      docs/src/format/theme/README.md
  50. 0 1
      docs/src/format/theme/editor.md
  51. 0 1
      docs/src/format/theme/index-hbs.md
  52. 0 1
      docs/src/format/theme/syntax-highlighting.md
  53. 0 37
      docs/src/gettingstarted/fromjs.md
  54. 0 26
      docs/src/gettingstarted/webapps.md
  55. 33 31
      docs/src/hello_world.md
  56. BIN
      docs/src/images/helloworld.png
  57. BIN
      docs/src/images/reddit.png
  58. BIN
      docs/src/images/reddit_post.png
  59. BIN
      docs/src/images/reddit_post_components.png
  60. 0 16
      docs/src/platforms/00-index.md
  61. 0 3
      docs/src/platforms/01-ssr.md
  62. 0 3
      docs/src/platforms/02-wasm.md
  63. 0 3
      docs/src/platforms/03-desktop.md
  64. 0 21
      docs/src/platforms/04-concurrency.md
  65. 0 20
      docs/src/platforms/05-liveview.md
  66. 0 31
      docs/src/platforms/06-components.md
  67. 11 7
      docs/src/setup.md
  68. 1 0
      docs/src/tutorial/advanced_guides.md
  69. 1 0
      docs/src/tutorial/components.md
  70. 13 0
      docs/src/tutorial/index.md
  71. 1 0
      docs/src/tutorial/new_app.md
  72. 1 0
      docs/src/tutorial/publishing.md
  73. 1 0
      docs/src/tutorial/state.md
  74. 1 0
      docs/src/tutorial/structure.md
  75. 1 0
      docs/src/tutorial/styling.md
  76. 11 0
      examples/hello_world.rs
  77. 1 153
      packages/core-macro/src/lib.rs
  78. 3 3
      packages/core-macro/src/props/mod.rs
  79. 9 42
      packages/core-macro/src/rsx/ambiguous.rs
  80. 5 19
      packages/core-macro/src/rsx/body.rs
  81. 15 164
      packages/core-macro/src/rsx/component.rs
  82. 144 282
      packages/core-macro/src/rsx/element.rs
  83. 6 26
      packages/core-macro/src/rsx/fragment.rs
  84. 0 4
      packages/core-macro/src/rsx/mod.rs
  85. 5 27
      packages/core-macro/src/rsx/node.rs
  86. 2 2
      packages/core/Cargo.toml
  87. 2 2
      packages/core/examples/async.rs
  88. 1 0
      packages/core/examples/expand.rs
  89. 5 2
      packages/core/examples/jsframework.rs
  90. 4 22
      packages/core/examples/syntax.rs
  91. 39 19
      packages/core/src/diff.rs
  92. 2 0
      packages/core/src/events.rs
  93. 1 0
      packages/core/src/hooklist.rs
  94. 4 0
      packages/core/src/nodes.rs
  95. 0 2
      packages/core/src/scheduler.rs
  96. 6 0
      packages/core/src/scope.rs
  97. 12 8
      packages/core/tests/borrowedstate.rs
  98. 2 0
      packages/core/tests/create_dom.rs
  99. 1 0
      packages/core/tests/diffing.rs
  100. 2 5
      packages/core/tests/display_vdom.rs

+ 1 - 1
README.md

@@ -49,7 +49,7 @@
 Dioxus is a portable, performant, and ergonomic framework for building cross-platform user experiences in Rust.
 
 ```rust
-static App: FC<()> = |(cx, props)| {
+fn App((cx, props): Component<()>) -> Element {
     let mut count = use_state(cx, || 0);
 
     cx.render(rsx!(

+ 0 - 15
docs/blog/01-release.md

@@ -1,15 +0,0 @@
-
-- Rust's smart pointers make use_state extremely easy and fun to work with.
-- Multiple flavors (raw, html, rsx) of templating to pick-and-choose for the type of component.
-- Extensible DSL through traits and impls plus support for WebComponents.
-- Simple-to-implement trait for new reconcilers.
-- Fast retained-mode server-side-renderer that works with buffered writers, files, and strings.
-- Compile-time correct inline CSS as well as global styling.
-- Integrated "signal" system allows for near-instant updates by skipping the diff algorithm entirely.
-- Built-in cooperative scheduler (IE React's fiber mechanism).
-- Built-in asynchronous scheduler for coroutines and tasks within components.
-- Built-in suspense scheduler for Rust's futures.
-- Extremely fast diffing algorithm for the most complex of apps.
-- Runs natively on mobile and on desktop with no need for a 3rd party JS engine.
-- Drastically fewer runtime errors and crashes with first-class error handling.
-- Extremely powerful iterator and optional chaining integration for fast and robust apps.

+ 13 - 11
docs/src/README.md

@@ -4,9 +4,8 @@
 
 **Dioxus** is a framework and ecosystem for building fast, scalable, and robust user interfaces with the Rust programming language. This guide will help you get up-and-running with Dioxus running on the Web, Desktop, Mobile, and more.
 
-```rust, ignore
-// An example Dioxus app - closely resembles React
-static App: FC<()> = |(cx, props)| {
+```rust
+fn App((cx, props): Component<()>) -> Element {
     let mut count = use_state(cx, || 0);
 
     cx.render(rsx!(
@@ -20,7 +19,6 @@ static App: FC<()> = |(cx, props)| {
 The Dioxus API and patterns closely resemble React - if this guide is lacking in any general concept or an error message is confusing, we recommend substituting "React" for "Dioxus" in your web search terms. A major goal of Dioxus is to provide a familiar toolkit for UI in Rust, so we've chosen to follow in the footsteps of popular UI frameworks (React, Redux, etc) - if you know React, then you already know Dioxus. If you don't know either, this guide will still help you!
 
 
-
 ### Web Support
 ---
 
@@ -79,23 +77,27 @@ Examples:
 ---
 Mobile is currently the least-supported renderer target for Dioxus. Mobile apps are rendered with the platform's WebView, meaning that animations, transparency, and native widgets are not currently achievable. In addition, iOS is the only supported Mobile Platform. It is possible to get Dioxus running on Android and rendered with WebView, but the Rust windowing library that Dioxus uses - tao - does not currently supported Android.
 
+Mobile support is currently best suited for CRUD-style apps, ideally for internal teams who need to develop quickly but don't care much about animations or native widgets.
+
 [Jump to the getting started guide for Mobile.]()
 
 Examples:
 - [Todo App]()
 - [Chat App]()
 
-### LiveView Support
+### LiveView / Server Component Support
 ---
 
-The internal architecture of Dioxus was designed from day one to support the `LiveView` use-case, where a web server hosts a running app for each connected user. As of today, there is no out-of-the-box LiveView support - you'll need to wire this up yourself. While not currently fully implemented, the expectation is that LiveView apps can be a hybrid between Wasm and server-rendered where only portions of a page are "live" and the rest of the page is either server-rendered, statically generated, or handled by the host SPA.
-
+The internal architecture of Dioxus was designed from day one to support the `LiveView` use-case, where a web server hosts a running app for each connected user. As of today, there is no first-class LiveView support - you'll need to wire this up yourself. 
 
+While not currently fully implemented, the expectation is that LiveView apps can be a hybrid between Wasm and server-rendered where only portions of a page are "live" and the rest of the page is either server-rendered, statically generated, or handled by the host SPA.
 
 ### Multithreaded Support
 ---
-The Dioxus VirtualDom, sadly, is not `Send.` This means you can't easily use Dioxus with most web frameworks like Tide, Rocket, Axum, etc. Currently, your two options include:
-- Actix: Actix supports `!Send` handlers
-- VirtualDomPool: A thread-per-core VirtualDom pool that uses message passing and serialization
+The Dioxus VirtualDom, sadly, is not currently `Send`. Internally, we use quite a bit of interior mutability which is not thread-safe. This means you can't easily use Dioxus with most web frameworks like Tide, Rocket, Axum, etc. 
+
+To solve this, you'll want to spawn a VirtualDom on its own thread and communicate with it via channels.
+
+When working with web frameworks that require `Send`, it is possible to render a VirtualDom immediately to a String - but you cannot hold the VirtualDom across an await point. For retained-state SSR (essentially LiveView), you'll need to create a pool of VirtualDoms.
 
-When working with web frameworks that require `Send`, it is possible to render a VirtualDom immediately to a String - but you cannot hold the VirtualDom across an await point. For retained-state SSR (essentially LiveView), you'll need to create a VirtualDomPool which solves the `Send` problem.
+Ultimately, you can always wrap the VirtualDom with a `Send` type and manually uphold the `Send` guarantees yourself.

+ 61 - 43
docs/src/SUMMARY.md

@@ -3,22 +3,47 @@
 - [Introduction](README.md)
 - [Getting Setup](setup.md)
 - [Hello, World!](hello_world.md)
-- [Core Topics](concepts/00-index.md)
-    - [Intro to VNodes](concepts/vnodes.md)
-    - [VNodes with RSX, HTML, and NodeFactory](concepts/rsx.md)
-    - [RSX in Depth](concepts/rsx_in_depth.md)
-    - [Components and Props](concepts/components.md)
-    - [Memoization](concepts/memoization.md)
-    - [Hooks and Internal State](concepts/hooks.md)
-    - [Event handlers](concepts/event_handlers.md)
-    - [Global State](concepts/sharedstate.md)
-    - [User Input and Controlled Components](concepts/errorhandling.md)
-    - [Error handling](concepts/errorhandling.md)
-- [Reference Guide]()
-- [Advanced Guides]()
-  - [Custom Renderer]()
-  - [LiveView Support]()
-  - [Bundling and Distributing]()
+- [Describing the UI](concepts/00-index.md)
+  - [Intro to Elements](concepts/vnodes.md)
+  - [Intro to Components](concepts/components.md)
+  - [Reusing, Importing, and Exporting Components](concepts/exporting_components.md)
+  - [Conditional Rendering](concepts/conditional_rendering.md)
+  - [Lists](concepts/lists.md)
+- [Adding Interactivity](concepts/interactivity.md)
+  - [Event handlers](concepts/event_handlers.md)
+  - [User Input and Controlled Components](concepts/errorhandling.md)
+  - [Lifecycle, updates, and effects](concepts/lifecycles.md)
+- [Managing State](concepts/managing_state.md)
+  - [Hooks and Internal State](concepts/hooks.md)
+  - [Global State](concepts/sharedstate.md)
+  - [Error handling](concepts/errorhandling.md)
+  - [Effects](concepts/effects.md)
+- [Working with Async](concepts/async.md)
+  - [Tasks](concepts/asynctasks.md)
+  - [Suspense](concepts/suspense.md)
+  - [Async Callbacks](concepts/asynccallbacks.md)
+- [Putting it all together](tutorial/index.md)
+  - [New app](tutorial/new_app.md)
+  - [Structuring our app](tutorial/structure.md)
+  - [Defining State](tutorial/state.md)
+  - [Defining Components](tutorial/components.md)
+  - [Styling](tutorial/styling.md)
+  - [Publishing](tutorial/publishing.md)
+- [Topics in Depth](depth/topics.md)
+  - [RSX](depth/rsx.md)
+  - [Components](depth/components.md)
+  - [Props](depth/props.md)
+  - [Memoization](depth/memoization.md)
+  - [Performance](depth/performance.md)
+  - [Testing](depth/testing.md)
+- [Advanced Guides](tutorial/advanced_guides.md)
+  - [Memoization](concepts/memoization.md)
+  - [RSX in Depth](concepts/rsx_in_depth.md)
+  - [Building Elements with NodeFactory](concepts/rsx.md)
+  - [Custom Elements](concepts/custom_elements.md)
+  - [Custom Renderer](concepts/custom_renderer.md)
+  - [Server-side components](concepts/server_side_components.md)
+  - [Bundling and Distributing](concepts/bundline.md)
 - [Web]()
   - [Getting Started]()
   - [Down-casting Nodes]()
@@ -29,33 +54,26 @@
   - [Wrapping Web APIs]()
 - [Mobile]()
   - [Wrapping Web APIs]()
-
-
-<!-- - [Command Line Tool](cli/README.md)
-    - [init](cli/init.md)
-    - [build](cli/build.md)
-    - [watch](cli/watch.md)
-    - [serve](cli/serve.md)
-    - [test](cli/test.md)
-    - [clean](cli/clean.md)
-- [Format](format/README.md)
-    - [SUMMARY.md](format/summary.md)
-        - [Draft chapter]()
-    - [Configuration](format/configuration/README.md)
-        - [General](format/configuration/general.md)
-        - [Preprocessors](format/configuration/preprocessors.md)
-        - [Renderers](format/configuration/renderers.md)
-        - [Environment Variables](format/configuration/environment-variables.md)
-    - [Theme](format/theme/README.md)
-        - [index.hbs](format/theme/index-hbs.md)
-        - [Syntax highlighting](format/theme/syntax-highlighting.md)
-        - [Editor](format/theme/editor.md)
-    - [MathJax Support](format/mathjax.md)
-    - [mdBook-specific features](format/mdbook.md)
-- [Continuous Integration](continuous-integration.md)
-- [For Developers](for_developers/README.md)
-    - [Preprocessors](for_developers/preprocessors.md)
-    - [Alternative Backends](for_developers/backends.md) -->
+- [Reference Guide]()
+  - [Anti-patterns]()
+  - [Children]()
+  - [Conditional Rendering]()
+  - [Controlled Inputs]()
+  - [Custom Elements]()
+  - [Empty Components]()
+  - [Error Handling]()
+  - [Fragments]()
+  - [Global CSS]()
+  - [Inline Styles]()
+  - [Iterators]()
+  - [Listeners]()
+  - [Memoization]()
+  - [Node Refs]()
+  - [Spread Pattern]()
+  - [State Management]()
+  - [Suspense]()
+  - [task]()
+  - [Testing]()
 
 -----------
 

+ 0 - 0
docs/src/concepts/07-state-management.md → docs/src/advanced-guides/rsx-in-depth.md


+ 0 - 1
docs/src/cli/README.md

@@ -1 +0,0 @@
-# Command Line Tool

+ 0 - 1
docs/src/cli/build.md

@@ -1 +0,0 @@
-# build

+ 0 - 1
docs/src/cli/clean.md

@@ -1 +0,0 @@
-# clean

+ 0 - 1
docs/src/cli/init.md

@@ -1 +0,0 @@
-# init

+ 0 - 1
docs/src/cli/serve.md

@@ -1 +0,0 @@
-# serve

+ 0 - 1
docs/src/cli/test.md

@@ -1 +0,0 @@
-# test

+ 0 - 1
docs/src/cli/watch.md

@@ -1 +0,0 @@
-# watch

+ 0 - 16
docs/src/concepts/04-hooks.md

@@ -1,16 +0,0 @@
-```rust
-fn Example<'a>(cx: Context<'a>, props: &()) -> DomTree<'a> {
-    let service = use_combubulator(cx);
-    let Status { name, pending, count } = service.info();
-    html! {
-        <div>
-            <p> "Hello, {name}!" </p>
-            <p> "Status: {pending}!" </p>
-            <p> "Count {count}!" </p>
-            <button onclick={|_| service.update()}>
-                "Refresh services"
-            </button>
-        </div>
-    }
-}
-```

+ 0 - 45
docs/src/concepts/05-context-api.md

@@ -1,45 +0,0 @@
-# Context API
-
-```rust
-// Create contexts available to children
-// Only one context can be associated with any given component
-// This is known as "exposed state". Children can access this context,
-// but will not be automatically subscribed.
-fn ContextCreate(cx: &mut Context<()>) -> DomTree {
-    let context = cx.set_context(|| CustomContext::new());
-    html! { <> {cx.children()} </> }
-}
-
-fn ContextRead(cx: &mut Context<()>) -> DomTree {
-    // Panics if context is not available
-    let some_cx = cx.get_context::<CustomContext>();
-    let text = some_cx.select("some_selector");
-    html! { <div> "{text}" </div> }
-}
-
-fn Subscription(cx: &mut Context<()>) -> DomTree {
-    // Open a "port" on the component for actions to trigger a re-evaluation
-    let subscription = cx.new_subscription();
-
-    // A looping timer - the effect is re-called on every re-evaluation
-    use_async_effect(cx, move || async {
-        timer::new(2000).await;
-        subscription.call();
-    }, None);
-
-    // A one-shot timer, the deps don't change so the effect only happens once
-    use_async_effect_deps(cx, move || async {
-        timer::new(2000).await;
-        subscription.call();
-    }, ());
-}
-
-// Mix subscriptions and context to make a simple Redux
-fn use_global_state<T: UserContextTrait>(cx: &mut Context<()>) -> T {
-    let some_cx = cx.get_context::<T>();
-    let component_subscription = cx.new_subscription();
-    some_cx.subscribe_component(component_subscription);
-    some_cx
-}
-
-```

+ 0 - 0
docs/src/concepts/09-interactivity.md


+ 0 - 18
docs/src/concepts/9901-hello-world.md

@@ -1,18 +0,0 @@
-# Hello, World!
-
-Dioxus should look and feel just like writing functional React components. In Dioxus, there are no class components with lifecycles. All state management is done via hooks. This encourages logic reusability and lessens the burden on Dioxus to maintain a non-breaking lifecycle API.
-
-```rust
-#[derive(Properties, PartialEq)]
-struct MyProps {
-    name: String
-}
-
-fn Example(cx: Context<MyProps>) -> DomTree {
-    cx.render(html! {
-        <div> "Hello {cx.name}!" </div>
-    })
-}
-```
-
-For functions to be valid components, they must take the `Context` object which is generic over some properties. The properties parameter must implement the `Properties` trait, which can be automatically derived. Whenever the input properties of a component changes, the function component will be re-ran and a new set of VNodes will be generated.

+ 1 - 0
docs/src/concepts/async.md

@@ -0,0 +1 @@
+# Working with Async

+ 1 - 0
docs/src/concepts/asynccallbacks.md

@@ -0,0 +1 @@
+# Async Callbacks

+ 1 - 0
docs/src/concepts/asynctasks.md

@@ -0,0 +1 @@
+# Tasks

+ 1 - 0
docs/src/concepts/bundline.md

@@ -0,0 +1 @@
+# Bundling and Distributing

+ 171 - 8
docs/src/concepts/components.md

@@ -1,18 +1,181 @@
-# Components and Props
+# Introduction to Components
 
-Dioxus should look and feel just like writing functional React components. In Dioxus, there are no class components with lifecycles. All state management is done via hooks. This encourages logic reusability and lessens the burden on Dioxus to maintain a non-breaking lifecycle API.
+In the previous chapter, we learned about Elements and how they can be composed to create a basic User Interface. In this chapter, we'll learn how to group Elements together to form Components.
 
+## What is a component?
+
+In short, a component is a special function that takes an input and outputs a group of Elements. Typically, Components serve a single purpose: group functionality of a User Interface. Much like a function encapsulates some specific computation task, a Component encapsulates some specific rendering task.
+
+### Learning through prior art
+
+Let's take a look at a post on r/rust and see if we can sketch out a component representation.
+
+![Reddit Post](../images/reddit_post.png)
+
+This component has a bunch of important information:
+
+- The score
+- The number of comments
+- How long ago it was posted
+- The url short address
+- The title
+- The username of the original poster
+
+If we wanted to sketch out these requirements in Rust, we would start with a struct:
+
+```rust
+struct Post {
+    score: i32,
+    comment_count: u32,
+    post_time: Instant,
+    url: String,
+    title: String,
+    original_poster_name: String
+}
+```
+
+If we look at the layout of the component, we notice quite a few buttons and functionality:
+
+- Upvote/Downvote
+- View comments
+- Share
+- Save
+- Hide
+- Give award
+- Report
+- Crosspost
+- Filter by site
+- View article
+- Visit user 
+
+If we included all this functionality in one `rsx!` call, it would be huge! Instead, let's break the post down into some core pieces:
+
+![Post as Component](../images/reddit_post_components.png)
+
+- **VoteButtons**: Upvote/Downvote
+- **TitleCard**: Title, Filter-By-Url
+- **MetaCard**: Original Poster, Time Submitted
+- **ActionCard**: View comments, Share, Save, Hide, Give award, Report, Crosspost
+
+### Modeling with Dioxus
+
+When designing these components, we can start by sketching out the hierarchy using Dioxus. In general, our "Post" component will simply be comprised of our four sub-components. We would start the process by defining our "Post" component. Our component will take in all of the important data we listed above as part of its input.
+
+Unlike normal functions, Dioxus components must explicitly define a single struct to contain all the inputs. These are commonly called "Properties" (props). Our component will be a combination of these properties and a function to render them.
+
+Our props must implement the `Properties` trait and - if the component does not borrow any data - `PartialEq`. Both of these can be done automatically through derive macros:
 ```rust
 #[derive(Properties, PartialEq)]
-struct MyProps {
-    name: String
+struct PostProps {
+    id: Uuid,
+    score: i32,
+    comment_count: u32,
+    post_time: Instant,
+    url: String,
+    title: String,
+    original_poster: String
+}
+```
+
+And our render function:
+```rust
+fn Post((cx, props): Component<PostProps>) -> Element {
+    cx.render(rsx!{
+        div { class: "post-container"
+            VoteButtons {
+                score: props.score,
+            }
+            TitleCard {
+                title: props.title,
+                url: props.url,
+            }
+            MetaCard {
+                original_poster: props.original_poster,
+                post_time: props.post_time,
+            }
+            ActionCard {
+                post_id: props.id
+            }
+        }
+    })
+}
+```
+
+When we render components, we use the traditional Rust struct syntax to declare their properties. Dioxus will automatically call "into" on the property fields, cloning when necessary. Notice how our `Post` component is simply a collection of important smaller components wrapped together in a single container.
+
+Let's take a look at the `VoteButtons` component. For now, we won't include any interactivity - just the rendering the vote buttons and score to the screen.
+
+Most of your Components will look exactly like this: a Props struct and a render function. As covered before, we'll build our User Interface with the `rsx!` macro and HTML tags. However, with components, we must actually "render" our HTML markup. Calling `cx.render` converts our "lazy" `rsx!` structure into an `Element`. Every component must take a tuple of `Context` and `&Props` and return an `Element`.
+```rust
+
+#[derive(PartialEq, Props)]
+struct VoteButtonsProps {
+    score: i32
+}
+
+fn VoteButtons((cx, props): Component<VoteButtonsProps>) -> Element {
+    cx.render(rsx!{
+        div { class: "votebuttons"
+            div { class: "arrow up" }
+            div { class: "score", "{props.score}"}
+            div { class: "arrow down" }
+        }
+    })
+}
+```
+
+## Borrowing
+
+You can avoid clones using borrowed component syntax. For example, let's say we passed the TitleCard title as an `&str` instead of `String`. In JavaScript, the string would simply be copied by reference - none of the contents would be copied, but rather the reference to the string's contents are copied. In Rust, this would be similar to calling `clone` on `Rc<str>`.
+
+Because we're working in Rust, we can choose to either use `Rc<str>`, clone `Title` on every re-render of `Post`, or simply borrow it. In most cases, you'll just want to let `Title` be cloned. 
+
+To enable borrowed values for your component, we need to add a lifetime to let the Rust compiler know that the output `Element` borrows from the component's props.
+
+```rust
+#[derive(Properties)]
+struct TitleCardProps<'a> {
+    title: &'a str,
+}
+
+fn TitleCard<'a>((cx, props): Component<'a, TitleCardProps>) -> Element<'a> {
+    cx.render(rsx!{
+        h1 { "{props.title}" }
+    })
+}   
+```
+
+## The `Context` object
+
+Though very similar with React, Dioxus is different in a few ways. Most notably, React components will not have a `Context` parameter in the component declaration. 
+
+Have you ever wondered how the `useState()` call works in React without a `this` object to actually store the state? 
+
+React uses global variables to store this information which must be carefully managed, especially in environments with multiple React roots - like the server.
+
+```javascript
+function Component({}) {
+    let [state, set_state] = useState(10);
 }
+```
+
 
-fn Example(cx: Context<MyProps>) -> DomTree {
-    html! { <div> "Hello {cx.cx.name}!" </div> }
+Because Dioxus needs to work with the rules of Rust, we need to provide a way for the component to do some internal bookkeeping. That's what the `Context` object is: a place for the component to store state, manage listeners, and allocate elements. Advanced users of Dioxus will want to learn how to properly leverage the `Context` object to build robust, performant extensions for Dioxus.
+
+```rust
+fn Post((cx /* <-- our Context object*/, props): Component<PostProps>) -> Element {
+    cx.render(rsx!{ })
 }
 ```
 
-Here, the `Context` object is used to access hook state, create subscriptions, and interact with the built-in context API. Props, children, and component APIs are accessible via the `Context` object. The functional component macro makes life more productive by inlining props directly as function arguments, similar to how Rocket parses URIs.
+## Moving forward
+
+Next chapter, we'll talk about composing Elements and Components across files to build a larger Dioxus App.
+
+For more references on components, make sure to check out:
+
+- [Components in depth]()
+- [Lifecycles]()
+- [The Context object]()
+- [Optional Prop fields]()
 
-The final output of components must be a tree of VNodes. We provide an html macro for using JSX-style syntax to write these, though, you could use any macro, DSL, templating engine, or the constructors directly.

+ 1 - 0
docs/src/concepts/conditional_rendering.md

@@ -0,0 +1 @@
+# Conditional Rendering

+ 1 - 0
docs/src/concepts/custom_elements.md

@@ -0,0 +1 @@
+# Custom Elements

+ 1 - 0
docs/src/concepts/custom_renderer.md

@@ -0,0 +1 @@
+# Custom Renderer

+ 1 - 0
docs/src/concepts/effects.md

@@ -0,0 +1 @@
+# Effects

+ 291 - 0
docs/src/concepts/exporting_components.md

@@ -0,0 +1,291 @@
+
+# Reusing, Importing, and Exporting Components
+
+As your application grows in size, you'll want to start breaking your UI into components and, eventually, different files. This is a great idea to encapsulate functionality of your UI and scale your team.
+
+Let's say our app looks something like this:
+
+```shell
+├── Cargo.toml
+└── src
+    └── main.rs
+```
+
+```rust
+// main.rs
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus::desktop::launch(App, |c| c);
+}
+
+fn App((cx, props): Component<()>) -> Element {} 
+
+#[derive(PartialEq, Props)]
+struct PostProps{}
+fn Post((cx, props): Component<PostProps>) -> Element {} 
+
+#[derive(PartialEq, Props)]
+struct VoteButtonsProps {}
+fn VoteButtons((cx, props): Component<VoteButtonsProps>) -> Element {} 
+
+#[derive(PartialEq, Props)]
+struct TitleCardProps {}
+fn TitleCard((cx, props): Component<TitleCardProps>) -> Element {} 
+
+#[derive(PartialEq, Props)]
+struct MetaCardProps {}
+fn MetaCard((cx, props): Component<MetaCardProps>) -> Element {} 
+
+#[derive(PartialEq, Props)]
+struct ActionCardProps {}
+fn ActionCard((cx, props): Component<ActionCardProps>) -> Element {} 
+```
+
+That's a lot of components for one file! We've successfully refactored our app into components, but we should probably start breaking it up into a file for each component.
+
+## Breaking into different files
+
+Fortunately, Rust has a built-in module system that's much cleaner than what you might be used to in JavaScript. Because `VoteButtons`, `TitleCard`, `MetaCard`, and `ActionCard` all belong to the `Post` component, let's put them all in a folder together called "post". We'll make a file for each component and move the props and render function.
+
+```rust
+// src/post/action.rs
+
+use dioxus::prelude::*;
+
+#[derive(PartialEq, Props)]
+struct ActionCardProps {}
+fn ActionCard((cx, props): Component<ActionCardProps>) -> Element {} 
+```
+
+We should also create a `mod.rs` file in the `post` folder so we can use it from our `main.rs`. Our `Post` component and its props will go into this file.
+
+```shell
+├── Cargo.toml
+└── src
+    ├── main.rs
+    └── post
+        ├── vote.rs
+        ├── title.rs
+        ├── meta.rs
+        ├── action.rs
+        └── mod.rs
+```
+
+
+
+In our `main.rs`, we'll want to declare the `post` module so we can access our `Post` component.
+
+```rust
+// main.rs
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus::desktop::launch(App, |c| c);
+}
+
+mod post;
+
+fn App((cx, props): Component<()>) -> Element {
+    cx.render(rsx!{
+        post::Post {
+            id: Uuid::new_v4(),
+            score: 10,
+            comment_count: 10,
+            post_time: std::Instant::now(),
+            url: "example".to_string(),
+            title: "Title".to_string(),
+            original_poster: "me".to_string()
+        }
+    })
+} 
+```
+
+If you tried to build this app right now, you'll get an error message saying that `Post is private, trying changing it to public`. This is because we haven't properly exported our component! To fix this, we need to make sure both the Props and Component are declared as "public":
+
+```rust
+// src/post/mod.rs
+
+use dioxus::prelude::*;
+
+#[derive(PartialEq, Props)]
+pub struct PostProps {}
+pub fn Post((cx, props): Component<PostProps>) -> Element {} 
+```
+
+While we're here, we also need to make sure each of our subcomponents are included as modules and exported.
+
+Our "post/mod.rs" file will eventually look like this:
+
+```rust
+use dioxus::prelude::*;
+
+mod vote;
+mod title;
+mod meta;
+mod action;
+
+#[derive(Properties, PartialEq)]
+pub struct PostProps {
+    id: uuid::Uuid,
+    score: i32,
+    comment_count: u32,
+    post_time: std::time::Instant,
+    url: String,
+    title: String,
+    original_poster: String
+}
+
+pub fn Post((cx, props): Component<PostProps>) -> Element {
+    cx.render(rsx!{
+        div { class: "post-container"
+            vote::VoteButtons {
+                score: props.score,
+            }
+            title::TitleCard {
+                title: props.title,
+                url: props.url,
+            }
+            meta::MetaCard {
+                original_poster: props.original_poster,
+                post_time: props.post_time,
+            }
+            action::ActionCard {
+                post_id: props.id
+            }
+        }
+    })
+}
+```
+
+
+Ultimately, including and exporting components is governed by Rust's module system. [The Rust book is a great resource to learn about these concepts in greater detail.](https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html)
+
+## Final structure:
+
+```shell
+├── Cargo.toml
+└── src
+    ├── main.rs
+    └── post
+        ├── vote.rs
+        ├── title.rs
+        ├── meta.rs
+        ├── action.rs
+        └── mod.rs
+```
+
+```rust
+// main.rs:
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus::desktop::launch(App, |c| c);
+}
+
+mod post;
+
+fn App((cx, props): Component<()>) -> Element {
+    cx.render(rsx!{
+        post::Post {
+            id: Uuid::new_v4(),
+            score: 10,
+            comment_count: 10,
+            post_time: std::Instant::now(),
+            url: "example".to_string(),
+            title: "Title".to_string(),
+            original_poster: "me".to_string()
+        }
+    })
+} 
+```
+
+
+```rust
+// src/post/mod.rs
+use dioxus::prelude::*;
+
+mod vote;
+mod title;
+mod meta;
+mod action;
+
+#[derive(Properties, PartialEq)]
+pub struct PostProps {
+    id: uuid::Uuid,
+    score: i32,
+    comment_count: u32,
+    post_time: std::time::Instant,
+    url: String,
+    title: String,
+    original_poster: String
+}
+
+pub fn Post((cx, props): Component<PostProps>) -> Element {
+    cx.render(rsx!{
+        div { class: "post-container"
+            vote::VoteButtons {
+                score: props.score,
+            }
+            title::TitleCard {
+                title: props.title,
+                url: props.url,
+            }
+            meta::MetaCard {
+                original_poster: props.original_poster,
+                post_time: props.post_time,
+            }
+            action::ActionCard {
+                post_id: props.id
+            }
+        }
+    })
+}
+```
+
+```rust
+// src/post/vote.rs
+use dioxus::prelude::*;
+
+#[derive(PartialEq, Props)]
+pub struct VoteButtonsProps {}
+pub fn VoteButtons((cx, props): Component<VoteButtonsProps>) -> Element {} 
+```
+
+```rust
+// src/post/title.rs
+use dioxus::prelude::*;
+
+#[derive(PartialEq, Props)]
+pub struct TitleCardProps {}
+pub fn TitleCard((cx, props): Component<TitleCardProps>) -> Element {} 
+```
+
+```rust
+// src/post/meta.rs
+use dioxus::prelude::*;
+
+#[derive(PartialEq, Props)]
+pub struct MetaCardProps {}
+pub fn MetaCard((cx, props): Component<MetaCardProps>) -> Element {} 
+```
+
+```rust
+// src/post/action.rs
+use dioxus::prelude::*;
+
+#[derive(PartialEq, Props)]
+pub struct ActionCardProps {}
+pub fn ActionCard((cx, props): Component<ActionCardProps>) -> Element {} 
+```
+
+## Moving forward
+
+Next chapter, we'll start to add use code to hide and show Elements with conditional rendering.
+
+For more reading on components:
+
+- [Components in depth]()
+- [Lifecycles]()
+- [The Context object]()
+- [Optional Prop fields]()

+ 1 - 0
docs/src/concepts/interactivity.md

@@ -0,0 +1 @@
+# Adding Interactivity

+ 1 - 0
docs/src/concepts/lifecycles.md

@@ -0,0 +1 @@
+# Lifecycle, updates, and effects

+ 1 - 0
docs/src/concepts/managing_state.md

@@ -0,0 +1 @@
+# Managing State

+ 1 - 0
docs/src/concepts/server_side_components.md

@@ -0,0 +1 @@
+# Server-side components

+ 1 - 0
docs/src/concepts/suspense.md

@@ -0,0 +1 @@
+# Suspense

+ 124 - 35
docs/src/concepts/vnodes.md

@@ -1,64 +1,153 @@
-# VNodes and Elements
+# Declaring your first UI with Elements
 
-At the heart of Dioxus is the concept of an "element" - a container that can have children, properties, event handlers, and other important attributes. Dioxus only knows how to render the `VNode` data structure - an Enum variant of an Element, Text, Components, Fragments, and Anchors.
+Every user interface you've ever used is just a symphony of tiny widgets working together to abstract over larger complex functions. In Dioxus, we call these tiny widgets "Elements." Using Components, you can easily compose Elements into larger groups to form even larger structures: Apps.
 
-Because Dioxus is meant for the Web and uses WebView as a desktop and mobile renderer, almost all elements in Dioxus share properties with their HTML counterpart. When we declare our elements, we'll do so using HTML semantics:
+Because Dioxus is mostly used with HTML/CSS renderers, the default Element "collection" is HTML. Provided the `html` feature is not disabled, we can declare Elements using the `rsx!` macro:
 
 ```rust
+#use dioxus::prelude::*;
+rsx!(
+    div {}
+)
+```
+As you might expect, we can render this call using Dioxus-SSR to produce valid HTML:
+
+```rust
+#use dioxus::prelude::*;
+dioxus::ssr::render_lazy(rsx!(
+    div {}
+))
+```
+
+Produces:
+```html
+<div></div>
+```
+
+We can construct any valid HTML tag with the `tag {}` pattern and expect the resulting HTML structure to resemble our declaration.
+## Composing Elements
+
+Of course, we need more complex structures to make our apps actually useful! Just like HTML, the `rsx!` macro lets us nest Elements inside of each other.
+
+```rust
+#use dioxus::prelude::*;
 rsx!(
     div {
-        "hello world"
+        h1 {}
+        h2 {}
+        p {}
     }
 )
 ```
+As you might expect, the generated HTML for this structure would look like:
+```html
+<div>
+    <h1></h1>
+    <h2></h2>
+    <p></p>
+</div>
+```
+
+With the default configuration, any Element defined within the `dioxus-html` crate can be declared in this way. To create your own new elements, see the `Custom Elements` Advanced Guide.
 
-As you would expect, this snippet would generate a simple hello-world div. In fact, we can render these nodes directly with the SSR crate:
+## Text Elements
+
+Dioxus also supports a special type of Element: Text. Text Elements do not accept children, but rather just string literals denoted with double quotes.
 
 ```rust
-dioxus::ssr::render_lazy(rsx!(
+rsx! (
+    "hello world"
+)
+```
+
+Text Elements can be composed within other Elements:
+```rust
+rsx! (
     div {
-        "hello world"
+        h1 { "hello world" }
+        p { "Some body content" }
     }
-))
+)
 ```
 
-And produce the corresponding html structure:
-```html
-<div>hello world</div>
+Text can also be formatted with any value that implements `Display`. We use [f-string formatting](https://docs.rs/fstrings/0.2.3/fstrings/) - a "coming soon" feature for stable Rust that is familiar for Python and JavaScript users:
+
+```rust
+let name = "Bob";
+rsx! ( "hello {name}" )
 ```
 
-Our structure declared above is made of two variants of the `VNode` data structure:
-- A VElement with a tag name of `div`
-- A VText with contents of `"hello world"`
+Unfortunately, you cannot drop in arbitrary expressions directly into the string literal. In the cases where we need to compute a complex value, we'll want to use `format_args!` directly. Due to specifics of how the `rsx!` macro (we'll cover later), our call to `format_args` must be contained within curly braces *and* square braces.
 
-## All the VNode types
+```rust
+rsx!( {[format_args!("Hello {}", if enabled { "Jack" } else { "Bob" } )]} )
+```
 
-VNodes can be any of:
-- **Element**: a container with a tag name, namespace, attributes, children, and event listeners
-- **Text**: bump allocated text derived from string formatting
-- **Fragments**: a container of elements with no parent
-- **Suspended**: a container for nodes that aren't yet ready to be rendered
-- **Anchor**: a special type of node that is only available when fragments have no children
+This is different from React's way of generating arbitrary markup but fits within idiomatic Rust. 
 
-In practice, only elements and text can be initialized directly while other node types can only be created through hooks or NodeFactory methods.
+Typically, with Dioxus, you'll just want to compute your substrings outside of the `rsx!` call:
 
-## Bump Arena Allocation
+```rust
+let name = if enabled { "Jack" } else { "Bob" };
+rsx! ( "hello {name}" )
+```
+
+## Attributes
+
+Every Element in your User Interface will have some sort of properties that the renderer will use when drawing to the screen. These might inform the renderer if the component should be hidden, what its background color should be, or to give it a specific name or ID.
+
+To do this, we use the familiar struct-style syntax that Rust provides. Commas are optional:
+
+```rust
+rsx!(
+    div {
+        hidden: true,
+        background_color: "blue",
+        class: "card color-{mycolor}"
+    }
+)
+```
+
+Each field is defined as a method on the element in the `dioxus-html` crate. This prevents you from misspelling a field name and lets us provide inline documentation. When you need to use a field not defined as a method, you have two options:
+
+1) file an issue if the attribute _should_ be enabled
+2) add a custom attribute on-the-fly
+
+To use custom attributes, simply put the attribute name in quotes followed by a colon:
 
-To speed up the process of building our elements and text, Dioxus uses a special type of memory allocator tuned for large batches of small allocations called a Bump Arena. We use the `bumpalo` allocator which was initially developed for Dioxus' spiritual predecessor: `Dodrio.`
+```rust
+rsx!(
+    div {
+        "customAttr": "important data here"
+    }
+)
+```
 
-- Bumpalo: [https://github.com/fitzgen/bumpalo](https://github.com/fitzgen/bumpalo)
-- Dodrio: [https://github.com/fitzgen/dodrio](https://github.com/fitzgen/dodrio)
+Note: the name of the custom attribute must match exactly what you want the renderer to output. All attributes defined as methods in `dioxus-html` follow the snake_case naming convention. However, they internally translate their snake_case convention to HTML's camelCase convention.
 
-In other frontend frameworks for Rust, nearly every string is allocated using the global allocator. This means that strings in Rust do not benefit from the immutable string interning optimizations that JavaScript engines employ. By using a smaller, faster, more limited allocator, we can increase framework performance, bypassing even the naive wasm-bindgen benchmarks for very quick renders.
+## Listeners
 
-It's important to note that VNodes are not `'static` - the VNode definition has a lifetime attached to it:
+Listeners are a special type of Attribute that only accept functions. Listeners let us attach functionality to our Elements by running a provided closure whenever the specified Listener is triggered.
 
-```rust, ignore
-enum VNode<'bump> {
-    VElement { tag: &'static str, children: &'bump [VNode<'bump>] },
-    VText { content: &'bump str },
-    // other VNodes ....
-}
+We'll cover listeners in more depth in the Listeners chapter, but for now, just know that every listener must start with the `on` keyword and can accept either a closure or an expression wrapped in curly braces.
+
+```rust
+rsx!(
+    div {
+        onclick: move |_| {}
+        onmouseover: {handler},
+    }
+)
 ```
 
-Because VNodes use a bump allocator as their memory backing, they can only be created through the `NodeFactory` API - which we'll cover in the next chapter. This particular detail is important to understand because "rendering" VNodes produces a lifetime attached to the bump arena - which must be explicitly declared when dealing with components that borrow data from their parents.
+## Moving On
+
+This chapter just scratches the surface on how Elements can be defined.
+
+We learned:
+- Elements are the basic building blocks of User Interfaces
+- Elements can contain other elements 
+- Elements can either be a named container or text
+- Some Elements have properties that the renderer can use to draw the UI to the screen
+
+Next, we'll compose Elements together to form components.

+ 1 - 0
docs/src/depth/components.md

@@ -0,0 +1 @@
+# Components

+ 1 - 0
docs/src/depth/memoization.md

@@ -0,0 +1 @@
+# Memoization

+ 1 - 0
docs/src/depth/performance.md

@@ -0,0 +1 @@
+# Performance

+ 1 - 0
docs/src/depth/props.md

@@ -0,0 +1 @@
+# Props

+ 1 - 0
docs/src/depth/rsx.md

@@ -0,0 +1 @@
+# RSX

+ 1 - 0
docs/src/depth/testing.md

@@ -0,0 +1 @@
+# Testing

+ 1 - 0
docs/src/depth/topics.md

@@ -0,0 +1 @@
+# Topics in Depth

+ 0 - 1
docs/src/format/README.md

@@ -1 +0,0 @@
-# Format

+ 0 - 1
docs/src/format/configuration/README.md

@@ -1 +0,0 @@
-# Configuration

+ 0 - 1
docs/src/format/configuration/environment-variables.md

@@ -1 +0,0 @@
-# Environment Variables

+ 0 - 1
docs/src/format/configuration/general.md

@@ -1 +0,0 @@
-# General

+ 0 - 1
docs/src/format/configuration/preprocessors.md

@@ -1 +0,0 @@
-# Preprocessors

+ 0 - 1
docs/src/format/configuration/renderers.md

@@ -1 +0,0 @@
-# Renderers

+ 0 - 1
docs/src/format/mathjax.md

@@ -1 +0,0 @@
-# MathJax Support

+ 0 - 1
docs/src/format/mdbook.md

@@ -1 +0,0 @@
-# mdBook-specific features

+ 0 - 1
docs/src/format/summary.md

@@ -1 +0,0 @@
-# SUMMARY.md

+ 0 - 1
docs/src/format/theme/README.md

@@ -1 +0,0 @@
-# Theme

+ 0 - 1
docs/src/format/theme/editor.md

@@ -1 +0,0 @@
-# Editor

+ 0 - 1
docs/src/format/theme/index-hbs.md

@@ -1 +0,0 @@
-# index.hbs

+ 0 - 1
docs/src/format/theme/syntax-highlighting.md

@@ -1 +0,0 @@
-# Syntax highlighting

+ 0 - 37
docs/src/gettingstarted/fromjs.md

@@ -1,37 +0,0 @@
-### Immutability by default?
-
----
-
-Rust, like JS and TS, supports both mutable and immutable data. With JS, `const` would be used to signify immutable data, while in rust, the absence of `mut` signifies immutable data.
-
-Mutability:
-
-```rust
-let mut val = 10; // rust
-let val = 10;     // js
-```
-
-Immutability
-
-```rust
-let val = 10;    // rust
-const val = 10;  // js
-```
-
-However, `const` in JS does not prohibit you from modify the value itself only disallowing assignment. In Rust, immutable **is immutable**. You _never_ have to work about accidentally mutating data; mutating immutable data in Rust requires deliberate advanced data structures that you won't find in your typical frontend code.
-
-## How do strings work?
-
----
-
-In rust, we have `&str`, `&'static str` `String`, and `Rc<str>`. It's a lot, yes, and it might be confusing at first. But it's actually not too bad.
-
-In Rust, UTF-8 is supported natively, allowing for emoji and extended character sets (like Chinese and Arabic!) instead of the typical ASCII. The primitive `str` can be seen as a couple of UTF-8 code points squished together with a dynamic size. Because this size is variable (not known at compile time for any single character), we reference an array of UTF-8 code points as `&str`. Essentially, we're referencing (the & symbol) some dynamic `str` (a collection of UTF-8 points).
-
-For text encoded directly in your code, this collection of UTF-8 code points is given the `'static` reference lifetime - essentially meaning the text can be safely referenced for the entire runtime of your program. Contrast this with JS, where a string will only exist for as long as code references it before it gets cleaned up by the garbage collector.
-
-For text that needs to have characters added, removed, sorted, uppercased, formatted, accessed for mutation, etc, Rust has the `String` type, which is essentially just a dynamically sized `str`. In JS, if you add a character to your string, you actually create an entirely new string (completely cloning the old one first). In Rust, you can safely added characters to strings _without_ having to clone first, making string manipulation in Rust very efficient.
-
-Finally, we have `Rc<str>`. This is essentially Rust's version of JavaScript's `string`. In JS, whenever you pass a `string` around (and don't mutate it), you don't actually clone it, but rather just increment a counter that says "this code is using this string." This counter prevents the garbage collector from deleting the string before your code is done using it. Only when all parts of your code are done with the string, will the string be deleted. `Rc<str>` works exactly the same way in Rust, but requires a deliberate `.clone()` to get the same behavior. In most instances, Dioxus will automatically do this for you, saving the trouble of having to `clone` when you pass an `Rc<str>` into child components. `Rc<str>` is typically better than `String` for Rust - it allows cheap sharing of strings, and through `make_mut` you can always produce your own mutable copy for modifying. You might not see `Rc<str>` in other Rust libraries as much, but you will see it in Dioxus due to Dioxus' aggressive memoization and focus on efficiency and performance.
-
-If you run into issues with `&str`, `String`, `Rc<str>`, just try cloning and `to_string` first. For the vast majority of apps, the slight performance hit will be unnoticeable. Once you get better with Strings, it's very easy to go back and remove all the clones for more efficient alternatives, but you will likely never need to.

+ 0 - 26
docs/src/gettingstarted/webapps.md

@@ -1,26 +0,0 @@
-# Choose your architecture type:
-- Static Pages
-- Server-side-rendering
-- Pure SPA
-- Hybrid SPA (liveview)
-
-
-## Static Pages
-You'll want the TextRenderer crate.
-Use it imperatively by passing props and rendering to string.
-
-## Server-side-rendering
-You'll want the TextRenderer crate.
-Serve it with a dedicated middleware crate.
-
-## Pure SPA
-You'll want the WebSys Crate.
-
-## Hybrid SPA (hydration)
-You'll want the WebSys Crate
-You'll want the TextRenderer crate 
-
-
-## Hybrid SPA (liveview)
-You'll want the WebSys crate.
-You'll want the Liveview crate.

+ 33 - 31
docs/src/hello_world.md

@@ -4,6 +4,8 @@ Let's put together a simple "hello world" to get acquainted with Dioxus. The Dio
 
 This demo will build a simple desktop app. Check out the platform-specific setup guides on how to port your app to different targets.
 
+### A new project with Cargo
+
 First, let's start a new project. Rust has the concept of executables and libraries. Executables have a `main.rs` and libraries have `lib.rs`. A project may have both. Our `hello world` will be an executable - we expect our app to launch when we run it! Cargo provides this for us:
 
 ```shell
@@ -65,6 +67,8 @@ edition = "2018"
 
 ```
 
+### Adding Dioxus as a dependency
+
 To use the Dioxus library, we'll want to add the most recent version of `Dioxus` to our crate. If you have `cargo edit` installed, simply call:
 
 ```shell
@@ -75,6 +79,8 @@ It's very important to add `dioxus` with the `desktop` feature for this example.
 
 If you plan to develop extensions for the `Dioxus` ecosystem, please use the `dioxus` crate with the `core` feature to limit the amount of dependencies your project brings in.
 
+### Our first app
+
 Now, let's edit our `main.rs` file:
 
 ```rust
@@ -85,14 +91,18 @@ fn main() {
     dioxus::desktop::start(App, |c| c);
 }
 
-static App: FC<()> = |(cx, props)| {
+fn App((cx, props): Component<()>) -> Element {
     cx.render(rsx! (
         div { "Hello, world!" }
     ))
-};
+}
 ```
 
-Let's dissect our example a bit.
+At this point, you could call `cargo run` and be greeted with a simple `Hello, World!` screen:
+
+![hello world](images/helloworld.png)
+
+### Dissecting our example
 
 This bit of code imports everything from the the `prelude` module. This brings into scope the right traits, types, and macros needed for working with Dioxus.
 
@@ -100,7 +110,7 @@ This bit of code imports everything from the the `prelude` module. This brings i
 use dioxus::prelude::*;
 ```
 
-This initialization code launches a Tokio runtime on a helper thread - where your code will run, and then the WebView on the main-thread. Due to platform requirements, the main thread is blocked by this call.
+This initialization code launches a Tokio runtime on a helper thread where your code will run. Then, the WebView renderer will be launched on the main-thread. Due to platform requirements, the main thread is blocked by your app's event loop.
 
 ```rust
 fn main() {
@@ -108,48 +118,40 @@ fn main() {
 }
 ```
 
-Finally, our app. Every component in Dioxus is a function that takes in `Context` and `Props` and returns an `Option<VNode>`.
+Finally, our app. Every component in Dioxus is a function that takes in `Context` and `Props` and returns an `Element`.
 
 ```rust
-static App: FC<()> = |(cx, props)| {
+fn App((cx, props): Component<()>) -> Element {
     cx.render(rsx! {
         div { "Hello, world!" }
-    })
-};
+    })    
+}
 ```
-
-The closure `FC<()>` syntax is identical to the function syntax, but with lifetimes managed for you. In cases where props need to borrow from their parent, you will need to specify lifetimes using the function syntax:
+In cases where props need to borrow from their parent, you will need to specify lifetimes using the function syntax:
 
 ```rust
-fn App<'a>(cx: Context<'a>, props: &'a ()) -> DomTree<'a> {
+fn App<'a>(cx: Component<'a, ()>) -> Element<'a> {
     cx.render(rsx! {
         div { "Hello, world!" }
     })
 }
 ```
 
-
-In React, you'll save data between renders with hooks. However, hooks rely on global variables which make them difficult to integrate in multi-tenant systems like server-rendering. In Dioxus, you are given an explicit `Context` object to control how the component renders and stores data.
-
-Next, we're greeted with the `rsx!` macro. This lets us add a custom DSL for declaratively building the structure of our app. The semantics of this macro are similar to that of JSX and HTML, though with a familiar Rust-y interface. The `html!` macro is also available for writing components with a JSX/HTML syntax.
-
-The `rsx!` macro is lazy: it does not immediately produce elements or allocates, but rather builds a closure which can be rendered with `cx.render`.
-
-Now, let's launch our app:
-
-```shell
-$ cargo run
+Writing `fn App((cx, props): Component<()>) -> Element {` might become tedious. Rust will also let you write functions as static closures, but these types of Components cannot have props that borrow data.
+```rust
+static App: Fc<()> = |(cx, props)| {
+    cx.render(rsx! {
+        div { "Hello, world!" }
+    })
+};
 ```
 
-Huzzah! We have a simple app.
+### The `Context` object
 
-![Hello world](images/01-setup-helloworld.png)
+In React, you'll want to store data between renders with hooks. However, hooks rely on global variables which make them difficult to integrate in multi-tenant systems like server-rendering. In Dioxus, you are given an explicit `Context` object to control how the component renders and stores data.
 
-If we wanted to golf a bit, we can shrink our hello-world even smaller:
+### The `rsx!` macro
 
-```rust
-fn main() {
-    static App: FC<()> = |(cx, props)| rsx!(cx, div {"hello world!"});
-    dioxus::web::start(App, |c| c);
-}
-```
+Next, we're greeted with the `rsx!` macro. This lets us add a custom DSL for declaratively building the structure of our app. The semantics of this macro are similar to that of JSX and HTML, though with a familiar Rust-y interface. The `html!` macro is also available for writing components with a JSX/HTML syntax.
+
+The `rsx!` macro is lazy: it does not immediately produce elements or allocates, but rather builds a closure which can be rendered with `cx.render`.

BIN
docs/src/images/helloworld.png


BIN
docs/src/images/reddit.png


BIN
docs/src/images/reddit_post.png


BIN
docs/src/images/reddit_post_components.png


+ 0 - 16
docs/src/platforms/00-index.md

@@ -1,16 +0,0 @@
-# Welcome to Dioxus!
-
-## Running Examples
-
-We use the dedicated `dioxus-cli` to build and test dioxus web-apps. This can run examples, tests, build web workers, launch development servers, bundle, and more. It's general purpose, but currently very tailored to Dioxus for liveview and bundling. If you've not used it before, `cargo install --path packages/dioxus-cli` will get it installed. This CLI tool should feel like using `cargo` but with 1st party support for assets, bundling, and other important dioxus-specific features.
-
-Alternatively, `trunk` works but can't run examples.
-
-- tide_ssr: Handle an HTTP request and return an html body using the html! macro. `cargo run --example tide_ssr`
-- doc_generator: Use dioxus SSR to generate the website and docs. `cargo run --example doc_generator`
-- fc_macro: Use the functional component macro to build terse components. `cargo run --example fc_macro`
-- hello_web: Start a simple Wasm app. Requires a web packer like dioxus-cli or trunk `cargo run --example hello`
-- router: `cargo run --example router`
-- tide_ssr: `cargo run --example tide_ssr`
-- webview: Use liveview to bridge into a webview context for a simple desktop application. `cargo run --example webview`
-- twitter-clone: A full-featured Twitter clone showcasing dioxus-liveview, state management patterns, and hooks. `cargo run --example twitter`

+ 0 - 3
docs/src/platforms/01-ssr.md

@@ -1,3 +0,0 @@
-# The Server-Side-Rendering Guide
-
-This guide will help you build your first app that leverages server-side-rendering to display the user interface.

+ 0 - 3
docs/src/platforms/02-wasm.md

@@ -1,3 +0,0 @@
-# The Wasm Guide
-
-This guide will help you build your first app that leverages Wasm in the browser to display the user interface.

+ 0 - 3
docs/src/platforms/03-desktop.md

@@ -1,3 +0,0 @@
-# The Desktop Guide
-
-This guide will help you build your first app that leverages webview for desktop to display the user interface.

+ 0 - 21
docs/src/platforms/04-concurrency.md

@@ -1,21 +0,0 @@
-## Concurrency
-
-In Dioxus, VNodes are asynchronous and can their rendering can be paused at any time by awaiting a future. Hooks can combine this functionality with the Context and Subscription APIs to craft dynamic and efficient user experiences.
-
-```rust
-fn user_data(cx: Context<()>) -> DomTree {
-    // Register this future as a task
-    use_suspense(cx, async {
-        // Continue on with the component as usual, waiting for data to arrive
-        let Profile { name, birthday, .. } = fetch_data().await;
-        html! {
-            <div>
-                {"Hello, {name}!"}
-                {if birthday === std::Instant::now() {html! {"Happy birthday!"}}}
-            </div>
-        }
-    })
-}
-```
-
-Asynchronous components are powerful but can also be easy to misuse as they pause rendering for the component and its children. Refer to the concurrent guide for information on how to best use async components.

+ 0 - 20
docs/src/platforms/05-liveview.md

@@ -1,20 +0,0 @@
-## Liveview
-
-With the Context, Subscription, and Asynchronous APIs, we've built Dioxus Liveview: a coupling of frontend and backend to deliver user experiences that do not require dedicated API development. Instead of building and maintaining frontend-specific API endpoints, components can directly access databases, server caches, and other services directly from the component.
-
-These set of features are still experimental. Currently, we're still working on making these components more ergonomic
-
-```rust
-fn live_component(cx: &Context<()>) -> DomTree {
-    use_live_component(
-        cx,
-        // Rendered via the client
-        #[cfg(target_arch = "wasm32")]
-        || html! { <div> {"Loading data from server..."} </div> },
-
-        // Rendered on the server
-        #[cfg(not(target_arch = "wasm32"))]
-        || html! { <div> {"Server Data Loaded!"} </div> },
-    )
-}
-```

+ 0 - 31
docs/src/platforms/06-components.md

@@ -1,31 +0,0 @@
-## Components
-
-Dioxus should look and feel just like writing functional React components. In Dioxus, there are no class components with lifecycles. All state management is done via hooks. This encourages logic reusability and lessens the burden on Dioxus to maintain a non-breaking lifecycle API.
-
-```rust
-#[derive(Properties, PartialEq)]
-struct MyProps {
-    name: String
-}
-
-fn Example(cx: Context<MyProps>) -> DomTree {
-    html! { <div> "Hello {cx.cx.name}!" </div> }
-}
-```
-
-Here, the `Context` object is used to access hook state, create subscriptions, and interact with the built-in context API. Props, children, and component APIs are accessible via the `Context` object. The functional component macro makes life more productive by inlining props directly as function arguments, similar to how Rocket parses URIs.
-
-```rust
-// A very terse component!
-#[fc]
-fn Example(cx: Context, name: String) -> DomTree {
-    html! { <div> "Hello {name}!" </div> }
-}
-
-// or
-
-#[functional_component]
-pub static Example: FC = |cx, name: String| html! { <div> "Hello {name}!" </div> };
-```
-
-The final output of components must be a tree of VNodes. We provide an html macro for using JSX-style syntax to write these, though, you could use any macro, DSL, templating engine, or the constructors directly.

+ 11 - 7
docs/src/setup.md

@@ -2,7 +2,7 @@
 
 Dioxus aims to provide a fast, friendly, and portable toolkit for building user interfaces with Rust.
 
-This guide assumes you'll be building a SPA (single page application) for the web. The process for building desktop apps, server-rendered apps, static sites, and mobile apps, is more-or-less the same. You can check out the [Platform Specific Guides](../platforms/00-index.md) for more information on setting up Dioxus for any of the various targets you are building for.
+This Getting Setup guide assumes you'll be building a small desktop application. You can check out the [Platform Specific Guides](../platforms/00-index.md) for more information on setting up Dioxus for any of the various supported platforms.
 
 # Setting up Dioxus
 
@@ -10,11 +10,13 @@ Dioxus requires a few main things to get up and running:
 
 - The [Rust compiler](https://www.rust-lang.org) and associated build tooling
 
-- An editor of your choice, with the [Rust-Analyzer LSP plugin](https://rust-analyzer.github.io)
+- An editor of your choice, ideally configured with the [Rust-Analyzer LSP plugin](https://rust-analyzer.github.io)
 
 Dioxus integrates very well with the Rust-Analyzer IDE plugin which will provide appropriate syntax highlighting, code navigation, folding, and more.
 
-We also recommend installing the Dioxus CLI. The Dioxus CLI automates building and packaging for various targets and integrates with simulators, development servers, and app deployment. It'll be our one-stop-shop for anything related to building and sharing our Dioxus Apps. To install the CLI, you'll need cargo (should be automatically installed with Rust):
+### Dioxus-CLI for dev server, bundling, etc.
+
+We also recommend installing the Dioxus CLI. The Dioxus CLI automates building and packaging for various targets and integrates with simulators, development servers, and app deployment. To install the CLI, you'll need cargo (should be automatically installed with Rust):
 
 ```
 $ cargo install dioxus-cli
@@ -28,15 +30,17 @@ $ cargo install --force dioxus-cli
 
 If your version of the CLI is out of date, it'll remind you to update whenever a new version is uploaded to Rust's package manager [crates.io](https://crates.io). We use a dedicated 1st-party CLI to save you from having to run potentially untrusted code every time you add a crate to your project - as is standard in the NPM ecosystem. You can vet the source of the Dioxus-CLI yourself at its [GitHub repo](https://github.com/jkelleyrtp/dioxus/tree/master/packages/cli).
 
+### Suggested extensions
+
 If you want to keep your traditional `npm install XXX` workflow for adding packages, you might want to install `cargo-edit` and a few other fun `cargo` extensions:
 
 - [cargo edit](https://github.com/killercup/cargo-edit) for adding dependencies from the CLI
 - [cargo-expand](https://github.com/dtolnay/cargo-expand) for expanding macro calls
-- `cargo tree` - an integrated cargo command that lets you inspect your dependency tree
+- [cargo tree](https://doc.rust-lang.org/cargo/commands/cargo-tree.html) - an integrated cargo command that lets you inspect your dependency tree
 
-That's it! We won't need to touch NPM/WebPack/Babel/Parcel, etc.
+That's it! We won't need to touch NPM/WebPack/Babel/Parcel, etc. However, you _can_ configure your app to use WebPack with [traditional WASM-pack tooling](https://rustwasm.github.io/wasm-pack/book/tutorials/hybrid-applications-with-webpack/using-your-library.html).
 
-# Important tools
+## Rust Knowledge
 
 With Rust, things like benchmarking, testing, and documentation are included in the language. We strongly recommend going through the official Rust book _completely_. However, our hope is that a Dioxus app can serve as a great first project. With Dioxus you'll learn about:
 
@@ -45,7 +49,7 @@ With Rust, things like benchmarking, testing, and documentation are included in
 - Closures
 - Macros
 
-We've put a lot of care into making Dioxus syntax familiar and easy to understand, so you won't need knowledge on async, lifetimes, and smart pointers until you really start building complex Dioxus apps.
+We've put a lot of care into making Dioxus syntax familiar and easy to understand, so you won't need deep knowledge on async, lifetimes, or smart pointers until you really start building complex Dioxus apps.
 
 We strongly encourage exploring the guides for more information on how to work with the integrated tooling:
 

+ 1 - 0
docs/src/tutorial/advanced_guides.md

@@ -0,0 +1 @@
+# Advanced Guides

+ 1 - 0
docs/src/tutorial/components.md

@@ -0,0 +1 @@
+# Defining Components

+ 13 - 0
docs/src/tutorial/index.md

@@ -0,0 +1,13 @@
+# Putting it all together
+
+So far, we've covered the basics of Dioxus. We've talked about:
+
+- Elements
+- Components
+- Interactivity
+- State Management
+- Async Actions
+- Styling
+
+In this chapter, we'll build a real-world weather app that combines everything we've learned into a cute application that you can run locally. It'll let us monitor different locations simultaneously and periodically check for updates.
+

+ 1 - 0
docs/src/tutorial/new_app.md

@@ -0,0 +1 @@
+# New app

+ 1 - 0
docs/src/tutorial/publishing.md

@@ -0,0 +1 @@
+# Publishing

+ 1 - 0
docs/src/tutorial/state.md

@@ -0,0 +1 @@
+# Defining State

+ 1 - 0
docs/src/tutorial/structure.md

@@ -0,0 +1 @@
+# Structuring our app

+ 1 - 0
docs/src/tutorial/styling.md

@@ -0,0 +1 @@
+# Styling

+ 11 - 0
examples/hello_world.rs

@@ -0,0 +1,11 @@
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus::desktop::launch(App, |c| c);
+}
+
+fn App((cx, props): Component<()>) -> DomTree {
+    cx.render(rsx! (
+        div { "Hello, world!" }
+    ))
+}

+ 1 - 153
packages/core-macro/src/lib.rs

@@ -1,6 +1,5 @@
 use proc_macro::TokenStream;
 use quote::ToTokens;
-use rsx::{AS_HTML, AS_RSX};
 use syn::parse_macro_input;
 
 pub(crate) mod htm;
@@ -182,158 +181,7 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
 /// ```
 #[proc_macro]
 pub fn rsx(s: TokenStream) -> TokenStream {
-    match syn::parse::<rsx::CallBody<AS_RSX>>(s) {
-        Err(e) => e.to_compile_error().into(),
-        Ok(s) => s.to_token_stream().into(),
-    }
-}
-
-/// The html! macro makes it easy for developers to write jsx-style markup in their components.
-///
-/// ## Complete Reference Guide:
-/// ```
-/// const Example: FC<()> = |(cx, props)|{
-///     let formatting = "formatting!";
-///     let formatting_tuple = ("a", "b");
-///     let lazy_fmt = format_args!("lazily formatted text");
-///     cx.render(html! {
-///         <div>
-///             <div />
-///             <div> </div>
-///             <h1>"Some text"</h1>
-///             <h1>"Some text with {formatting}"</h1>
-///             <h1>"Formatting basic expressions {formatting_tuple.0} and {formatting_tuple.1}"</h1>
-///             <h2>
-///                 "Multiple"
-///                 "Text"
-///                 "Blocks"
-///                 "Use comments as separators in html"
-///             </h2>
-///             <div>
-///                 <h1>"multiple"</h1>
-///                 <h2>"nested"</h2>
-///                 <h3>"elements"</h3>
-///             </div>
-///             <div class="my special div">
-///                 <h1>"Headers and attributes!"</h1>
-///             </div>
-///             <div class: lazy_fmt, id=format_args!("attributes can be passed lazily with std::fmt::Arguments")>
-///                 <div class: {
-///                     const WORD: &str = "expressions";
-///                     format_args!("Arguments can be passed in through curly braces for complex {}", WORD)
-///                 } />
-///             </div>
-///             {rsx!(p { "More templating!" })}
-///             {html!(<p>"Even HTML templating!!"</p>)}
-///             {(0..10).map(|i| html!(<li>"{i}"</li>))}
-///             {{
-///                 let data = std::collections::HashMap::<&'static str, &'static str>::new();
-///                 // Iterators *should* have keys when you can provide them.
-///                 // Keys make your app run faster. Make sure your keys are stable, unique, and predictable.
-///                 // Using an "ID" associated with your data is a good idea.
-///                 data.into_iter().map(|(k, v)| rsx!(<li key="{k}"> "{v}" </li>))
-///             }}
-
-///             // Matching
-///             // Matching will throw a Rust error about "no two closures are the same type"
-///             // To fix this, call "render" method or use the "in" syntax to produce VNodes.
-///             // There's nothing we can do about it, sorry :/ (unless you want *really* unhygenic macros)
-///             {match true {
-///                 true => rsx!(cx, <h1>"Top text"</h1>),
-///                 false => rsx!(cx, <h1>"Bottom text"</h1>),
-///             }}
-///
-///             // Conditional rendering
-///             // Dioxus conditional rendering is based around None/Some. We have no special syntax for conditionals.
-///             // You can convert a bool condition to rsx! with .then and .or
-///             {true.then(|| html!(<div />))}
-///
-///             // True conditions need to be rendered (same reasons as matching)
-///             {if true {
-///                 html!(cx, <h1>"Top text"</h1>)
-///             } else {
-///                 html!(cx, <h1>"Bottom text"</h1>)
-///             }}
-///
-///             // returning "None" is a bit noisy... but rare in practice
-///             {None as Option<()>}
-///
-///             // Use the Dioxus type-alias for less noise
-///             {NONE_ELEMENT}
-///
-///             // can also just use empty fragments
-///             <Fragment />
-///
-///             // Fragments let you insert groups of nodes without a parent.
-///             // This lets you make components that insert elements as siblings without a container.
-///             <div> "A" </div>
-///             <Fragment>
-///                 <div> "B" </div>
-///                 <div> "C" </div>
-///                 <Fragment>
-///                     "D"
-///                     <Fragment>
-///                         "heavily nested fragments is an antipattern"
-///                         "they cause Dioxus to do unnecessary work"
-///                         "don't use them carelessly if you can help it"
-///                     </Fragment>
-///                 </Fragment
-///             </Fragment>
-///
-///             // Components
-///             // Can accept any paths
-///             // Notice how you still get syntax highlighting and IDE support :)
-///             <Baller />
-///             <baller::Baller />
-///             <crate::baller::Baller />
-///
-///             // Can take properties
-///             <Taller a="asd" />
-///
-///             // Can take optional properties
-///             <Taller a="asd" />
-///
-///             // Can pass in props directly as an expression
-///             {{
-///                 let props = TallerProps {a: "hello"};
-///                 html!(<Taller ..{props} />)
-///             }}
-///
-///             // Spreading can also be overridden manually
-///             <Taller {..TallerProps { a: "ballin!" }} a="not ballin!" />
-///
-///             // Can take children too!
-///             <Taller a="asd">
-///                 <div> "hello world!" </div>
-///             </Taller>
-///         }
-///     })
-/// };
-///
-/// mod baller {
-///     use super::*;
-///     pub struct BallerProps {}
-///
-///     /// This component totally balls
-///     pub fn Baller(cx: Context<()>) -> DomTree {
-///         todo!()
-///     }
-/// }
-///
-/// #[derive(Debug, PartialEq, Props)]
-/// pub struct TallerProps {
-///     a: &'static str,
-/// }
-///
-/// /// This component is taller than most :)
-/// pub fn Taller(cx: Context<TallerProps>) -> DomTree {
-///     let b = true;
-///     todo!()
-/// }
-/// ```
-#[proc_macro]
-pub fn html(s: TokenStream) -> TokenStream {
-    match syn::parse::<rsx::CallBody<AS_HTML>>(s) {
+    match syn::parse::<rsx::CallBody>(s) {
         Err(e) => e.to_compile_error().into(),
         Ok(s) => s.to_token_stream().into(),
     }

+ 3 - 3
packages/core-macro/src/props/mod.rs

@@ -17,7 +17,7 @@ pub fn impl_my_derive(ast: &syn::DeriveInput) -> Result<TokenStream, Error> {
     let data = match &ast.data {
         syn::Data::Struct(data) => match &data.fields {
             syn::Fields::Named(fields) => {
-                let struct_info = struct_info::StructInfo::new(&ast, fields.named.iter())?;
+                let struct_info = struct_info::StructInfo::new(ast, fields.named.iter())?;
                 let builder_creation = struct_info.builder_creation_impl()?;
                 let conversion_helper = struct_info.conversion_helper_impl()?;
                 let fields = struct_info
@@ -196,7 +196,7 @@ mod field_info {
             if let Some(ref name) = field.ident {
                 Ok(FieldInfo {
                     ordinal,
-                    name: &name,
+                    name,
                     generic_ident: syn::Ident::new(
                         &format!("__{}", strip_raw_ident_prefix(name.to_string())),
                         proc_macro2::Span::call_site(),
@@ -585,7 +585,7 @@ mod struct_info {
             // we use the heuristic: are there *any* generic parameters?
             // If so, then they might have non-static lifetimes and we can't compare two generic things that *might borrow*
             // Therefore, we will generate code that shortcircuits the "comparison" in memoization
-            let are_there_generics = self.generics.params.len() > 0;
+            let are_there_generics = !self.generics.params.is_empty();
 
             let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
             let all_fields_param = syn::GenericParam::Type(

+ 9 - 42
packages/core-macro/src/rsx/ambiguous.rs

@@ -16,26 +16,22 @@ use syn::{
 };
 
 #[allow(clippy::large_enum_variant)]
-pub enum AmbiguousElement<const AS: HtmlOrRsx> {
-    Element(Element<AS>),
-    Component(Component<AS>),
+pub enum AmbiguousElement {
+    Element(Element),
+    Component(Component),
 }
 
-impl Parse for AmbiguousElement<AS_RSX> {
+impl Parse for AmbiguousElement {
     fn parse(input: ParseStream) -> Result<Self> {
         // Try to parse as an absolute path and immediately defer to the componetn
         if input.peek(Token![::]) {
-            return input
-                .parse::<Component<AS_RSX>>()
-                .map(AmbiguousElement::Component);
+            return input.parse::<Component>().map(AmbiguousElement::Component);
         }
 
         // If not an absolute path, then parse the ident and check if it's a valid tag
         if let Ok(pat) = input.fork().parse::<syn::Path>() {
             if pat.segments.len() > 1 {
-                return input
-                    .parse::<Component<AS_RSX>>()
-                    .map(AmbiguousElement::Component);
+                return input.parse::<Component>().map(AmbiguousElement::Component);
             }
         }
 
@@ -45,45 +41,16 @@ impl Parse for AmbiguousElement<AS_RSX> {
 
             let first_char = name_str.chars().next().unwrap();
             if first_char.is_ascii_uppercase() {
-                input
-                    .parse::<Component<AS_RSX>>()
-                    .map(AmbiguousElement::Component)
+                input.parse::<Component>().map(AmbiguousElement::Component)
             } else {
-                input
-                    .parse::<Element<AS_RSX>>()
-                    .map(AmbiguousElement::Element)
+                input.parse::<Element>().map(AmbiguousElement::Element)
             }
         } else {
             Err(Error::new(input.span(), "Not a valid Html tag"))
         }
     }
 }
-
-impl Parse for AmbiguousElement<AS_HTML> {
-    fn parse(input: ParseStream) -> Result<Self> {
-        if input.peek(Token![<]) {
-            let forked = input.fork();
-            forked.parse::<Token![<]>().unwrap();
-            let tag = forked.parse::<Ident>()?;
-            let name_str = tag.to_string();
-
-            let first_char = name_str.chars().next().unwrap();
-            if first_char.is_ascii_uppercase() {
-                input
-                    .parse::<Component<AS_HTML>>()
-                    .map(AmbiguousElement::Component)
-            } else {
-                input
-                    .parse::<Element<AS_HTML>>()
-                    .map(AmbiguousElement::Element)
-            }
-        } else {
-            Err(Error::new(input.span(), "Not a valid Html tag"))
-        }
-    }
-}
-
-impl<const AS: HtmlOrRsx> ToTokens for AmbiguousElement<AS> {
+impl ToTokens for AmbiguousElement {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         match self {
             AmbiguousElement::Element(el) => el.to_tokens(tokens),

+ 5 - 19
packages/core-macro/src/rsx/body.rs

@@ -7,30 +7,16 @@ use syn::{
 
 use super::*;
 
-pub struct CallBody<const AS: HtmlOrRsx> {
+pub struct CallBody {
     custom_context: Option<Ident>,
-    roots: Vec<BodyNode<AS>>,
+    roots: Vec<BodyNode>,
 }
 
 /// The custom rusty variant of parsing rsx!
-impl Parse for CallBody<AS_RSX> {
+impl Parse for CallBody {
     fn parse(input: ParseStream) -> Result<Self> {
         let custom_context = try_parse_custom_context(input)?;
-        let (_, roots, _) = BodyConfig::<AS_RSX>::new_call_body().parse_component_body(input)?;
-        Ok(Self {
-            custom_context,
-            roots,
-        })
-    }
-}
-
-/// The HTML variant of parsing rsx!
-impl Parse for CallBody<AS_HTML> {
-    fn parse(input: ParseStream) -> Result<Self> {
-        let custom_context = try_parse_custom_context(input)?;
-
-        // parsing the contents is almost like parsing the inner of any element, but with no props
-        let (_, roots, _) = BodyConfig::<AS_HTML>::new_call_body().parse_component_body(input)?;
+        let (_, roots, _) = BodyConfig::new_call_body().parse_component_body(input)?;
         Ok(Self {
             custom_context,
             roots,
@@ -50,7 +36,7 @@ fn try_parse_custom_context(input: ParseStream) -> Result<Option<Ident>> {
 }
 
 /// Serialize the same way, regardless of flavor
-impl<const A: HtmlOrRsx> ToTokens for CallBody<A> {
+impl ToTokens for CallBody {
     fn to_tokens(&self, out_tokens: &mut TokenStream2) {
         let inner = if self.roots.len() == 1 {
             let inner = &self.roots[0];

+ 15 - 164
packages/core-macro/src/rsx/component.rs

@@ -22,15 +22,15 @@ use syn::{
     token, Error, Expr, ExprClosure, Ident, Result, Token,
 };
 
-pub struct Component<const AS: HtmlOrRsx> {
+pub struct Component {
     // accept any path-like argument
     name: syn::Path,
-    body: Vec<ComponentField<AS>>,
-    children: Vec<BodyNode<AS>>,
+    body: Vec<ComponentField>,
+    children: Vec<BodyNode>,
     manual_props: Option<Expr>,
 }
 
-impl Parse for Component<AS_RSX> {
+impl Parse for Component {
     fn parse(stream: ParseStream) -> Result<Self> {
         // let name = s.parse::<syn::ExprPath>()?;
         // todo: look into somehow getting the crate/super/etc
@@ -41,7 +41,7 @@ impl Parse for Component<AS_RSX> {
         let content: ParseBuffer;
         syn::braced!(content in stream);
 
-        let cfg: BodyConfig<AS_RSX> = BodyConfig {
+        let cfg: BodyConfig = BodyConfig {
             allow_children: true,
             allow_fields: true,
             allow_manual_props: true,
@@ -57,80 +57,14 @@ impl Parse for Component<AS_RSX> {
         })
     }
 }
-impl Parse for Component<AS_HTML> {
-    fn parse(stream: ParseStream) -> Result<Self> {
-        let _l_tok = stream.parse::<Token![<]>()?;
-        let name = syn::Path::parse_mod_style(stream)?;
-
-        let mut manual_props = None;
-
-        let mut body: Vec<ComponentField<AS_HTML>> = vec![];
-        let mut children: Vec<BodyNode<AS_HTML>> = vec![];
-
-        if stream.peek(Token![..]) {
-            stream.parse::<Token![..]>()?;
-            manual_props = Some(stream.parse::<Expr>()?);
-        }
-
-        while !stream.peek(Token![>]) {
-            // self-closing
-            if stream.peek(Token![/]) {
-                stream.parse::<Token![/]>()?;
-                stream.parse::<Token![>]>()?;
-
-                return Ok(Self {
-                    name,
-                    manual_props,
-                    body,
-                    children,
-                });
-            }
-            body.push(stream.parse::<ComponentField<AS_HTML>>()?);
-        }
-
-        stream.parse::<Token![>]>()?;
-
-        'parsing: loop {
-            if stream.peek(Token![<]) && stream.peek2(Token![/]) {
-                break 'parsing;
-            }
-
-            // [1] Break if empty
-            if stream.is_empty() {
-                break 'parsing;
-            }
-
-            children.push(stream.parse::<BodyNode<AS_HTML>>()?);
-        }
-
-        // closing element
-        stream.parse::<Token![<]>()?;
-        stream.parse::<Token![/]>()?;
-        let close = syn::Path::parse_mod_style(stream)?;
-        if close != name {
-            return Err(Error::new_spanned(
-                close,
-                "closing element does not match opening",
-            ));
-        }
-        stream.parse::<Token![>]>()?;
-
-        Ok(Self {
-            name,
-            body,
-            children,
-            manual_props,
-        })
-    }
-}
 
-pub struct BodyConfig<const AS: HtmlOrRsx> {
+pub struct BodyConfig {
     pub allow_fields: bool,
     pub allow_children: bool,
     pub allow_manual_props: bool,
 }
 
-impl<const AS: HtmlOrRsx> BodyConfig<AS> {
+impl BodyConfig {
     /// The configuration to parse the root
     pub fn new_call_body() -> Self {
         Self {
@@ -141,74 +75,13 @@ impl<const AS: HtmlOrRsx> BodyConfig<AS> {
     }
 }
 
-impl BodyConfig<AS_RSX> {
-    // todo: unify this body parsing for both elements and components
-    // both are style rather ad-hoc, though components are currently more configured
-    pub fn parse_component_body(
-        &self,
-        content: &ParseBuffer,
-    ) -> Result<(
-        Vec<ComponentField<AS_RSX>>,
-        Vec<BodyNode<AS_RSX>>,
-        Option<Expr>,
-    )> {
-        let mut body = Vec::new();
-        let mut children = Vec::new();
-        let mut manual_props = None;
-
-        'parsing: loop {
-            // [1] Break if empty
-            if content.is_empty() {
-                break 'parsing;
-            }
-
-            if content.peek(Token![..]) {
-                if !self.allow_manual_props {
-                    return Err(Error::new(
-                        content.span(),
-                        "Props spread syntax is not allowed in this context. \nMake to only use the elipsis `..` in Components.",
-                    ));
-                }
-                content.parse::<Token![..]>()?;
-                manual_props = Some(content.parse::<Expr>()?);
-            } else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
-                if !self.allow_fields {
-                    return Err(Error::new(
-                        content.span(),
-                        "Property fields is not allowed in this context. \nMake to only use fields in Components or Elements.",
-                    ));
-                }
-                body.push(content.parse::<ComponentField<AS_RSX>>()?);
-            } else {
-                if !self.allow_children {
-                    return Err(Error::new(
-                        content.span(),
-                        "This item is not allowed to accept children.",
-                    ));
-                }
-                children.push(content.parse::<BodyNode<AS_RSX>>()?);
-            }
-
-            // consume comma if it exists
-            // we don't actually care if there *are* commas between attrs
-            if content.peek(Token![,]) {
-                let _ = content.parse::<Token![,]>();
-            }
-        }
-        Ok((body, children, manual_props))
-    }
-}
-impl BodyConfig<AS_HTML> {
+impl BodyConfig {
     // todo: unify this body parsing for both elements and components
     // both are style rather ad-hoc, though components are currently more configured
     pub fn parse_component_body(
         &self,
         content: &ParseBuffer,
-    ) -> Result<(
-        Vec<ComponentField<AS_HTML>>,
-        Vec<BodyNode<AS_HTML>>,
-        Option<Expr>,
-    )> {
+    ) -> Result<(Vec<ComponentField>, Vec<BodyNode>, Option<Expr>)> {
         let mut body = Vec::new();
         let mut children = Vec::new();
         let mut manual_props = None;
@@ -235,7 +108,7 @@ impl BodyConfig<AS_HTML> {
                         "Property fields is not allowed in this context. \nMake to only use fields in Components or Elements.",
                     ));
                 }
-                body.push(content.parse::<ComponentField<AS_HTML>>()?);
+                body.push(content.parse::<ComponentField>()?);
             } else {
                 if !self.allow_children {
                     return Err(Error::new(
@@ -243,8 +116,7 @@ impl BodyConfig<AS_HTML> {
                         "This item is not allowed to accept children.",
                     ));
                 }
-
-                children.push(content.parse::<BodyNode<AS_HTML>>()?);
+                children.push(content.parse::<BodyNode>()?);
             }
 
             // consume comma if it exists
@@ -257,7 +129,7 @@ impl BodyConfig<AS_HTML> {
     }
 }
 
-impl<const AS: HtmlOrRsx> ToTokens for Component<AS> {
+impl ToTokens for Component {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let name = &self.name;
 
@@ -327,7 +199,7 @@ impl<const AS: HtmlOrRsx> ToTokens for Component<AS> {
 }
 
 // the struct's fields info
-pub struct ComponentField<const AS: HtmlOrRsx> {
+pub struct ComponentField {
     name: Ident,
     content: ContentField,
 }
@@ -354,28 +226,7 @@ impl ToTokens for ContentField {
     }
 }
 
-impl Parse for ComponentField<AS_RSX> {
-    fn parse(input: ParseStream) -> Result<Self> {
-        let name = Ident::parse_any(input)?;
-        input.parse::<Token![:]>()?;
-
-        let name_str = name.to_string();
-        let content = if name_str.starts_with("on") {
-            if input.peek(token::Brace) {
-                let content;
-                syn::braced!(content in input);
-                ContentField::OnHandlerRaw(content.parse()?)
-            } else {
-                ContentField::OnHandler(input.parse()?)
-            }
-        } else {
-            ContentField::ManExpr(input.parse::<Expr>()?)
-        };
-
-        Ok(Self { name, content })
-    }
-}
-impl Parse for ComponentField<AS_HTML> {
+impl Parse for ComponentField {
     fn parse(input: ParseStream) -> Result<Self> {
         let name = Ident::parse_any(input)?;
         input.parse::<Token![:]>()?;
@@ -397,7 +248,7 @@ impl Parse for ComponentField<AS_HTML> {
     }
 }
 
-impl<const AS: HtmlOrRsx> ToTokens for ComponentField<AS> {
+impl ToTokens for ComponentField {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let ComponentField { name, content, .. } = self;
         tokens.append_all(quote! {

+ 144 - 282
packages/core-macro/src/rsx/element.rs

@@ -3,54 +3,118 @@ use super::*;
 use proc_macro2::TokenStream as TokenStream2;
 use quote::{quote, ToTokens, TokenStreamExt};
 use syn::{
-    ext::IdentExt,
-    parse::{discouraged::Speculative, Parse, ParseBuffer, ParseStream},
-    token, Error, Expr, ExprClosure, Ident, LitStr, Result, Token,
+    parse::{Parse, ParseBuffer, ParseStream},
+    token, Expr, ExprClosure, Ident, LitStr, Result, Token,
 };
 
 // =======================================
 // Parse the VNode::Element type
 // =======================================
-pub struct Element<const AS: HtmlOrRsx> {
+pub struct Element {
     name: Ident,
     key: Option<LitStr>,
-    attributes: Vec<ElementAttr<AS>>,
-    listeners: Vec<ElementAttr<AS>>,
-    children: Vec<BodyNode<AS>>,
+    attributes: Vec<ElementAttrNamed>,
+    listeners: Vec<ElementAttrNamed>,
+    children: Vec<BodyNode>,
     _is_static: bool,
 }
 
-impl Parse for Element<AS_RSX> {
+impl Parse for Element {
     fn parse(stream: ParseStream) -> Result<Self> {
-        let name = Ident::parse(stream)?;
+        let el_name = Ident::parse(stream)?;
 
         // parse the guts
         let content: ParseBuffer;
         syn::braced!(content in stream);
 
-        let mut attributes: Vec<ElementAttr<AS_RSX>> = vec![];
-        let mut listeners: Vec<ElementAttr<AS_RSX>> = vec![];
-        let mut children: Vec<BodyNode<AS_RSX>> = vec![];
+        let mut attributes: Vec<ElementAttrNamed> = vec![];
+        let mut listeners: Vec<ElementAttrNamed> = vec![];
+        let mut children: Vec<BodyNode> = vec![];
         let mut key = None;
-        let mut el_ref = None;
-
-        'parsing: loop {
-            // [1] Break if empty
-            if content.is_empty() {
-                break 'parsing;
-            }
+        let mut _el_ref = None;
 
+        // todo: more descriptive error handling
+        while !content.is_empty() {
             if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
-                parse_rsx_element_field(
-                    &content,
-                    &mut attributes,
-                    &mut listeners,
-                    &mut key,
-                    &mut el_ref,
-                    name.clone(),
-                )?;
+                let name = content.parse::<Ident>()?;
+                let name_str = name.to_string();
+                content.parse::<Token![:]>()?;
+
+                if name_str.starts_with("on") {
+                    if content.peek(token::Brace) {
+                        let mycontent;
+                        syn::braced!(mycontent in content);
+
+                        listeners.push(ElementAttrNamed {
+                            el_name: el_name.clone(),
+                            attr: ElementAttr::EventTokens {
+                                name,
+                                tokens: mycontent.parse()?,
+                            },
+                        });
+                    } else {
+                        listeners.push(ElementAttrNamed {
+                            el_name: el_name.clone(),
+                            attr: ElementAttr::EventClosure {
+                                name,
+                                closure: content.parse()?,
+                            },
+                        });
+                    };
+                } else {
+                    match name_str.as_str() {
+                        "key" => {
+                            key = Some(content.parse()?);
+                        }
+                        "classes" => {
+                            todo!("custom class list not supported")
+                        }
+                        "namespace" => {
+                            todo!("custom namespace not supported")
+                        }
+                        "node_ref" => {
+                            _el_ref = Some(content.parse::<Expr>()?);
+                        }
+                        _ => {
+                            if content.peek(LitStr) {
+                                attributes.push(ElementAttrNamed {
+                                    el_name: el_name.clone(),
+                                    attr: ElementAttr::AttrText {
+                                        name,
+                                        value: content.parse()?,
+                                    },
+                                });
+                            } else {
+                                attributes.push(ElementAttrNamed {
+                                    el_name: el_name.clone(),
+                                    attr: ElementAttr::AttrExpression {
+                                        name,
+                                        value: content.parse()?,
+                                    },
+                                });
+                            }
+                        }
+                    }
+                }
+            } else if content.peek(LitStr) && content.peek2(Token![:]) {
+                let name = content.parse::<LitStr>()?;
+                content.parse::<Token![:]>()?;
+
+                if content.peek(LitStr) {
+                    let value = content.parse::<LitStr>()?;
+                    attributes.push(ElementAttrNamed {
+                        el_name: el_name.clone(),
+                        attr: ElementAttr::CustomAttrText { name, value },
+                    });
+                } else {
+                    let value = content.parse::<Expr>()?;
+                    attributes.push(ElementAttrNamed {
+                        el_name: el_name.clone(),
+                        attr: ElementAttr::CustomAttrExpression { name, value },
+                    });
+                }
             } else {
-                children.push(content.parse::<BodyNode<AS_RSX>>()?);
+                children.push(content.parse::<BodyNode>()?);
             }
 
             // consume comma if it exists
@@ -60,112 +124,6 @@ impl Parse for Element<AS_RSX> {
             }
         }
 
-        Ok(Self {
-            key,
-            name,
-            attributes,
-            children,
-            listeners,
-            _is_static: false,
-        })
-    }
-}
-
-impl Parse for Element<AS_HTML> {
-    fn parse(stream: ParseStream) -> Result<Self> {
-        let _l_tok = stream.parse::<Token![<]>()?;
-        let el_name = Ident::parse(stream)?;
-
-        let mut attributes: Vec<ElementAttr<AS_HTML>> = vec![];
-        let mut listeners: Vec<ElementAttr<AS_HTML>> = vec![];
-        let mut children: Vec<BodyNode<AS_HTML>> = vec![];
-        let key = None;
-
-        while !stream.peek(Token![>]) {
-            // self-closing
-            if stream.peek(Token![/]) {
-                stream.parse::<Token![/]>()?;
-                stream.parse::<Token![>]>()?;
-
-                return Ok(Self {
-                    name: el_name,
-                    key: None,
-                    attributes,
-                    _is_static: false,
-                    listeners,
-                    children,
-                });
-            }
-
-            let name = Ident::parse_any(stream)?;
-            let name_str = name.to_string();
-            stream.parse::<Token![=]>()?;
-            if name_str.starts_with("on") {
-                let inner;
-                syn::braced!(inner in stream);
-                let toks = inner.parse::<Expr>()?;
-                let ty = AttrType::EventTokens(toks);
-                listeners.push(ElementAttr {
-                    element_name: el_name.clone(),
-                    name,
-                    value: ty,
-                    namespace: None,
-                })
-            } else {
-                match name_str.as_str() {
-                    "style" => {}
-                    "key" => {}
-                    _ => {
-                        // "classes" | "namespace" | "ref" | _ => {
-                        let ty = if stream.peek(LitStr) {
-                            let rawtext = stream.parse::<LitStr>().unwrap();
-                            AttrType::BumpText(rawtext)
-                        } else {
-                            // like JSX, we expect raw expressions
-                            let inner;
-                            syn::braced!(inner in stream);
-                            let toks = inner.parse::<Expr>()?;
-                            AttrType::FieldTokens(toks)
-                        };
-                        attributes.push(ElementAttr {
-                            element_name: el_name.clone(),
-                            name,
-                            value: ty,
-                            namespace: None,
-                        })
-                    }
-                }
-            };
-        }
-
-        stream.parse::<Token![>]>()?;
-
-        'parsing: loop {
-            if stream.peek(Token![<]) && stream.peek2(Token![/]) {
-                break 'parsing;
-            }
-
-            // [1] Break if empty
-            if stream.is_empty() {
-                break 'parsing;
-            }
-
-            children.push(stream.parse::<BodyNode<AS_HTML>>()?);
-        }
-
-        // closing element
-        stream.parse::<Token![<]>()?;
-        stream.parse::<Token![/]>()?;
-
-        let close = Ident::parse_any(stream)?;
-        if close != el_name {
-            return Err(Error::new_spanned(
-                close,
-                "closing element does not match opening",
-            ));
-        }
-        stream.parse::<Token![>]>()?;
-
         Ok(Self {
             key,
             name: el_name,
@@ -177,12 +135,14 @@ impl Parse for Element<AS_HTML> {
     }
 }
 
-impl<const AS: HtmlOrRsx> ToTokens for Element<AS> {
+impl ToTokens for Element {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let name = &self.name;
-        let attr = &self.attributes;
         let childs = &self.children;
+
         let listeners = &self.listeners;
+        let attr = &self.attributes;
+
         let key = match &self.key {
             Some(ty) => quote! { Some(format_args_f!(#ty)) },
             None => quote! { None },
@@ -200,164 +160,66 @@ impl<const AS: HtmlOrRsx> ToTokens for Element<AS> {
     }
 }
 
-/// =======================================
-/// Parse a VElement's Attributes
-/// =======================================
-struct ElementAttr<const AS: HtmlOrRsx> {
-    element_name: Ident,
-    name: Ident,
-    value: AttrType,
-    namespace: Option<String>,
-}
-
-enum AttrType {
-    BumpText(LitStr),
-    FieldTokens(Expr),
-    EventTokens(Expr),
-    Event(ExprClosure),
-}
+enum ElementAttr {
+    // attribute: "valuee {}"
+    AttrText { name: Ident, value: LitStr },
 
-// We parse attributes and dump them into the attribute vec
-// This is because some tags might be namespaced (IE style)
-// These dedicated tags produce multiple name-spaced attributes
-fn parse_rsx_element_field(
-    stream: ParseStream,
-    attrs: &mut Vec<ElementAttr<AS_RSX>>,
-    listeners: &mut Vec<ElementAttr<AS_RSX>>,
-    key: &mut Option<LitStr>,
-    el_ref: &mut Option<Expr>,
-    element_name: Ident,
-) -> Result<()> {
-    let name = Ident::parse_any(stream)?;
-    let name_str = name.to_string();
-    stream.parse::<Token![:]>()?;
+    // attribute: true,
+    AttrExpression { name: Ident, value: Expr },
 
-    // Return early if the field is a listener
-    if name_str.starts_with("on") {
-        // remove the "on" bit
-        let ty = if stream.peek(token::Brace) {
-            let content;
-            syn::braced!(content in stream);
+    // "attribute": "value {}"
+    CustomAttrText { name: LitStr, value: LitStr },
 
-            // Try to parse directly as a closure
-            let fork = content.fork();
-            if let Ok(event) = fork.parse::<ExprClosure>() {
-                content.advance_to(&fork);
-                AttrType::Event(event)
-            } else {
-                AttrType::EventTokens(content.parse()?)
-            }
-        } else {
-            AttrType::Event(stream.parse()?)
-        };
-        listeners.push(ElementAttr {
-            name,
-            value: ty,
-            namespace: None,
-            element_name,
-        });
-        return Ok(());
-    }
-
-    let ty: AttrType = match name_str.as_str() {
-        // short circuit early if style is using the special syntax
-        "style" if stream.peek(token::Brace) => {
-            let inner;
-            syn::braced!(inner in stream);
-
-            while !inner.is_empty() {
-                let name = Ident::parse_any(&inner)?;
-                inner.parse::<Token![:]>()?;
-                let ty = if inner.peek(LitStr) {
-                    let rawtext = inner.parse::<LitStr>().unwrap();
-                    AttrType::BumpText(rawtext)
-                } else {
-                    let toks = inner.parse::<Expr>()?;
-                    AttrType::FieldTokens(toks)
-                };
-                if inner.peek(Token![,]) {
-                    let _ = inner.parse::<Token![,]>();
-                }
-                attrs.push(ElementAttr {
-                    name,
-                    value: ty,
-                    namespace: Some("style".to_string()),
-                    element_name: element_name.clone(),
-                });
-            }
-
-            return Ok(());
-        }
-        "key" => {
-            *key = Some(stream.parse::<LitStr>()?);
-            return Ok(());
-        }
-        "classes" => {
-            todo!("custom class list not supported")
-        }
-        "namespace" => {
-            todo!("custom namespace not supported")
-        }
-        "node_ref" => {
-            *el_ref = Some(stream.parse::<Expr>()?);
-            return Ok(());
-        }
+    // "attribute": true,
+    CustomAttrExpression { name: LitStr, value: Expr },
 
-        // Fall through
-        _ => {
-            if stream.peek(LitStr) {
-                let rawtext = stream.parse::<LitStr>().unwrap();
-                AttrType::BumpText(rawtext)
-            } else {
-                let toks = stream.parse::<Expr>()?;
-                AttrType::FieldTokens(toks)
-            }
-        }
-    };
+    // onclick: move |_| {}
+    EventClosure { name: Ident, closure: ExprClosure },
 
-    // consume comma if it exists
-    // we don't actually care if there *are* commas between attrs
-    if stream.peek(Token![,]) {
-        let _ = stream.parse::<Token![,]>();
-    }
+    // onclick: {}
+    EventTokens { name: Ident, tokens: Expr },
+}
 
-    attrs.push(ElementAttr {
-        name,
-        value: ty,
-        namespace: None,
-        element_name,
-    });
-    Ok(())
+struct ElementAttrNamed {
+    el_name: Ident,
+    attr: ElementAttr,
 }
 
-impl<const AS: HtmlOrRsx> ToTokens for ElementAttr<AS> {
+impl ToTokens for ElementAttrNamed {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
-        let el_name = &self.element_name;
-        let nameident = &self.name;
+        let ElementAttrNamed { el_name, attr } = self;
 
-        // TODO: wire up namespace
-        let _name_str = self.name.to_string();
-        let _namespace = match &self.namespace {
-            Some(t) => quote! { Some(#t) },
-            None => quote! { None },
-        };
-
-        match &self.value {
-            AttrType::BumpText(value) => tokens.append_all(quote! {
-                dioxus_elements::#el_name.#nameident(__cx, format_args_f!(#value))
-            }),
+        let toks = match attr {
+            ElementAttr::AttrText { name, value } => {
+                quote! {
+                    dioxus_elements::#el_name.#name(__cx, format_args_f!(#value))
+                }
+            }
+            ElementAttr::AttrExpression { name, value } => {
+                quote! {
+                    dioxus_elements::#el_name.#name(__cx, #value)
+                }
+            }
 
-            AttrType::FieldTokens(exp) => tokens.append_all(quote! {
-                dioxus_elements::#el_name.#nameident(__cx, #exp)
-            }),
+            ElementAttr::CustomAttrText { name, value } => {
+                quote! { __cx.attr( #name, format_args_f!(#value), None, false ) }
+            }
+            ElementAttr::CustomAttrExpression { name, value } => {
+                quote! { __cx.attr( #name, format_args_f!(#value), None, false ) }
+            }
 
-            AttrType::Event(event) => tokens.append_all(quote! {
-                dioxus::events::on::#nameident(__cx, #event)
-            }),
+            ElementAttr::EventClosure { name, closure } => {
+                quote! {
+                    dioxus::events::on::#name(__cx, #closure)
+                }
+            }
+            ElementAttr::EventTokens { name, tokens } => {
+                quote! {
+                    dioxus::events::on::#name(__cx, #tokens)
+                }
+            }
+        };
 
-            AttrType::EventTokens(event) => tokens.append_all(quote! {
-                dioxus::events::on::#nameident(__cx, #event)
-            }),
-        }
+        tokens.append_all(toks);
     }
 }

+ 6 - 26
packages/core-macro/src/rsx/fragment.rs

@@ -8,7 +8,7 @@
 //! - [ ] Children
 //! - [ ] Keys
 
-use super::{AmbiguousElement, HtmlOrRsx, AS_HTML, AS_RSX};
+use super::AmbiguousElement;
 use syn::parse::ParseBuffer;
 use {
     proc_macro2::TokenStream as TokenStream2,
@@ -19,11 +19,11 @@ use {
     },
 };
 
-pub struct Fragment<const AS: HtmlOrRsx> {
-    children: Vec<AmbiguousElement<AS>>,
+pub struct Fragment {
+    children: Vec<AmbiguousElement>,
 }
 
-impl Parse for Fragment<AS_RSX> {
+impl Parse for Fragment {
     fn parse(input: ParseStream) -> Result<Self> {
         input.parse::<Ident>()?;
 
@@ -33,7 +33,7 @@ impl Parse for Fragment<AS_RSX> {
         let content: ParseBuffer;
         syn::braced!(content in input);
         while !content.is_empty() {
-            content.parse::<AmbiguousElement<AS_RSX>>()?;
+            content.parse::<AmbiguousElement>()?;
 
             if content.peek(Token![,]) {
                 let _ = content.parse::<Token![,]>();
@@ -43,27 +43,7 @@ impl Parse for Fragment<AS_RSX> {
     }
 }
 
-impl Parse for Fragment<AS_HTML> {
-    fn parse(input: ParseStream) -> Result<Self> {
-        input.parse::<Ident>()?;
-
-        let children = Vec::new();
-
-        // parse the guts
-        let content: ParseBuffer;
-        syn::braced!(content in input);
-        while !content.is_empty() {
-            content.parse::<AmbiguousElement<AS_HTML>>()?;
-
-            if content.peek(Token![,]) {
-                let _ = content.parse::<Token![,]>();
-            }
-        }
-        Ok(Self { children })
-    }
-}
-
-impl<const AS: HtmlOrRsx> ToTokens for Fragment<AS> {
+impl ToTokens for Fragment {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let childs = &self.children;
         let children = quote! {

+ 0 - 4
packages/core-macro/src/rsx/mod.rs

@@ -25,7 +25,3 @@ pub use component::*;
 pub use element::*;
 pub use fragment::*;
 pub use node::*;
-
-pub(crate) type HtmlOrRsx = bool;
-pub const AS_HTML: bool = true;
-pub const AS_RSX: bool = false;

+ 5 - 27
packages/core-macro/src/rsx/node.rs

@@ -10,13 +10,13 @@ use syn::{
 // ==============================================
 // Parse any div {} as a VElement
 // ==============================================
-pub enum BodyNode<const AS: HtmlOrRsx> {
-    Element(AmbiguousElement<AS>),
+pub enum BodyNode {
+    Element(AmbiguousElement),
     Text(TextNode),
     RawExpr(Expr),
 }
 
-impl Parse for BodyNode<AS_RSX> {
+impl Parse for BodyNode {
     fn parse(stream: ParseStream) -> Result<Self> {
         // Supposedly this approach is discouraged due to inability to return proper errors
         // TODO: Rework this to provide more informative errors
@@ -31,33 +31,11 @@ impl Parse for BodyNode<AS_RSX> {
             return Ok(BodyNode::Text(stream.parse::<TextNode>()?));
         }
 
-        Ok(BodyNode::Element(
-            stream.parse::<AmbiguousElement<AS_RSX>>()?,
-        ))
-    }
-}
-impl Parse for BodyNode<AS_HTML> {
-    fn parse(stream: ParseStream) -> Result<Self> {
-        // Supposedly this approach is discouraged due to inability to return proper errors
-        // TODO: Rework this to provide more informative errors
-
-        if stream.peek(token::Brace) {
-            let content;
-            syn::braced!(content in stream);
-            return Ok(BodyNode::RawExpr(content.parse::<Expr>()?));
-        }
-
-        if stream.peek(LitStr) {
-            return Ok(BodyNode::Text(stream.parse::<TextNode>()?));
-        }
-
-        Ok(BodyNode::Element(
-            stream.parse::<AmbiguousElement<AS_HTML>>()?,
-        ))
+        Ok(BodyNode::Element(stream.parse::<AmbiguousElement>()?))
     }
 }
 
-impl<const AS: HtmlOrRsx> ToTokens for BodyNode<AS> {
+impl ToTokens for BodyNode {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         match &self {
             BodyNode::Element(el) => el.to_tokens(tokens),

+ 2 - 2
packages/core/Cargo.toml

@@ -42,13 +42,13 @@ serde_repr = { version = "0.1.7", optional = true }
 
 [dev-dependencies]
 anyhow = "1.0.42"
-async-std = { version = "1.9.0", features = ["attributes"] }
-criterion = "0.3.5"
 dioxus-html = { path = "../html" }
 fern = { version = "0.6.0", features = ["colored"] }
 rand = { version = "0.8.4", features = ["small_rng"] }
 simple_logger = "1.13.0"
 dioxus-core-macro = { path = "../core-macro", version = "0.1.2" }
+async-std = { version = "1.9.0", features = ["attributes"] }
+criterion = "0.3.5"
 
 [features]
 default = []

+ 2 - 2
packages/core/examples/async.rs

@@ -7,9 +7,9 @@ fn main() {
 
 const App: FC<()> = |(cx, props)| {
     let id = cx.scope_id();
-    cx.submit_task(Box::pin(async move { id }));
+    // cx.submit_task(Box::pin(async move { id }));
 
-    let (handle, contents) = use_task(cx, || async { "hello world".to_string() });
+    // let (handle, contents) = use_task(cx, || async { "hello world".to_string() });
 
     todo!()
 };

+ 1 - 0
packages/core/examples/expand.rs

@@ -0,0 +1 @@
+fn main( ) {}

+ 5 - 2
packages/core/examples/jsframework.rs

@@ -1,3 +1,5 @@
+#![allow(non_snake_case)]
+
 use dioxus::component::Component;
 use dioxus::events::on::MouseEvent;
 use dioxus_core as dioxus;
@@ -13,7 +15,7 @@ fn main() {
     assert!(g.edits.len() > 1);
 }
 
-static App: FC<()> = |(cx, props)| {
+fn App((cx, props): Component<()>) -> DomTree {
     let mut rng = SmallRng::from_entropy();
     let rows = (0..10_000_usize).map(|f| {
         let label = Label::new(&mut rng);
@@ -31,13 +33,14 @@ static App: FC<()> = |(cx, props)| {
             }
         }
     })
-};
+}
 
 #[derive(PartialEq, Props)]
 struct RowProps {
     row_id: usize,
     label: Label,
 }
+
 fn Row((cx, props): Component<RowProps>) -> DomTree {
     let handler = move |evt: MouseEvent| {
         let g = evt.button;

+ 4 - 22
packages/core/examples/syntax.rs

@@ -8,31 +8,13 @@ use dioxus_html as dioxus_elements;
 fn main() {}
 
 fn html_usage() {
-    let r = html! {
-        <div>
-            "hello world"
-            <div>
-            </div>
-            <div />
-            "hello world"
-            <Fragment>
-            </Fragment>
-        </div>
-    };
+    let mo = move |_| {};
     let r = rsx! {
         div {
+            onclick: move |_| {}
+            onmouseover: {mo}
+            "type": "bar",
             "hello world"
         }
     };
 }
-
-fn rsx_uage() {
-    // let r = html! {
-    //     <Fragment>
-    //         "hello world"
-    //         "hello world"
-    //         "hello world"
-    //         "hello world"
-    //     </Fragment>
-    // };
-}

+ 39 - 19
packages/core/src/diff.rs

@@ -221,12 +221,12 @@ impl<'bump> DiffMachine<'bump> {
             MountType::Replace { old } => {
                 if let Some(old_id) = old.try_mounted_id() {
                     self.mutations.replace_with(old_id, nodes_created as u32);
-                    self.remove_nodes(Some(old));
+                    self.remove_nodes(Some(old), true);
                 } else {
                     if let Some(id) = self.find_first_element_id(old) {
                         self.mutations.replace_with(id, nodes_created as u32);
                     }
-                    self.remove_nodes(Some(old));
+                    self.remove_nodes(Some(old), true);
                 }
             }
 
@@ -369,7 +369,11 @@ impl<'bump> DiffMachine<'bump> {
 
         let new_component = self.vdom.get_scope_mut(new_idx).unwrap();
 
-        log::debug!("initializing component {:?}", new_idx);
+        log::debug!(
+            "initializing component {:?} with height {:?}",
+            new_idx,
+            parent_scope.height + 1
+        );
 
         // Run the scope for one iteration to initialize it
         if new_component.run_scope(self.vdom) {
@@ -614,7 +618,7 @@ impl<'bump> DiffMachine<'bump> {
                 self.stack.create_children(new, MountType::Append);
             }
             (_, []) => {
-                self.remove_nodes(old);
+                self.remove_nodes(old, true);
             }
             ([VNode::Anchor(old_anchor)], [VNode::Anchor(new_anchor)]) => {
                 old_anchor.dom_id.set(new_anchor.dom_id.get());
@@ -667,7 +671,7 @@ impl<'bump> DiffMachine<'bump> {
 
         use std::cmp::Ordering;
         match old.len().cmp(&new.len()) {
-            Ordering::Greater => self.remove_nodes(&old[new.len()..]),
+            Ordering::Greater => self.remove_nodes(&old[new.len()..], true),
             Ordering::Less => {
                 self.stack.create_children(
                     &new[old.len()..],
@@ -750,7 +754,7 @@ impl<'bump> DiffMachine<'bump> {
         );
         if new_middle.is_empty() {
             // remove the old elements
-            self.remove_nodes(old_middle);
+            self.remove_nodes(old_middle, true);
         } else if old_middle.is_empty() {
             // there were no old elements, so just create the new elements
             // we need to find the right "foothold" though - we shouldn't use the "append" at all
@@ -825,7 +829,7 @@ impl<'bump> DiffMachine<'bump> {
         // And if that was all of the new children, then remove all of the remaining
         // old children and we're finished.
         if left_offset == new.len() {
-            self.remove_nodes(&old[left_offset..]);
+            self.remove_nodes(&old[left_offset..], true);
             return None;
         }
 
@@ -1070,7 +1074,7 @@ impl<'bump> DiffMachine<'bump> {
         new: &'bump VNode<'bump>,
     ) {
         if let Some(first_old) = old.get(0) {
-            self.remove_nodes(&old[1..]);
+            self.remove_nodes(&old[1..], true);
             self.stack
                 .create_node(new, MountType::Replace { old: first_old });
         } else {
@@ -1080,42 +1084,58 @@ impl<'bump> DiffMachine<'bump> {
 
     /// schedules nodes for garbage collection and pushes "remove" to the mutation stack
     /// remove can happen whenever
-    fn remove_nodes(&mut self, nodes: impl IntoIterator<Item = &'bump VNode<'bump>>) {
+    fn remove_nodes(
+        &mut self,
+        nodes: impl IntoIterator<Item = &'bump VNode<'bump>>,
+        gen_muts: bool,
+    ) {
         // or cache the vec on the diff machine
         for node in nodes {
             match node {
                 VNode::Text(t) => {
-                    if let Some(id) = t.dom_id.get() {
+                    let id = t.dom_id.get().unwrap();
+                    self.vdom.collect_garbage(id);
+
+                    if gen_muts {
                         self.mutations.remove(id.as_u64());
-                        self.vdom.collect_garbage(id);
                     }
                 }
                 VNode::Suspended(s) => {
-                    if let Some(id) = s.dom_id.get() {
+                    let id = s.dom_id.get().unwrap();
+                    self.vdom.collect_garbage(id);
+
+                    if gen_muts {
                         self.mutations.remove(id.as_u64());
-                        self.vdom.collect_garbage(id);
                     }
                 }
                 VNode::Anchor(a) => {
-                    if let Some(id) = a.dom_id.get() {
+                    let id = a.dom_id.get().unwrap();
+                    self.vdom.collect_garbage(id);
+
+                    if gen_muts {
                         self.mutations.remove(id.as_u64());
-                        self.vdom.collect_garbage(id);
                     }
                 }
                 VNode::Element(e) => {
-                    if let Some(id) = e.dom_id.get() {
+                    let id = e.dom_id.get().unwrap();
+
+                    if gen_muts {
                         self.mutations.remove(id.as_u64());
                     }
+
+                    self.remove_nodes(e.children, false);
                 }
                 VNode::Fragment(f) => {
-                    self.remove_nodes(f.children);
+                    self.remove_nodes(f.children, gen_muts);
                 }
 
                 VNode::Component(c) => {
                     let scope_id = c.associated_scope.get().unwrap();
                     let scope = self.vdom.get_scope_mut(scope_id).unwrap();
                     let root = scope.root_node();
-                    self.remove_nodes(Some(root));
+                    self.remove_nodes(Some(root), gen_muts);
+
+                    log::debug!("Destroying scope {:?}", scope_id);
                     let mut s = self.vdom.try_remove(scope_id).unwrap();
                     s.hooks.clear_hooks();
                 }
@@ -1132,7 +1152,7 @@ impl<'bump> DiffMachine<'bump> {
         new: &'bump [VNode<'bump>],
     ) {
         if let Some(first_old) = old.get(0) {
-            self.remove_nodes(&old[1..]);
+            self.remove_nodes(&old[1..], true);
             self.stack
                 .create_children(new, MountType::Replace { old: first_old })
         } else {

+ 2 - 0
packages/core/src/events.rs

@@ -137,6 +137,7 @@ pub mod on {
     }
 
     // The Dioxus Synthetic event system
+    // todo: move these into the html event system. dioxus accepts *any* event, so having these here doesn't make sense.
     event_directory! {
         ClipboardEvent: [
             /// Called when "copy"
@@ -1031,6 +1032,7 @@ pub enum KeyCode {
     // kanji, = 244
     // unlock trackpad (Chrome/Edge), = 251
     // toggle touchpad, = 255
+    #[cfg_attr(feature = "serialize", serde(other))]
     Unknown,
 }
 

+ 1 - 0
packages/core/src/hooklist.rs

@@ -57,6 +57,7 @@ impl HookList {
     }
 
     pub fn clear_hooks(&mut self) {
+        log::debug!("clearing hooks...");
         self.vals
             .borrow_mut()
             .drain(..)

+ 4 - 0
packages/core/src/nodes.rs

@@ -487,6 +487,8 @@ impl<'a> NodeFactory<'a> {
                         props.memoize(real_other)
                     };
 
+                    log::debug!("comparing props...");
+
                     // It's only okay to memoize if there are no children and the props can be memoized
                     // Implementing memoize is unsafe and done automatically with the props trait
                     matches!((props_memoized, children.is_empty()), (true, true))
@@ -502,6 +504,7 @@ impl<'a> NodeFactory<'a> {
 
             let drop_props: &mut dyn FnMut() = bump.alloc_with(|| {
                 move || unsafe {
+                    log::debug!("dropping props!");
                     if !has_dropped {
                         let real_other = raw_props as *mut _ as *mut P;
                         let b = BumpBox::from_raw(real_other);
@@ -525,6 +528,7 @@ impl<'a> NodeFactory<'a> {
 
         let caller: &'a mut dyn for<'b> Fn(&'b Scope) -> DomTree<'b> =
             bump.alloc(move |scope: &Scope| -> DomTree {
+                log::debug!("calling component renderr {:?}", scope.our_arena_idx);
                 let props: &'_ P = unsafe { &*(raw_props as *const P) };
                 let res = component((Context { scope }, props));
                 unsafe { std::mem::transmute(res) }

+ 0 - 2
packages/core/src/scheduler.rs

@@ -407,8 +407,6 @@ impl Scheduler {
 
         let work_completed = machine.work(deadline_reached);
 
-        log::debug!("Working finished? {:?}", work_completed);
-
         // log::debug!("raw edits {:?}", machine.mutations.edits);
 
         let mut machine: DiffMachine<'static> = unsafe { std::mem::transmute(machine) };

+ 6 - 0
packages/core/src/scope.rs

@@ -43,6 +43,8 @@ pub struct Scope {
 
     // State
     pub(crate) hooks: HookList,
+
+    // todo: move this into a centralized place - is more memory efficient
     pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
 
     // whenever set_state is called, we fire off a message to the scheduler
@@ -345,11 +347,15 @@ impl Scope {
         debug_assert!(self.suspended_nodes.borrow().is_empty());
         debug_assert!(self.borrowed_props.borrow().is_empty());
 
+        log::debug!("Borrowed stuff is successfully cleared");
+
         // Cast the caller ptr from static to one with our own reference
         let render: &dyn for<'b> Fn(&'b Scope) -> DomTree<'b> = unsafe { &*self.caller };
 
         // Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders.
         if let Some(new_head) = render(self) {
+            log::debug!("Render is successful");
+
             // the user's component succeeded. We can safely cycle to the next frame
             self.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
             self.frames.cycle_frame();

+ 12 - 8
packages/core/tests/borrowedstate.rs

@@ -1,9 +1,16 @@
-use dioxus::{nodes::VSuspended, prelude::*, DomEdit, TestDom};
+#![allow(non_snake_case)]
+
+use dioxus::prelude::*;
 use dioxus_core as dioxus;
 use dioxus_core_macro::*;
 use dioxus_html as dioxus_elements;
 
-static Parent: FC<()> = |(cx, props)| {
+#[test]
+fn test_borrowed_state() {
+    let _ = VirtualDom::new(Parent);
+}
+
+fn Parent((cx, _): Component<()>) -> DomTree {
     let value = cx.use_hook(|_| String::new(), |f| &*f, |_| {});
 
     cx.render(rsx! {
@@ -14,11 +21,11 @@ static Parent: FC<()> = |(cx, props)| {
             Child { name: value }
         }
     })
-};
+}
 
 #[derive(Props)]
 struct ChildProps<'a> {
-    name: &'a String,
+    name: &'a str,
 }
 
 fn Child<'a>((cx, props): Component<'a, ChildProps>) -> DomTree<'a> {
@@ -32,7 +39,7 @@ fn Child<'a>((cx, props): Component<'a, ChildProps>) -> DomTree<'a> {
 
 #[derive(Props)]
 struct Grandchild<'a> {
-    name: &'a String,
+    name: &'a str,
 }
 
 fn Child2<'a>((cx, props): Component<'a, Grandchild>) -> DomTree<'a> {
@@ -40,6 +47,3 @@ fn Child2<'a>((cx, props): Component<'a, Grandchild>) -> DomTree<'a> {
         div { "Hello {props.name}!" }
     })
 }
-
-#[test]
-fn test_borrowed_state() {}

+ 2 - 0
packages/core/tests/create_dom.rs

@@ -1,3 +1,5 @@
+#![allow(unused, non_upper_case_globals)]
+
 //! Prove that the dom works normally through virtualdom methods.
 //!
 //! This methods all use "rebuild" which completely bypasses the scheduler.

+ 1 - 0
packages/core/tests/diffing.rs

@@ -1,3 +1,4 @@
+#![allow(unused, non_upper_case_globals)]
 //! Diffing Tests
 //!
 //! These tests only verify that the diffing algorithm works properly for single components.

+ 2 - 5
packages/core/tests/display_vdom.rs

@@ -1,19 +1,16 @@
+#![allow(unused, non_upper_case_globals)]
+
 //! test that we can display the virtualdom properly
 //!
 //!
 //!
 
-use std::{cell::RefCell, rc::Rc};
-
-use anyhow::{Context, Result};
 use dioxus::prelude::*;
 use dioxus_core as dioxus;
 use dioxus_core_macro::*;
 use dioxus_html as dioxus_elements;
 mod test_logging;
 
-const IS_LOGGING_ENABLED: bool = true;
-
 #[test]
 fn please_work() {
     static App: FC<()> = |(cx, props)| {

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików