소스 검색

Feat: more docs, dissolve vnode crate into dioxus-core

Jonathan Kelley 4 년 전
부모
커밋
9c616ea

+ 1 - 0
.vscode/spellright.dict

@@ -1,3 +1,4 @@
 bearly
 Clippy
 tide_ssr
+Liveview

+ 3 - 3
Cargo.toml

@@ -13,8 +13,8 @@ members = [
     #
     # Pulled from percy
     "packages/html-macro",
-    "packages/html-macro-test",
-    "packages/virtual-dom-rs",
-    "packages/virtual-node",
+    # "packages/html-macro-test",
+    # "packages/virtual-dom-rs",
+    # "packages/virtual-node",
     "examples",
 ]

+ 27 - 12
README.md

@@ -1,6 +1,28 @@
 # Dioxus: A concurrent, functional, virtual DOM for Rust
 
-Dioxus is a new approach for creating performant cross platform user experiences in Rust. In Dioxus, the UI is represented by a tree of Virtual Nodes not bound to any renderer framework. Instead, external renderers can leverage Dioxus' virtual DOM and event system as a source of truth for rendering to a medium of their choice. Developers used to crafting react-based experiences should feel comfortable with Dioxus.
+Dioxus is a new approach for creating performant cross platform user experiences in Rust. In Dioxus, the UI is represented as a tree of Virtual Nodes not bound to any specific renderer. Instead, external renderers can leverage Dioxus' virtual DOM and event system as a source of truth for rendering to a medium of their choice. Developers used to crafting react-based experiences should feel comfortable with Dioxus.
+
+Dioxus is unique in the space of UI for Rust. Dioxus supports a renderer approach called "broadcasting" where two VDoms with separate renderers can sync their UI states remotely. Our goal as a framework is to work towards "Dioxus Liveview" where a server and client work in tandem, eliminating the need for frontend-specific APIs altogether.
+
+## Features
+Dioxus' goal is to be the most advanced UI system for Rust, targeting isomorphism and hybrid approaches. Our goal is to never be burdened with context switching between various programming languages, build tools, and environment idiosyncrasies. 
+
+Dioxus Core supports:
+- [ ] Hooks
+- [ ] Concurrent rendering
+- [ ] Context subscriptions
+- [ ] State management integrations
+
+On top of these, we have several projects you can find in the `packages` folder.
+- [x] Testing, development, and packaging tools for Dioxus apps
+- [ ] Redux-style global state management
+- [ ] Recoil-style global state management
+- [ ] Hybrid apps (SSR + Web)
+- [ ] Live view
+- [ ] Desktop Applications
+- [ ] iOS apps
+- [ ] Android apps
+- [ ] AR/VR Apps
 
 ## 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.
@@ -12,16 +34,17 @@ struct MyProps {
 }
 
 fn Example(ctx: &Context<MyProps>) -> VNode {
-    html! { <div> "Hello {:?ctx.props().name}!" </div> }
+    html! { <div> "Hello {ctx.props.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. If using the functional component macro, it's possible to inline props into the function definition itself.
 
 ```rust
+// A very terse component!
 #[functional_component]
 fn Example(ctx: &Context<{ name: String }>) -> VNode {
-    html! { <div> "Hello {:?name}!" </div> }
+    html! { <div> "Hello {name}!" </div> }
 }
 
 // or
@@ -32,14 +55,6 @@ static Example: FC<{ name: String }> = |ctx| html! { <div> "Hello {:?name}!" </d
 
 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, or templating engine. Work is being done on a terra template processor for existing templates.
 
-## Features
-
-Dioxus supports:
-- Hooks
-- Concurrent rendering
-- Context subscriptions
-- Isomorphism
-
 ## Concurrency
 
 Dioxus, using React as a reference, provides the ability to have asynchronous components. With Dioxus, this is a valid component:
@@ -61,7 +76,7 @@ Asynchronous components are powerful but can also be easy to misuse as they paus
 ## Examples
 We use `diopack` to build and test webapps. 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 pacakages/diopack` will get it installed. 
 
-Alternatively, `Trunk` works but can't run examples.
+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`
 - simple_wasm: Simple WASM app that says hello. `diopack develop --example simple`

+ 2 - 2
examples/Cargo.toml

@@ -7,8 +7,8 @@ edition = "2018"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-fern = { vesion = "0.6.0", features = ["colored"] }
-log = "0.4.13"
+fern = { version = "0.6.0", features = ["colored"] }
+log = "0.4.1"
 dioxus-core = { path = "../packages/core" }
 rand = "0.8.2"
 

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

@@ -9,8 +9,8 @@ type VirtualNode = VNode;
 
 pub fn main() {
     let dom = VirtualDom::new(root);
-    let mut renderer = TextRenderer::new(dom);
-    let output = renderer.render();
+    // let mut renderer = TextRenderer::new(dom);
+    // let output = renderer.render();
 }
 
 fn root(ctx: &mut AnyContext) -> VNode {

+ 56 - 49
packages/core/src/lib.rs

@@ -1,25 +1,15 @@
 //! Dioxus: a concurrent, functional, virtual dom for any renderer in Rust
 //!
 //!
-//!
-//!
-//!
-//!
-//!
-//!
-//!
-//!
-//!
-//!
-//!
-//!
 
 /// Re-export common types for ease of development use.
 /// Essential when working with the html! macro
+///
+///
+///
 pub mod prelude {
     pub use crate::component::Context;
     use crate::nodes;
-    pub use crate::renderer::TextRenderer;
     pub use crate::types::FC;
     pub use crate::virtual_dom::VirtualDom;
     pub use nodes::iterables::IterableNodes;
@@ -33,6 +23,9 @@ pub mod prelude {
 }
 
 /// The Dioxus Virtual Dom integrates an event system and virtual nodes to create reactive user interfaces.
+///
+///
+///
 pub mod virtual_dom {
     use super::*;
 
@@ -48,6 +41,9 @@ pub mod virtual_dom {
 }
 
 /// Virtual Node Support
+///
+///
+///
 pub mod nodes {
     pub use vcomponent::VComponent;
     pub use velement::VElement;
@@ -513,34 +509,55 @@ pub mod nodes {
         //     }
         // }
     }
-}
 
-pub mod diff {
-    pub enum Patch {}
-}
+    #[cfg(test)]
+    mod tests {
+        use super::*;
 
-/// Example on how to craft a renderer that interacts with the VirtualDom
-pub mod renderer {
-    use crate::virtual_dom::VirtualDom;
+        #[test]
+        fn self_closing_tag_to_string() {
+            let node = VNode::element("br");
 
-    use super::*;
+            // No </br> since self closing tag
+            assert_eq!(&node.to_string(), "<br>");
+        }
 
-    /// Renders a full Dioxus app to a String
-    ///
-    pub struct TextRenderer {}
+        #[test]
+        fn to_string() {
+            let mut node = VNode::Element(VElement::new("div"));
+            node.as_velement_mut()
+                .unwrap()
+                .attrs
+                .insert("id".into(), "some-id".into());
 
-    impl TextRenderer {
-        /// Create a new Text Renderer which renders the VirtualDom to a string
-        pub fn new(dom: VirtualDom) -> Self {
-            Self {}
-        }
+            let mut child = VNode::Element(VElement::new("span"));
+
+            let mut text = VNode::Text(VText::new("Hello world"));
 
-        pub fn render(&mut self) -> String {
-            todo!()
+            child.as_velement_mut().unwrap().children.push(text);
+
+            node.as_velement_mut().unwrap().children.push(child);
+
+            let expected = r#"<div id="some-id"><span>Hello world</span></div>"#;
+
+            assert_eq!(node.to_string(), expected);
         }
     }
 }
 
+///
+///
+///
+///
+pub mod diff {
+    pub enum Patch {}
+}
+
+///
+///
+///
+///
+///
 pub mod component {
 
     /// A wrapper around component contexts that hides component property types
@@ -556,6 +573,9 @@ pub mod component {
 }
 
 /// Utility types that wrap internals
+///
+///
+///
 pub mod types {
     use super::*;
     use component::{AnyContext, Context};
@@ -567,6 +587,9 @@ pub mod types {
 /// TODO @Jon
 /// Figure out if validation should be its own crate, or embedded directly into dioxus
 /// Should we even be bothered with validation?
+///
+///
+///
 mod validation {
     use once_cell::sync::Lazy;
     use std::collections::HashSet;
@@ -587,11 +610,9 @@ mod validation {
 
     /// Whether or not this tag is self closing
     ///
-    /// ```
-    /// use html_validation::is_self_closing;
-    ///
+    /// ```ignore
+    /// use dioxus_core::validation::is_self_closing;
     /// assert_eq!(is_self_closing("br"), true);
-    ///
     /// assert_eq!(is_self_closing("div"), false);
     /// ```
     pub fn is_self_closing(tag: &str) -> bool {
@@ -599,17 +620,3 @@ mod validation {
         // SELF_CLOSING_TAGS.contains(tag) || is_self_closing_svg_tag(tag)
     }
 }
-
-#[cfg(test)]
-mod integration_tests {
-    use crate::prelude::*;
-    type VirtualNode = VNode;
-
-    /// Test a basic usage of a virtual dom + text renderer combo
-    #[test]
-    fn simple_integration() {
-        let dom = VirtualDom::new(|_| html! { <div>Hello World!</div> });
-        let mut renderer = TextRenderer::new(dom);
-        let output = renderer.render();
-    }
-}

+ 16 - 0
packages/core/tests/integration.rs

@@ -0,0 +1,16 @@
+///
+///
+///
+///
+///
+// use crate::prelude::*;
+use dioxus_core::prelude::*;
+type VirtualNode = VNode;
+
+/// Test a basic usage of a virtual dom + text renderer combo
+#[test]
+fn simple_integration() {
+    let dom = VirtualDom::new(|_| html! { <div>Hello World!</div> });
+    // let mut renderer = TextRenderer::new(dom);
+    // let output = renderer.render();
+}

+ 0 - 35
packages/virtual-node/Cargo.toml

@@ -1,35 +0,0 @@
-[package]
-name = "virtual-node"
-version = "0.2.7"
-description = "A standalone Virtual DOM"
-authors = ["Chinedu Francis Nwafili <frankie.nwafili@gmail.com>"]
-keywords = ["virtual", "dom", "wasm", "assembly", "webassembly"]
-license = "MIT/Apache-2.0"
-repository = "https://github.com/chinedufn/percy"
-documentation = "https://chinedufn.github.io/percy/api/virtual_node/"
-edition = "2018"
-
-[dependencies]
-js-sys = "0.3"
-wasm-bindgen = "0.2.33"
-html-validation = {path = "../html-validation", version = "0.1.1"}
-lazy_static = "1.0"
-
-[dependencies.web-sys]
-version = "0.3"
-features = [
-    "Comment",
-    "Document",
-    "Element",
-    "HtmlElement",
-    "EventTarget",
-    "HtmlCollection",
-    "Node",
-    "NodeList",
-    "Text",
-    "Window",
-    "Event",
-    "MouseEvent",
-    "InputEvent",
-]
-

+ 0 - 656
packages/virtual-node/src/lib.rs

@@ -1,656 +0,0 @@
-//! The virtual_node module exposes the `VirtualNode` struct and methods that power our
-//! virtual dom.
-
-// TODO: A few of these dependencies (including js_sys) are used to power events.. yet events
-// only work on wasm32 targest. So we should start sprinkling some
-//
-// #[cfg(target_arch = "wasm32")]
-// #[cfg(not(target_arch = "wasm32"))]
-//
-// Around in order to get rid of dependencies that we don't need in non wasm32 targets
-
-use std::collections::HashMap;
-use std::fmt;
-use std::rc::Rc;
-
-pub mod virtual_node_test_utils;
-
-use web_sys::{self, Element, EventTarget, Node, Text};
-
-use wasm_bindgen::JsCast;
-use wasm_bindgen::JsValue;
-
-use std::ops::Deref;
-use std::sync::Mutex;
-
-// Used to uniquely identify elements that contain closures so that the DomUpdater can
-// look them up by their unique id.
-// When the DomUpdater sees that the element no longer exists it will drop all of it's
-// Rc'd Closures for those events.
-use lazy_static::lazy_static;
-lazy_static! {
-    static ref ELEM_UNIQUE_ID: Mutex<u32> = Mutex::new(0);
-}
-
-/// When building your views you'll typically use the `html!` macro to generate
-/// `VirtualNode`'s.
-///
-/// `html! { <div> <span></span> </div> }` really generates a `VirtualNode` with
-/// one child (span).
-///
-/// Later, on the client side, you'll use the `diff` and `patch` modules to
-/// update the real DOM with your latest tree of virtual nodes (virtual dom).
-///
-/// Or on the server side you'll just call `.to_string()` on your root virtual node
-/// in order to recursively render the node and all of its children.
-///
-/// TODO: Make all of these fields private and create accessor methods
-/// TODO: Create a builder to create instances of VirtualNode::Element with
-/// attrs and children without having to explicitly create a VElement
-#[derive(PartialEq)]
-pub enum VirtualNode {
-    /// An element node (node type `ELEMENT_NODE`).
-    Element(VElement),
-    /// A text node (node type `TEXT_NODE`).
-    ///
-    /// Note: This wraps a `VText` instead of a plain `String` in
-    /// order to enable custom methods like `create_text_node()` on the
-    /// wrapped type.
-    Text(VText),
-    // /// A User-defined componen node (node type COMPONENT_NODE)
-    // Component(VComponent),
-}
-
-#[derive(PartialEq)]
-pub struct VElement {
-    /// The HTML tag, such as "div"
-    pub tag: String,
-
-    /// HTML attributes such as id, class, style, etc
-    pub attrs: HashMap<String, String>,
-
-    /// Events that will get added to your real DOM element via `.addEventListener`
-    pub events: Events,
-
-    /// The children of this `VirtualNode`. So a <div> <em></em> </div> structure would
-    /// have a parent div and one child, em.
-    pub children: Vec<VirtualNode>,
-}
-
-#[derive(PartialEq)]
-pub struct VText {
-    pub text: String,
-}
-
-impl VirtualNode {
-    /// Create a new virtual element node with a given tag.
-    ///
-    /// These get patched into the DOM using `document.createElement`
-    ///
-    /// ```ignore
-    /// use virtual_dom_rs::VirtualNode;
-    ///
-    /// let div = VirtualNode::element("div");
-    /// ```
-    pub fn element<S>(tag: S) -> Self
-    where
-        S: Into<String>,
-    {
-        VirtualNode::Element(VElement::new(tag))
-    }
-
-    /// Create a new virtual text node with the given text.
-    ///
-    /// These get patched into the DOM using `document.createTextNode`
-    ///
-    /// ```ignore
-    /// use virtual_dom_rs::VirtualNode;
-    ///
-    /// let div = VirtualNode::text("div");
-    /// ```
-    pub fn text<S>(text: S) -> Self
-    where
-        S: Into<String>,
-    {
-        VirtualNode::Text(VText::new(text.into()))
-    }
-
-    /// Return a [`VElement`] reference, if this is an [`Element`] variant.
-    ///
-    /// [`VElement`]: struct.VElement.html
-    /// [`Element`]: enum.VirtualNode.html#variant.Element
-    pub fn as_velement_ref(&self) -> Option<&VElement> {
-        match self {
-            VirtualNode::Element(ref element_node) => Some(element_node),
-            _ => None,
-        }
-    }
-
-    /// Return a mutable [`VElement`] reference, if this is an [`Element`] variant.
-    ///
-    /// [`VElement`]: struct.VElement.html
-    /// [`Element`]: enum.VirtualNode.html#variant.Element
-    pub fn as_velement_mut(&mut self) -> Option<&mut VElement> {
-        match self {
-            VirtualNode::Element(ref mut element_node) => Some(element_node),
-            _ => None,
-        }
-    }
-
-    /// Return a [`VText`] reference, if this is an [`Text`] variant.
-    ///
-    /// [`VText`]: struct.VText.html
-    /// [`Text`]: enum.VirtualNode.html#variant.Text
-    pub fn as_vtext_ref(&self) -> Option<&VText> {
-        match self {
-            VirtualNode::Text(ref text_node) => Some(text_node),
-            _ => None,
-        }
-    }
-
-    /// Return a mutable [`VText`] reference, if this is an [`Text`] variant.
-    ///
-    /// [`VText`]: struct.VText.html
-    /// [`Text`]: enum.VirtualNode.html#variant.Text
-    pub fn as_vtext_mut(&mut self) -> Option<&mut VText> {
-        match self {
-            VirtualNode::Text(ref mut text_node) => Some(text_node),
-            _ => None,
-        }
-    }
-
-    /// Create and return a `CreatedNode` instance (containing a DOM `Node`
-    /// together with potentially related closures) for this virtual node.
-    pub fn create_dom_node(&self) -> CreatedNode<Node> {
-        match self {
-            VirtualNode::Text(text_node) => {
-                CreatedNode::without_closures(text_node.create_text_node())
-            }
-            VirtualNode::Element(element_node) => element_node.create_element_node().into(),
-            // VirtualNode::Component(_) => todo!("WIP on Component Syntax"),
-        }
-    }
-
-    /// Used by html-macro to insert space before text that is inside of a block that came after
-    /// an open tag.
-    ///
-    /// html! { <div> {world}</div> }
-    ///
-    /// So that we end up with <div> world</div> when we're finished parsing.
-    pub fn insert_space_before_text(&mut self) {
-        match self {
-            VirtualNode::Text(text_node) => {
-                text_node.text = " ".to_string() + &text_node.text;
-            }
-            _ => {}
-        }
-    }
-
-    /// Used by html-macro to insert space after braced text if we know that the next block is
-    /// another block or a closing tag.
-    ///
-    /// html! { <div>{Hello} {world}</div> } -> <div>Hello world</div>
-    /// html! { <div>{Hello} </div> } -> <div>Hello </div>
-    ///
-    /// So that we end up with <div>Hello world</div> when we're finished parsing.
-    pub fn insert_space_after_text(&mut self) {
-        match self {
-            VirtualNode::Text(text_node) => {
-                text_node.text += " ";
-            }
-            _ => {}
-        }
-    }
-}
-
-impl VElement {
-    pub fn new<S>(tag: S) -> Self
-    where
-        S: Into<String>,
-    {
-        VElement {
-            tag: tag.into(),
-            attrs: HashMap::new(),
-            events: Events(HashMap::new()),
-            children: vec![],
-        }
-    }
-
-    /// Build a DOM element by recursively creating DOM nodes for this element and it's
-    /// children, it's children's children, etc.
-    pub fn create_element_node(&self) -> CreatedNode<Element> {
-        let document = web_sys::window().unwrap().document().unwrap();
-
-        let element = if html_validation::is_svg_namespace(&self.tag) {
-            document
-                .create_element_ns(Some("http://www.w3.org/2000/svg"), &self.tag)
-                .unwrap()
-        } else {
-            document.create_element(&self.tag).unwrap()
-        };
-
-        let mut closures = HashMap::new();
-
-        self.attrs.iter().for_each(|(name, value)| {
-            if name == "unsafe_inner_html" {
-                element.set_inner_html(value);
-
-                return;
-            }
-
-            element
-                .set_attribute(name, value)
-                .expect("Set element attribute in create element");
-        });
-
-        if self.events.0.len() > 0 {
-            let unique_id = create_unique_identifier();
-
-            element
-                .set_attribute("data-vdom-id".into(), &unique_id.to_string())
-                .expect("Could not set attribute on element");
-
-            closures.insert(unique_id, vec![]);
-
-            self.events.0.iter().for_each(|(onevent, callback)| {
-                // onclick -> click
-                let event = &onevent[2..];
-
-                let current_elem: &EventTarget = element.dyn_ref().unwrap();
-
-                current_elem
-                    .add_event_listener_with_callback(
-                        event,
-                        callback.as_ref().as_ref().unchecked_ref(),
-                    )
-                    .unwrap();
-
-                closures
-                    .get_mut(&unique_id)
-                    .unwrap()
-                    .push(Rc::clone(callback));
-            });
-        }
-
-        let mut previous_node_was_text = false;
-
-        self.children.iter().for_each(|child| {
-            match child {
-                VirtualNode::Text(text_node) => {
-                    let current_node = element.as_ref() as &web_sys::Node;
-
-                    // We ensure that the text siblings are patched by preventing the browser from merging
-                    // neighboring text nodes. Originally inspired by some of React's work from 2016.
-                    //  -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
-                    //  -> https://github.com/facebook/react/pull/5753
-                    //
-                    // `ptns` = Percy text node separator
-                    if previous_node_was_text {
-                        let separator = document.create_comment("ptns");
-                        current_node
-                            .append_child(separator.as_ref() as &web_sys::Node)
-                            .unwrap();
-                    }
-
-                    current_node
-                        .append_child(&text_node.create_text_node())
-                        .unwrap();
-
-                    previous_node_was_text = true;
-                }
-                VirtualNode::Element(element_node) => {
-                    previous_node_was_text = false;
-
-                    let child = element_node.create_element_node();
-                    let child_elem: Element = child.node;
-
-                    closures.extend(child.closures);
-
-                    element.append_child(&child_elem).unwrap();
-                } // VirtualNode::Component(_) => {
-                  //     todo!("WIP on Component Syntax")
-                  // }
-            }
-        });
-
-        if let Some(on_create_elem) = self.events.0.get("on_create_elem") {
-            let on_create_elem: &js_sys::Function =
-                on_create_elem.as_ref().as_ref().unchecked_ref();
-            on_create_elem
-                .call1(&wasm_bindgen::JsValue::NULL, &element)
-                .unwrap();
-        }
-
-        CreatedNode {
-            node: element,
-            closures,
-        }
-    }
-}
-
-impl VText {
-    /// Create an new `VText` instance with the specified text.
-    pub fn new<S>(text: S) -> Self
-    where
-        S: Into<String>,
-    {
-        VText { text: text.into() }
-    }
-
-    /// Return a `Text` element from a `VirtualNode`, typically right before adding it
-    /// into the DOM.
-    pub fn create_text_node(&self) -> Text {
-        let document = web_sys::window().unwrap().document().unwrap();
-        document.create_text_node(&self.text)
-    }
-}
-
-/// A node along with all of the closures that were created for that
-/// node's events and all of it's child node's events.
-pub struct CreatedNode<T> {
-    /// A `Node` or `Element` that was created from a `VirtualNode`
-    pub node: T,
-    /// A map of a node's unique identifier along with all of the Closures for that node.
-    ///
-    /// The DomUpdater uses this to look up nodes and see if they're still in the page. If not
-    /// the reference that we maintain to their closure will be dropped, thus freeing the Closure's
-    /// memory.
-    pub closures: HashMap<u32, Vec<DynClosure>>,
-}
-
-impl<T> CreatedNode<T> {
-    pub fn without_closures<N: Into<T>>(node: N) -> Self {
-        CreatedNode {
-            node: node.into(),
-            closures: HashMap::with_capacity(0),
-        }
-    }
-}
-
-impl<T> Deref for CreatedNode<T> {
-    type Target = T;
-    fn deref(&self) -> &Self::Target {
-        &self.node
-    }
-}
-
-impl From<CreatedNode<Element>> for CreatedNode<Node> {
-    fn from(other: CreatedNode<Element>) -> CreatedNode<Node> {
-        CreatedNode {
-            node: other.node.into(),
-            closures: other.closures,
-        }
-    }
-}
-
-fn create_unique_identifier() -> u32 {
-    let mut elem_unique_id = ELEM_UNIQUE_ID.lock().unwrap();
-
-    *elem_unique_id += 1;
-
-    *elem_unique_id
-}
-
-/// A trait with common functionality for rendering front-end views.
-pub trait View {
-    /// Render a VirtualNode, or any IntoIter<VirtualNode>
-    fn render(&self) -> VirtualNode;
-}
-
-impl<V> From<&V> for VirtualNode
-where
-    V: View,
-{
-    fn from(v: &V) -> Self {
-        v.render()
-    }
-}
-
-/// Used by the html! macro for all braced child nodes so that we can use any type
-/// that implements Into<IterableNodes>
-///
-/// html! { <div> { nodes } </div> }
-///
-/// nodes can be a String .. VirtualNode .. Vec<VirtualNode> ... etc
-pub struct IterableNodes(Vec<VirtualNode>);
-
-impl IterableNodes {
-    /// Retrieve the first node mutably
-    pub fn first(&mut self) -> &mut VirtualNode {
-        self.0.first_mut().unwrap()
-    }
-
-    /// Retrieve the last node mutably
-    pub fn last(&mut self) -> &mut VirtualNode {
-        self.0.last_mut().unwrap()
-    }
-}
-
-impl IntoIterator for IterableNodes {
-    type Item = VirtualNode;
-    // TODO: Is this possible with an array [VirtualNode] instead of a vec?
-    type IntoIter = ::std::vec::IntoIter<VirtualNode>;
-
-    fn into_iter(self) -> Self::IntoIter {
-        self.0.into_iter()
-    }
-}
-
-impl From<VirtualNode> for IterableNodes {
-    fn from(other: VirtualNode) -> Self {
-        IterableNodes(vec![other])
-    }
-}
-
-impl From<&str> for IterableNodes {
-    fn from(other: &str) -> Self {
-        IterableNodes(vec![VirtualNode::text(other)])
-    }
-}
-
-impl From<String> for IterableNodes {
-    fn from(other: String) -> Self {
-        IterableNodes(vec![VirtualNode::text(other.as_str())])
-    }
-}
-
-impl From<Vec<VirtualNode>> for IterableNodes {
-    fn from(other: Vec<VirtualNode>) -> Self {
-        IterableNodes(other)
-    }
-}
-
-impl<V: View> From<Vec<V>> for IterableNodes {
-    fn from(other: Vec<V>) -> Self {
-        IterableNodes(other.into_iter().map(|it| it.render()).collect())
-    }
-}
-
-impl<V: View> From<&Vec<V>> for IterableNodes {
-    fn from(other: &Vec<V>) -> Self {
-        IterableNodes(other.iter().map(|it| it.render()).collect())
-    }
-}
-
-impl<V: View> From<&[V]> for IterableNodes {
-    fn from(other: &[V]) -> Self {
-        IterableNodes(other.iter().map(|it| it.render()).collect())
-    }
-}
-
-impl From<VText> for VirtualNode {
-    fn from(other: VText) -> Self {
-        VirtualNode::Text(other)
-    }
-}
-
-impl From<VElement> for VirtualNode {
-    fn from(other: VElement) -> Self {
-        VirtualNode::Element(other)
-    }
-}
-
-impl From<&str> for VirtualNode {
-    fn from(other: &str) -> Self {
-        VirtualNode::text(other)
-    }
-}
-
-impl From<String> for VirtualNode {
-    fn from(other: String) -> Self {
-        VirtualNode::text(other.as_str())
-    }
-}
-
-impl From<&str> for VText {
-    fn from(text: &str) -> Self {
-        VText {
-            text: text.to_string(),
-        }
-    }
-}
-
-impl From<String> for VText {
-    fn from(text: String) -> Self {
-        VText { text }
-    }
-}
-
-impl IntoIterator for VirtualNode {
-    type Item = VirtualNode;
-    // TODO: Is this possible with an array [VirtualNode] instead of a vec?
-    type IntoIter = ::std::vec::IntoIter<VirtualNode>;
-
-    fn into_iter(self) -> Self::IntoIter {
-        vec![self].into_iter()
-    }
-}
-
-impl Into<::std::vec::IntoIter<VirtualNode>> for VirtualNode {
-    fn into(self) -> ::std::vec::IntoIter<VirtualNode> {
-        self.into_iter()
-    }
-}
-
-impl fmt::Debug for VirtualNode {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            VirtualNode::Element(e) => write!(f, "Node::{:?}", e),
-            VirtualNode::Text(t) => write!(f, "Node::{:?}", t),
-            // VirtualNode::Component(c) => write!(f, "Node::{:?}", c),
-        }
-    }
-}
-
-impl fmt::Debug for VElement {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(
-            f,
-            "Element(<{}>, attrs: {:?}, children: {:?})",
-            self.tag, self.attrs, self.children,
-        )
-    }
-}
-
-impl fmt::Debug for VText {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "Text({})", self.text)
-    }
-}
-
-impl fmt::Display for VElement {
-    // Turn a VElement and all of it's children (recursively) into an HTML string
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "<{}", self.tag).unwrap();
-
-        for (attr, value) in self.attrs.iter() {
-            write!(f, r#" {}="{}""#, attr, value)?;
-        }
-
-        write!(f, ">")?;
-
-        for child in self.children.iter() {
-            write!(f, "{}", child.to_string())?;
-        }
-
-        if !html_validation::is_self_closing(&self.tag) {
-            write!(f, "</{}>", self.tag)?;
-        }
-
-        Ok(())
-    }
-}
-
-// Turn a VText into an HTML string
-impl fmt::Display for VText {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "{}", self.text)
-    }
-}
-
-// Turn a VirtualNode into an HTML string (delegate impl to variants)
-impl fmt::Display for VirtualNode {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            VirtualNode::Element(element) => write!(f, "{}", element),
-            VirtualNode::Text(text) => write!(f, "{}", text),
-        }
-    }
-}
-
-/// Box<dyn AsRef<JsValue>>> is our js_sys::Closure. Stored this way to allow us to store
-/// any Closure regardless of the arguments.
-pub type DynClosure = Rc<dyn AsRef<JsValue>>;
-
-/// We need a custom implementation of fmt::Debug since JsValue doesn't
-/// implement debug.
-pub struct Events(pub HashMap<String, DynClosure>);
-
-impl PartialEq for Events {
-    // TODO: What should happen here..? And why?
-    fn eq(&self, _rhs: &Self) -> bool {
-        true
-    }
-}
-
-impl fmt::Debug for Events {
-    // Print out all of the event names for this VirtualNode
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let events: String = self.0.keys().map(|key| " ".to_string() + key).collect();
-        write!(f, "{}", events)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn self_closing_tag_to_string() {
-        let node = VirtualNode::element("br");
-
-        // No </br> since self closing tag
-        assert_eq!(&node.to_string(), "<br>");
-    }
-
-    #[test]
-    fn to_string() {
-        let mut node = VirtualNode::Element(VElement::new("div"));
-        node.as_velement_mut()
-            .unwrap()
-            .attrs
-            .insert("id".into(), "some-id".into());
-
-        let mut child = VirtualNode::Element(VElement::new("span"));
-
-        let mut text = VirtualNode::Text(VText::new("Hello world"));
-
-        child.as_velement_mut().unwrap().children.push(text);
-
-        node.as_velement_mut().unwrap().children.push(child);
-
-        let expected = r#"<div id="some-id"><span>Hello world</span></div>"#;
-
-        assert_eq!(node.to_string(), expected);
-    }
-}

+ 24 - 1
packages/web/Cargo.toml

@@ -6,4 +6,27 @@ edition = "2018"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
-[dependencies]
+[dependencies]
+dioxus-core = { path = "../core" }
+js-sys = "0.3"
+wasm-bindgen = "0.2.69"
+html-validation = { path = "../html-validation", version = "0.1.1" }
+lazy_static = "1.4.0"
+
+[dependencies.web-sys]
+version = "0.3"
+features = [
+    "Comment",
+    "Document",
+    "Element",
+    "HtmlElement",
+    "EventTarget",
+    "HtmlCollection",
+    "Node",
+    "NodeList",
+    "Text",
+    "Window",
+    "Event",
+    "MouseEvent",
+    "InputEvent",
+]

+ 210 - 0
packages/web/src/lib.rs

@@ -1 +1,211 @@
+use std::{collections::HashMap, fmt, rc::Rc};
+use web_sys::{self, Element, EventTarget, Node, Text};
 
+use dioxus_core::prelude::{VElement, VNode, VText, VirtualNode};
+use std::ops::Deref;
+use std::sync::Mutex;
+use wasm_bindgen::JsCast;
+use wasm_bindgen::JsValue;
+
+pub struct DomRenderer {}
+
+// Used to uniquely identify elements that contain closures so that the DomUpdater can
+// look them up by their unique id.
+// When the DomUpdater sees that the element no longer exists it will drop all of it's
+// Rc'd Closures for those events.
+use lazy_static::lazy_static;
+lazy_static! {
+    static ref ELEM_UNIQUE_ID: Mutex<u32> = Mutex::new(0);
+}
+
+fn create_unique_identifier() -> u32 {
+    let mut elem_unique_id = ELEM_UNIQUE_ID.lock().unwrap();
+    *elem_unique_id += 1;
+    *elem_unique_id
+}
+
+/// A node along with all of the closures that were created for that
+/// node's events and all of it's child node's events.
+pub struct CreatedNode<T> {
+    /// A `Node` or `Element` that was created from a `VirtualNode`
+    pub node: T,
+    /// A map of a node's unique identifier along with all of the Closures for that node.
+    ///
+    /// The DomUpdater uses this to look up nodes and see if they're still in the page. If not
+    /// the reference that we maintain to their closure will be dropped, thus freeing the Closure's
+    /// memory.
+    pub closures: HashMap<u32, Vec<DynClosure>>,
+}
+
+impl<T> CreatedNode<T> {
+    pub fn without_closures<N: Into<T>>(node: N) -> Self {
+        CreatedNode {
+            node: node.into(),
+            closures: HashMap::with_capacity(0),
+        }
+    }
+}
+
+impl<T> Deref for CreatedNode<T> {
+    type Target = T;
+    fn deref(&self) -> &Self::Target {
+        &self.node
+    }
+}
+
+impl From<CreatedNode<Element>> for CreatedNode<Node> {
+    fn from(other: CreatedNode<Element>) -> CreatedNode<Node> {
+        CreatedNode {
+            node: other.node.into(),
+            closures: other.closures,
+        }
+    }
+}
+
+//----------------------------------
+// Create nodes for the VNode types
+// ---------------------------------
+
+/// Return a `Text` element from a `VirtualNode`, typically right before adding it
+/// into the DOM.
+pub fn create_text_node(text_node: &VText) -> Text {
+    let document = web_sys::window().unwrap().document().unwrap();
+    document.create_text_node(&text_node.text)
+}
+
+/// Build a DOM element by recursively creating DOM nodes for this element and it's
+/// children, it's children's children, etc.
+pub fn create_element_node(velement: &VElement) -> CreatedNode<Element> {
+    let document = web_sys::window().unwrap().document().unwrap();
+
+    let element = if html_validation::is_svg_namespace(&velement.tag) {
+        document
+            .create_element_ns(Some("http://www.w3.org/2000/svg"), &velement.tag)
+            .unwrap()
+    } else {
+        document.create_element(&velement.tag).unwrap()
+    };
+
+    let mut closures = HashMap::new();
+
+    velement.attrs.iter().for_each(|(name, value)| {
+        if name == "unsafe_inner_html" {
+            element.set_inner_html(value);
+
+            return;
+        }
+
+        element
+            .set_attribute(name, value)
+            .expect("Set element attribute in create element");
+    });
+
+    todo!("Support events properly in web ");
+    // if velement.events.0.len() > 0 {
+    //     let unique_id = create_unique_identifier();
+
+    //     element
+    //         .set_attribute("data-vdom-id".into(), &unique_id.to_string())
+    //         .expect("Could not set attribute on element");
+
+    //     closures.insert(unique_id, vec![]);
+
+    //     velement.events.0.iter().for_each(|(onevent, callback)| {
+    //         // onclick -> click
+    //         let event = &onevent[2..];
+
+    //         let current_elem: &EventTarget = element.dyn_ref().unwrap();
+
+    //         current_elem
+    //             .add_event_listener_with_callback(event, callback.as_ref().as_ref().unchecked_ref())
+    //             .unwrap();
+
+    //         closures
+    //             .get_mut(&unique_id)
+    //             .unwrap()
+    //             .push(Rc::clone(callback));
+    //     });
+    // }
+
+    let mut previous_node_was_text = false;
+
+    velement.children.iter().for_each(|child| {
+        match child {
+            VNode::Text(text_node) => {
+                let current_node = element.as_ref() as &web_sys::Node;
+
+                // We ensure that the text siblings are patched by preventing the browser from merging
+                // neighboring text nodes. Originally inspired by some of React's work from 2016.
+                //  -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
+                //  -> https://github.com/facebook/react/pull/5753
+                //
+                // `ptns` = Percy text node separator
+                if previous_node_was_text {
+                    let separator = document.create_comment("ptns");
+                    current_node
+                        .append_child(separator.as_ref() as &web_sys::Node)
+                        .unwrap();
+                }
+
+                current_node
+                    .append_child(&create_text_node(&text_node))
+                    // .append_child(&text_node.create_text_node())
+                    .unwrap();
+
+                previous_node_was_text = true;
+            }
+            VNode::Element(element_node) => {
+                previous_node_was_text = false;
+
+                let child = create_element_node(&element_node);
+                // let child = element_node.create_element_node();
+                let child_elem: Element = child.node;
+
+                closures.extend(child.closures);
+
+                element.append_child(&child_elem).unwrap();
+            }
+
+            VNode::Component(component) => {
+                //
+                todo!("Support components in the web properly");
+            }
+        }
+    });
+
+    todo!("Support events properly in web ");
+    // if let Some(on_create_elem) = velement.events.0.get("on_create_elem") {
+    //     let on_create_elem: &js_sys::Function = on_create_elem.as_ref().as_ref().unchecked_ref();
+    //     on_create_elem
+    //         .call1(&wasm_bindgen::JsValue::NULL, &element)
+    //         .unwrap();
+    // }
+
+    CreatedNode {
+        node: element,
+        closures,
+    }
+}
+
+/// Box<dyn AsRef<JsValue>>> is our js_sys::Closure. Stored this way to allow us to store
+/// any Closure regardless of the arguments.
+pub type DynClosure = Rc<dyn AsRef<JsValue>>;
+
+/// We need a custom implementation of fmt::Debug since JsValue doesn't
+/// implement debug.
+pub struct Events(pub HashMap<String, DynClosure>);
+
+impl PartialEq for Events {
+    // TODO: What should happen here..? And why?
+    fn eq(&self, _rhs: &Self) -> bool {
+        true
+    }
+}
+
+impl fmt::Debug for Events {
+    // Print out all of the event names for this VirtualNode
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let events: String = self.0.keys().map(|key| " ".to_string() + key).collect();
+        write!(f, "{}", events)
+    }
+}

+ 0 - 0
packages/virtual-node/src/virtual_node_test_utils.rs → packages/web/src/virtual_node_test_utils.rs