Ver código fonte

WIP hot reload

Evan Almloff 3 anos atrás
pai
commit
c7c84da8ab

+ 5 - 1
Cargo.toml

@@ -28,6 +28,7 @@ dioxus-tui = { path = "./packages/tui", version = "^0.2.0", optional = true }
 
 dioxus-liveview = { path = "./packages/liveview", optional = true }
 dioxus-rsx = { path = "./packages/rsx", optional = true }
+dioxus-rsx-interperter = { path = "./packages/rsx_interperter", optional = true }
 dioxus-autofmt = { path = "./packages/autofmt", optional = true }
 
 [features]
@@ -44,6 +45,7 @@ router = ["dioxus-router"]
 tui = ["dioxus-tui"]
 liveview = ["dioxus-liveview"]
 autofmt = ["dioxus-autofmt"]
+hot_reload = ["dioxus-core-macro/hot_reload"]
 
 [workspace]
 members = [
@@ -61,6 +63,7 @@ members = [
     "packages/liveview",
     "packages/rsx",
     "packages/autofmt",
+    "packages/rsx_interperter",
 ]
 
 [dev-dependencies]
@@ -75,7 +78,8 @@ serde_json = "1.0.79"
 rand = { version = "0.8.4", features = ["small_rng"] }
 tokio = { version = "1.16.1", features = ["full"] }
 reqwest = { version = "0.11.9", features = ["json"] }
-dioxus = { path = ".", features = ["desktop", "ssr", "router", "fermi", "tui"] }
+# dioxus = { path = ".", features = ["desktop", "ssr", "router", "fermi", "tui"] }
+dioxus = { path = ".", features = ["desktop"] }
 fern = { version = "0.6.0", features = ["colored"] }
 criterion = "0.3.5"
 thiserror = "1.0.30"

+ 4 - 0
packages/core-macro/Cargo.toml

@@ -20,8 +20,12 @@ proc-macro2 = { version = "1.0.6" }
 quote = "1.0"
 syn = { version = "1.0.11", features = ["full", "extra-traits"] }
 dioxus-rsx = {  path = "../rsx" }
+dioxus-rsx-interperter = { path = "../rsx_interperter", optional = true }
 
 # testing
 [dev-dependencies]
 rustversion = "1.0"
 trybuild = "1.0"
+
+[features]
+hot_reload = ["dioxus-rsx-interperter"]

+ 14 - 3
packages/core-macro/src/lib.rs

@@ -1,8 +1,9 @@
 use proc_macro::TokenStream;
 use quote::ToTokens;
+use rsx::{BodyNode, Component, ContentField, ElementAttr, IfmtInput};
+use std::collections::HashMap;
 use syn::parse_macro_input;
 
-mod ifmt;
 mod inlineprops;
 mod props;
 
@@ -11,7 +12,7 @@ use dioxus_rsx as rsx;
 
 #[proc_macro]
 pub fn format_args_f(input: TokenStream) -> TokenStream {
-    use ifmt::*;
+    use rsx::*;
     let item = parse_macro_input!(input as IfmtInput);
     format_args_f_impl(item)
         .unwrap_or_else(|err| err.to_compile_error())
@@ -183,7 +184,17 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
 pub fn rsx(s: TokenStream) -> TokenStream {
     match syn::parse::<rsx::CallBody>(s) {
         Err(err) => err.to_compile_error().into(),
-        Ok(stream) => stream.to_token_stream().into(),
+        Ok(body) => {
+            #[cfg(feature = "hot_reload")]
+            {
+                use dioxus_rsx_interperter::captuered_context::CapturedContextBuilder;
+
+                let captured = CapturedContextBuilder::from_call_body(body);
+                captured.to_token_stream().into()
+            }
+            #[cfg(not(feature = "hot_reload"))]
+            body.to_token_stream().into()
+        }
     }
 }
 

+ 0 - 233
packages/core-macro/src/rsx/component.rs

@@ -1,233 +0,0 @@
-//! Parse components into the VComponent VNode
-//! ==========================================
-//!
-//! This parsing path emerges from [`AmbiguousElement`] which supports validation of the vcomponent format.
-//! We can be reasonably sure that whatever enters this parsing path is in the right format.
-//! This feature must support
-//! - [x] Namespaced components
-//! - [x] Fields
-//! - [x] Componentbuilder synax
-//! - [x] Optional commas
-//! - [ ] Children
-//! - [ ] Keys
-//! - [ ] Properties spreading with with `..` syntax
-
-use super::*;
-
-use proc_macro2::TokenStream as TokenStream2;
-use quote::{quote, ToTokens, TokenStreamExt};
-use syn::{
-    ext::IdentExt,
-    parse::{Parse, ParseBuffer, ParseStream},
-    token, Expr, Ident, LitStr, Result, Token,
-};
-
-pub struct Component {
-    pub name: syn::Path,
-    pub body: Vec<ComponentField>,
-    pub children: Vec<BodyNode>,
-    pub manual_props: Option<Expr>,
-}
-
-impl Parse for Component {
-    fn parse(stream: ParseStream) -> Result<Self> {
-        let name = syn::Path::parse_mod_style(stream)?;
-
-        let content: ParseBuffer;
-
-        // if we see a `{` then we have a block
-        // else parse as a function-like call
-        if stream.peek(token::Brace) {
-            syn::braced!(content in stream);
-        } else {
-            syn::parenthesized!(content in stream);
-        }
-
-        let mut body = Vec::new();
-        let mut children = Vec::new();
-        let mut manual_props = None;
-
-        while !content.is_empty() {
-            // if we splat into a component then we're merging properties
-            if content.peek(Token![..]) {
-                content.parse::<Token![..]>()?;
-                manual_props = Some(content.parse::<Expr>()?);
-            } else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
-                body.push(content.parse::<ComponentField>()?);
-            } else {
-                children.push(content.parse::<BodyNode>()?);
-            }
-
-            if content.peek(Token![,]) {
-                let _ = content.parse::<Token![,]>();
-            }
-        }
-
-        Ok(Self {
-            name,
-            body,
-            children,
-            manual_props,
-        })
-    }
-}
-
-impl ToTokens for Component {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        let name = &self.name;
-
-        let mut has_key = None;
-
-        let builder = match &self.manual_props {
-            Some(manual_props) => {
-                let mut toks = quote! {
-                    let mut __manual_props = #manual_props;
-                };
-                for field in &self.body {
-                    if field.name == "key" {
-                        has_key = Some(field);
-                    } else {
-                        let name = &field.name;
-                        let val = &field.content;
-                        toks.append_all(quote! {
-                            __manual_props.#name = #val;
-                        });
-                    }
-                }
-                toks.append_all(quote! {
-                    __manual_props
-                });
-                quote! {{
-                    #toks
-                }}
-            }
-            None => {
-                let mut toks = quote! { fc_to_builder(#name) };
-                for field in &self.body {
-                    match field.name.to_string().as_str() {
-                        "key" => {
-                            //
-                            has_key = Some(field);
-                        }
-                        _ => toks.append_all(quote! {#field}),
-                    }
-                }
-
-                if !self.children.is_empty() {
-                    let childs = &self.children;
-                    toks.append_all(quote! {
-                        .children(__cx.create_children([ #( #childs ),* ]))
-                    });
-                }
-
-                toks.append_all(quote! {
-                    .build()
-                });
-                toks
-            }
-        };
-
-        let key_token = match has_key {
-            Some(field) => {
-                let inners = &field.content;
-                quote! { Some(format_args_f!(#inners)) }
-            }
-            None => quote! { None },
-        };
-
-        let fn_name = self.name.segments.last().unwrap().ident.to_string();
-
-        tokens.append_all(quote! {
-            __cx.component(
-                #name,
-                #builder,
-                #key_token,
-                #fn_name
-            )
-        })
-    }
-}
-
-// the struct's fields info
-pub struct ComponentField {
-    name: Ident,
-    content: ContentField,
-}
-
-enum ContentField {
-    ManExpr(Expr),
-    Formatted(LitStr),
-    OnHandlerRaw(Expr),
-}
-
-impl ToTokens for ContentField {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        match self {
-            ContentField::ManExpr(e) => e.to_tokens(tokens),
-            ContentField::Formatted(s) => tokens.append_all(quote! {
-                __cx.raw_text(format_args_f!(#s)).0
-            }),
-            ContentField::OnHandlerRaw(e) => tokens.append_all(quote! {
-                __cx.event_handler(#e)
-            }),
-        }
-    }
-}
-
-impl Parse for ComponentField {
-    fn parse(input: ParseStream) -> Result<Self> {
-        let name = Ident::parse_any(input)?;
-        input.parse::<Token![:]>()?;
-
-        if name.to_string().starts_with("on") {
-            let content = ContentField::OnHandlerRaw(input.parse()?);
-            return Ok(Self { name, content });
-        }
-
-        if name == "key" {
-            let content = ContentField::ManExpr(input.parse()?);
-            return Ok(Self { name, content });
-        }
-
-        if input.peek(LitStr) && input.peek2(Token![,]) {
-            let t: LitStr = input.fork().parse()?;
-
-            if is_literal_foramtted(&t) {
-                let content = ContentField::Formatted(input.parse()?);
-                return Ok(Self { name, content });
-            }
-        }
-
-        if input.peek(LitStr) && input.peek2(LitStr) {
-            missing_trailing_comma!(input.span());
-        }
-
-        let content = ContentField::ManExpr(input.parse()?);
-        Ok(Self { name, content })
-    }
-}
-
-impl ToTokens for ComponentField {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        let ComponentField { name, content, .. } = self;
-        tokens.append_all(quote! {
-            .#name(#content)
-        })
-    }
-}
-
-fn is_literal_foramtted(lit: &LitStr) -> bool {
-    let s = lit.value();
-    let mut chars = s.chars();
-
-    while let Some(next) = chars.next() {
-        if next == '{' {
-            let nen = chars.next();
-            if nen != Some('{') {
-                return true;
-            }
-        }
-    }
-
-    false
-}

+ 0 - 253
packages/core-macro/src/rsx/element.rs

@@ -1,253 +0,0 @@
-use super::*;
-
-use proc_macro2::TokenStream as TokenStream2;
-use quote::{quote, ToTokens, TokenStreamExt};
-use syn::{
-    parse::{Parse, ParseBuffer, ParseStream},
-    Expr, Ident, LitStr, Result, Token,
-};
-
-// =======================================
-// Parse the VNode::Element type
-// =======================================
-pub struct Element {
-    pub name: Ident,
-    pub key: Option<LitStr>,
-    pub attributes: Vec<ElementAttrNamed>,
-    pub children: Vec<BodyNode>,
-    pub _is_static: bool,
-}
-
-impl Parse for Element {
-    fn parse(stream: ParseStream) -> Result<Self> {
-        let el_name = Ident::parse(stream)?;
-
-        // parse the guts
-        let content: ParseBuffer;
-        syn::braced!(content in stream);
-
-        let mut attributes: Vec<ElementAttrNamed> = vec![];
-        let mut children: Vec<BodyNode> = vec![];
-        let mut key = None;
-        let mut _el_ref = None;
-
-        // parse fields with commas
-        // break when we don't get this pattern anymore
-        // start parsing bodynodes
-        // "def": 456,
-        // abc: 123,
-        loop {
-            // Parse the raw literal fields
-            if content.peek(LitStr) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
-                let name = content.parse::<LitStr>()?;
-                let ident = name.clone();
-
-                content.parse::<Token![:]>()?;
-
-                if content.peek(LitStr) && content.peek2(Token![,]) {
-                    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 },
-                    });
-                }
-
-                if content.is_empty() {
-                    break;
-                }
-
-                if content.parse::<Token![,]>().is_err() {
-                    missing_trailing_comma!(ident);
-                }
-                continue;
-            }
-
-            if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
-                let name = content.parse::<Ident>()?;
-                let ident = name.clone();
-
-                let name_str = name.to_string();
-                content.parse::<Token![:]>()?;
-
-                if name_str.starts_with("on") {
-                    attributes.push(ElementAttrNamed {
-                        el_name: el_name.clone(),
-                        attr: ElementAttr::EventTokens {
-                            name,
-                            tokens: content.parse()?,
-                        },
-                    });
-                } else {
-                    match name_str.as_str() {
-                        "key" => {
-                            key = Some(content.parse()?);
-                        }
-                        "classes" => todo!("custom class list not supported yet"),
-                        // "namespace" => todo!("custom namespace not supported yet"),
-                        "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()?,
-                                    },
-                                });
-                            }
-                        }
-                    }
-                }
-
-                if content.is_empty() {
-                    break;
-                }
-
-                // todo: add a message saying you need to include commas between fields
-                if content.parse::<Token![,]>().is_err() {
-                    missing_trailing_comma!(ident);
-                }
-                continue;
-            }
-
-            break;
-        }
-
-        while !content.is_empty() {
-            if (content.peek(LitStr) && content.peek2(Token![:])) && !content.peek3(Token![:]) {
-                attr_after_element!(content.span());
-            }
-
-            if (content.peek(Ident) && content.peek2(Token![:])) && !content.peek3(Token![:]) {
-                attr_after_element!(content.span());
-            }
-
-            children.push(content.parse::<BodyNode>()?);
-            // consume comma if it exists
-            // we don't actually care if there *are* commas after elements/text
-            if content.peek(Token![,]) {
-                let _ = content.parse::<Token![,]>();
-            }
-        }
-
-        Ok(Self {
-            key,
-            name: el_name,
-            attributes,
-            children,
-            _is_static: false,
-        })
-    }
-}
-
-impl ToTokens for Element {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        let name = &self.name;
-        let children = &self.children;
-
-        let key = match &self.key {
-            Some(ty) => quote! { Some(format_args_f!(#ty)) },
-            None => quote! { None },
-        };
-
-        let listeners = self
-            .attributes
-            .iter()
-            .filter(|f| matches!(f.attr, ElementAttr::EventTokens { .. }));
-
-        let attr = self
-            .attributes
-            .iter()
-            .filter(|f| !matches!(f.attr, ElementAttr::EventTokens { .. }));
-
-        tokens.append_all(quote! {
-            __cx.element(
-                dioxus_elements::#name,
-                __cx.bump().alloc([ #(#listeners),* ]),
-                __cx.bump().alloc([ #(#attr),* ]),
-                __cx.bump().alloc([ #(#children),* ]),
-                #key,
-            )
-        });
-    }
-}
-
-pub enum ElementAttr {
-    /// attribute: "valuee {}"
-    AttrText { name: Ident, value: LitStr },
-
-    /// attribute: true,
-    AttrExpression { name: Ident, value: Expr },
-
-    /// "attribute": "value {}"
-    CustomAttrText { name: LitStr, value: LitStr },
-
-    /// "attribute": true,
-    CustomAttrExpression { name: LitStr, value: Expr },
-
-    // /// onclick: move |_| {}
-    // EventClosure { name: Ident, closure: ExprClosure },
-    /// onclick: {}
-    EventTokens { name: Ident, tokens: Expr },
-}
-
-pub struct ElementAttrNamed {
-    pub el_name: Ident,
-    pub attr: ElementAttr,
-}
-
-impl ToTokens for ElementAttrNamed {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        let ElementAttrNamed { el_name, attr } = self;
-
-        tokens.append_all(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)
-                }
-            }
-            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 )
-                }
-            }
-            // ElementAttr::EventClosure { name, closure } => {
-            //     quote! {
-            //         dioxus_elements::on::#name(__cx, #closure)
-            //     }
-            // }
-            ElementAttr::EventTokens { name, tokens } => {
-                quote! {
-                    dioxus_elements::on::#name(__cx, #tokens)
-                }
-            }
-        });
-    }
-}

+ 0 - 15
packages/core-macro/src/rsx/errors.rs

@@ -1,15 +0,0 @@
-macro_rules! missing_trailing_comma {
-    ($span:expr) => {
-        proc_macro_error::emit_error!($span, "missing trailing comma")
-    };
-}
-
-macro_rules! attr_after_element {
-    ($span:expr) => {
-        proc_macro_error::emit_error!(
-            $span,
-            "expected element";
-            help = "move the attribute above all the children and text elements"
-        )
-    };
-}

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

@@ -1,100 +0,0 @@
-//! Parse the root tokens in the rsx!{} macro
-//! =========================================
-//!
-//! This parsing path emerges directly from the macro call, with `RsxRender` being the primary entrance into parsing.
-//! This feature must support:
-//! - [x] Optionally rendering if the `in XYZ` pattern is present
-//! - [x] Fragments as top-level element (through ambiguous)
-//! - [x] Components as top-level element (through ambiguous)
-//! - [x] Tags as top-level elements (through ambiguous)
-//! - [x] Good errors if parsing fails
-//!
-//! Any errors in using rsx! will likely occur when people start using it, so the first errors must be really helpful.
-
-#[macro_use]
-mod errors;
-
-mod component;
-mod element;
-mod node;
-
-pub mod pretty;
-
-// Re-export the namespaces into each other
-pub use component::*;
-pub use element::*;
-pub use node::*;
-
-// imports
-use proc_macro2::TokenStream as TokenStream2;
-use quote::{quote, ToTokens, TokenStreamExt};
-use syn::{
-    parse::{Parse, ParseStream},
-    Ident, Result, Token,
-};
-
-pub struct CallBody {
-    pub custom_context: Option<Ident>,
-    pub roots: Vec<BodyNode>,
-}
-
-impl Parse for CallBody {
-    fn parse(input: ParseStream) -> Result<Self> {
-        let custom_context = if input.peek(Ident) && input.peek2(Token![,]) {
-            let name = input.parse::<Ident>()?;
-            input.parse::<Token![,]>()?;
-
-            Some(name)
-        } else {
-            None
-        };
-
-        let mut roots = Vec::new();
-
-        while !input.is_empty() {
-            let node = input.parse::<BodyNode>()?;
-
-            if input.peek(Token![,]) {
-                let _ = input.parse::<Token![,]>();
-            }
-
-            roots.push(node);
-        }
-
-        Ok(Self {
-            custom_context,
-            roots,
-        })
-    }
-}
-
-/// Serialize the same way, regardless of flavor
-impl ToTokens for CallBody {
-    fn to_tokens(&self, out_tokens: &mut TokenStream2) {
-        let inner = if self.roots.len() == 1 {
-            let inner = &self.roots[0];
-            quote! { #inner }
-        } else {
-            let childs = &self.roots;
-            quote! { __cx.fragment_root([ #(#childs),* ]) }
-        };
-
-        match &self.custom_context {
-            // The `in cx` pattern allows directly rendering
-            Some(ident) => out_tokens.append_all(quote! {
-                #ident.render(LazyNodes::new(move |__cx: NodeFactory| -> VNode {
-                    use dioxus_elements::{GlobalAttributes, SvgAttributes};
-                    #inner
-                }))
-            }),
-
-            // Otherwise we just build the LazyNode wrapper
-            None => out_tokens.append_all(quote! {
-                LazyNodes::new(move |__cx: NodeFactory| -> VNode {
-                    use dioxus_elements::{GlobalAttributes, SvgAttributes};
-                    #inner
-                })
-            }),
-        };
-    }
-}

+ 0 - 84
packages/core-macro/src/rsx/node.rs

@@ -1,84 +0,0 @@
-use super::*;
-
-use proc_macro2::TokenStream as TokenStream2;
-use quote::{quote, ToTokens, TokenStreamExt};
-use syn::{
-    parse::{Parse, ParseStream},
-    token, Expr, LitStr, Result, Token,
-};
-
-/*
-Parse
--> div {}
--> Component {}
--> component()
--> "text {with_args}"
--> (0..10).map(|f| rsx!("asd")),  // <--- notice the comma - must be a complete expr
-*/
-pub enum BodyNode {
-    Element(Element),
-    Component(Component),
-    Text(LitStr),
-    RawExpr(Expr),
-}
-
-impl Parse for BodyNode {
-    fn parse(stream: ParseStream) -> Result<Self> {
-        if stream.peek(LitStr) {
-            return Ok(BodyNode::Text(stream.parse()?));
-        }
-
-        // div {} -> el
-        // Div {} -> comp
-        if stream.peek(syn::Ident) && stream.peek2(token::Brace) {
-            if stream
-                .fork()
-                .parse::<Ident>()?
-                .to_string()
-                .chars()
-                .next()
-                .unwrap()
-                .is_ascii_uppercase()
-            {
-                return Ok(BodyNode::Component(stream.parse()?));
-            } else {
-                return Ok(BodyNode::Element(stream.parse::<Element>()?));
-            }
-        }
-
-        // component() -> comp
-        // ::component {} -> comp
-        // ::component () -> comp
-        if (stream.peek(syn::Ident) && stream.peek2(token::Paren))
-            || (stream.peek(Token![::]))
-            || (stream.peek(Token![:]) && stream.peek2(Token![:]))
-        {
-            return Ok(BodyNode::Component(stream.parse::<Component>()?));
-        }
-
-        // crate::component{} -> comp
-        // crate::component() -> comp
-        if let Ok(pat) = stream.fork().parse::<syn::Path>() {
-            if pat.segments.len() > 1 {
-                return Ok(BodyNode::Component(stream.parse::<Component>()?));
-            }
-        }
-
-        Ok(BodyNode::RawExpr(stream.parse::<Expr>()?))
-    }
-}
-
-impl ToTokens for BodyNode {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        match &self {
-            BodyNode::Element(el) => el.to_tokens(tokens),
-            BodyNode::Component(comp) => comp.to_tokens(tokens),
-            BodyNode::Text(txt) => tokens.append_all(quote! {
-                __cx.text(format_args_f!(#txt))
-            }),
-            BodyNode::RawExpr(exp) => tokens.append_all(quote! {
-                 __cx.fragment_from_iter(#exp)
-            }),
-        }
-    }
-}

+ 0 - 1
packages/core-macro/src/rsx/pretty.rs

@@ -1 +0,0 @@
-//! pretty printer for rsx!

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

@@ -85,6 +85,8 @@ pub mod prelude {
         fc_to_builder, Attributes, Component, DioxusElement, Element, EventHandler, Fragment,
         LazyNodes, NodeFactory, Properties, Scope, ScopeId, ScopeState, VNode, VirtualDom,
     };
+    #[cfg(feature = "hot_reload")]
+    pub use dioxus_rsx_interperter::captuered_context::{CapturedContext, IfmtArgs};
 }
 
 pub mod exports {

+ 3 - 3
packages/core-macro/src/ifmt.rs → packages/rsx/src/ifmt.rs

@@ -140,9 +140,9 @@ pub fn format_args_f_impl(input: IfmtInput) -> Result<TokenStream> {
 
 #[allow(dead_code)] // dumb compiler does not see the struct being used...
 pub struct IfmtInput {
-    format_literal: LitStr,
-    positional_args: Vec<Expr>,
-    named_args: Vec<(Ident, Expr)>,
+    pub format_literal: LitStr,
+    pub positional_args: Vec<Expr>,
+    pub named_args: Vec<(Ident, Expr)>,
 }
 
 impl Parse for IfmtInput {

+ 2 - 0
packages/rsx/src/lib.rs

@@ -16,11 +16,13 @@ mod errors;
 
 mod component;
 mod element;
+mod ifmt;
 mod node;
 
 // Re-export the namespaces into each other
 pub use component::*;
 pub use element::*;
+pub use ifmt::*;
 pub use node::*;
 
 // imports

+ 12 - 0
packages/rsx_interperter/Cargo.toml

@@ -0,0 +1,12 @@
+[package]
+name = "dioxus-rsx-interperter"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+syn = "1.0"
+quote = "1.0"
+dioxus-rsx = { path = "../rsx" }
+dioxus-ssr = { path = "../ssr" }
+dioxus-core = { path = "../core" }
+dioxus-html = { path = "../html" }

+ 869 - 0
packages/rsx_interperter/src/attributes.rs

@@ -0,0 +1,869 @@
+use crate::elements::*;
+
+// find the mapped attribute name
+pub fn attrbute_to_static_str(attr: &str) -> Option<(&'static str, Option<&'static str>)> {
+    NO_NAMESPACE_ATTRIBUTES
+        .iter()
+        .find(|&a| *a == attr)
+        .map(|a| (*a, None))
+        .or_else(|| {
+            STYLE_ATTRIBUTES
+                .iter()
+                .find(|(a, _)| *a == attr)
+                .map(|(_, b)| (*b, Some("style")))
+        })
+        .or_else(|| {
+            MAPPED_ATTRIBUTES
+                .iter()
+                .find(|(a, _)| *a == attr)
+                .map(|(_, b)| (*b, None))
+        })
+        .or_else(|| {
+            svg::MAPPED_ATTRIBUTES
+                .iter()
+                .find(|(a, _)| *a == attr)
+                .map(|(_, b)| (*b, None))
+        })
+        .or_else(|| {
+            ELEMENTS_WITH_MAPPED_ATTRIBUTES
+                .iter()
+                .find_map(|(_, attrs)| {
+                    attrs
+                        .iter()
+                        .find(|(a, _)| *a == attr)
+                        .map(|(_, b)| (*b, None))
+                })
+        })
+        .or_else(|| {
+            ELEMENTS_WITH_NAMESPACE
+                .iter()
+                .find_map(|(_, _, attrs)| attrs.iter().find(|a| **a == attr).map(|a| (*a, None)))
+        })
+        .or_else(|| {
+            ELEMENTS_WITHOUT_NAMESPACE
+                .iter()
+                .find_map(|(_, attrs)| attrs.iter().find(|a| **a == attr).map(|a| (*a, None)))
+        })
+}
+
+macro_rules! no_namespace_trait_methods {
+    (
+        $(
+            $(#[$attr:meta])*
+            $name:ident;
+        )*
+    ) => {
+        pub const NO_NAMESPACE_ATTRIBUTES: &'static [&'static str] = &[
+            $(
+                stringify!($name),
+            )*
+        ];
+    };
+}
+
+macro_rules! style_trait_methods {
+    (
+        $(
+            $(#[$attr:meta])*
+            $name:ident: $lit:literal,
+        )*
+    ) => {
+        pub const STYLE_ATTRIBUTES: &'static [(&'static str, &'static str)] = &[
+            $(
+                (stringify!($name), $lit),
+            )*
+        ];
+    };
+}
+
+macro_rules! mapped_trait_methods {
+    (
+        $(
+            $(#[$attr:meta])*
+            $name:ident: $lit:literal,
+        )*
+    ) => {
+        pub const MAPPED_ATTRIBUTES: &'static [(&'static str, &'static str)] = &[
+            $(
+                (stringify!($name), $lit),
+            )*
+            ("prevent_default", "dioxus-prevent-default"),
+        ];
+    };
+}
+
+no_namespace_trait_methods! {
+    accesskey;
+
+    /// The HTML class attribute is used to specify a class for an HTML element.
+    ///
+    /// ## Details
+    /// Multiple HTML elements can share the same class.
+    ///
+    /// The class global attribute is a space-separated list of the case-sensitive classes of the element.
+    /// Classes allow CSS and Javascript to select and access specific elements via the class selectors or
+    /// functions like the DOM method document.getElementsByClassName.
+    ///
+    /// ## Example
+    ///
+    /// ### HTML:
+    /// ```html
+    /// <p class="note editorial">Above point sounds a bit obvious. Remove/rewrite?</p>
+    /// ```
+    ///
+    /// ### CSS:
+    /// ```css
+    /// .note {
+    ///     font-style: italic;
+    ///     font-weight: bold;
+    /// }
+    ///
+    /// .editorial {
+    ///     background: rgb(255, 0, 0, .25);
+    ///     padding: 10px;
+    /// }
+    /// ```
+    class;
+    contenteditable;
+    data;
+    dir;
+    draggable;
+    hidden;
+    id;
+    lang;
+    spellcheck;
+    style;
+    tabindex;
+    title;
+    translate;
+
+    role;
+
+    /// dangerous_inner_html is Dioxus's replacement for using innerHTML in the browser DOM. In general, setting
+    /// HTML from code is risky because it’s easy to inadvertently expose your users to a cross-site scripting (XSS)
+    /// attack. So, you can set HTML directly from Dioxus, but you have to type out dangerous_inner_html to remind
+    /// yourself that it’s dangerous
+    dangerous_inner_html;
+}
+
+// This macro creates an explicit method call for each of the style attributes.
+//
+// The left token specifies the name of the attribute in the rsx! macro, and the right string literal specifies the
+// actual name of the attribute generated.
+//
+// This roughly follows the html spec
+style_trait_methods! {
+    align_content: "align-content",
+    align_items: "align-items",
+    align_self: "align-self",
+    alignment_adjust: "alignment-adjust",
+    alignment_baseline: "alignment-baseline",
+    all: "all",
+    alt: "alt",
+    animation: "animation",
+    animation_delay: "animation-delay",
+    animation_direction: "animation-direction",
+    animation_duration: "animation-duration",
+    animation_fill_mode: "animation-fill-mode",
+    animation_iteration_count: "animation-iteration-count",
+    animation_name: "animation-name",
+    animation_play_state: "animation-play-state",
+    animation_timing_function: "animation-timing-function",
+    azimuth: "azimuth",
+    backface_visibility: "backface-visibility",
+    background: "background",
+    background_attachment: "background-attachment",
+    background_clip: "background-clip",
+    background_color: "background-color",
+    background_image: "background-image",
+    background_origin: "background-origin",
+    background_position: "background-position",
+    background_repeat: "background-repeat",
+    background_size: "background-size",
+    background_blend_mode: "background-blend-mode",
+    baseline_shift: "baseline-shift",
+    bleed: "bleed",
+    bookmark_label: "bookmark-label",
+    bookmark_level: "bookmark-level",
+    bookmark_state: "bookmark-state",
+    border: "border",
+    border_color: "border-color",
+    border_style: "border-style",
+    border_width: "border-width",
+    border_bottom: "border-bottom",
+    border_bottom_color: "border-bottom-color",
+    border_bottom_style: "border-bottom-style",
+    border_bottom_width: "border-bottom-width",
+    border_left: "border-left",
+    border_left_color: "border-left-color",
+    border_left_style: "border-left-style",
+    border_left_width: "border-left-width",
+    border_right: "border-right",
+    border_right_color: "border-right-color",
+    border_right_style: "border-right-style",
+    border_right_width: "border-right-width",
+    border_top: "border-top",
+    border_top_color: "border-top-color",
+    border_top_style: "border-top-style",
+    border_top_width: "border-top-width",
+    border_collapse: "border-collapse",
+    border_image: "border-image",
+    border_image_outset: "border-image-outset",
+    border_image_repeat: "border-image-repeat",
+    border_image_slice: "border-image-slice",
+    border_image_source: "border-image-source",
+    border_image_width: "border-image-width",
+    border_radius: "border-radius",
+    border_bottom_left_radius: "border-bottom-left-radius",
+    border_bottom_right_radius: "border-bottom-right-radius",
+    border_top_left_radius: "border-top-left-radius",
+    border_top_right_radius: "border-top-right-radius",
+    border_spacing: "border-spacing",
+    bottom: "bottom",
+    box_decoration_break: "box-decoration-break",
+    box_shadow: "box-shadow",
+    box_sizing: "box-sizing",
+    box_snap: "box-snap",
+    break_after: "break-after",
+    break_before: "break-before",
+    break_inside: "break-inside",
+    buffered_rendering: "buffered-rendering",
+    caption_side: "caption-side",
+    clear: "clear",
+    clear_side: "clear-side",
+    clip: "clip",
+    clip_path: "clip-path",
+    clip_rule: "clip-rule",
+    color: "color",
+    color_adjust: "color-adjust",
+    color_correction: "color-correction",
+    color_interpolation: "color-interpolation",
+    color_interpolation_filters: "color-interpolation-filters",
+    color_profile: "color-profile",
+    color_rendering: "color-rendering",
+    column_fill: "column-fill",
+    column_gap: "column-gap",
+    column_rule: "column-rule",
+    column_rule_color: "column-rule-color",
+    column_rule_style: "column-rule-style",
+    column_rule_width: "column-rule-width",
+    column_span: "column-span",
+    columns: "columns",
+    column_count: "column-count",
+    column_width: "column-width",
+    contain: "contain",
+    content: "content",
+    counter_increment: "counter-increment",
+    counter_reset: "counter-reset",
+    counter_set: "counter-set",
+    cue: "cue",
+    cue_after: "cue-after",
+    cue_before: "cue-before",
+    cursor: "cursor",
+    direction: "direction",
+    display: "display",
+    display_inside: "display-inside",
+    display_outside: "display-outside",
+    display_extras: "display-extras",
+    display_box: "display-box",
+    dominant_baseline: "dominant-baseline",
+    elevation: "elevation",
+    empty_cells: "empty-cells",
+    enable_background: "enable-background",
+    fill: "fill",
+    fill_opacity: "fill-opacity",
+    fill_rule: "fill-rule",
+    filter: "filter",
+    float: "float",
+    float_defer_column: "float-defer-column",
+    float_defer_page: "float-defer-page",
+    float_offset: "float-offset",
+    float_wrap: "float-wrap",
+    flow_into: "flow-into",
+    flow_from: "flow-from",
+    flex: "flex",
+    flex_basis: "flex-basis",
+    flex_grow: "flex-grow",
+    flex_shrink: "flex-shrink",
+    flex_flow: "flex-flow",
+    flex_direction: "flex-direction",
+    flex_wrap: "flex-wrap",
+    flood_color: "flood-color",
+    flood_opacity: "flood-opacity",
+    font: "font",
+    font_family: "font-family",
+    font_size: "font-size",
+    font_stretch: "font-stretch",
+    font_style: "font-style",
+    font_weight: "font-weight",
+    font_feature_settings: "font-feature-settings",
+    font_kerning: "font-kerning",
+    font_language_override: "font-language-override",
+    font_size_adjust: "font-size-adjust",
+    font_synthesis: "font-synthesis",
+    font_variant: "font-variant",
+    font_variant_alternates: "font-variant-alternates",
+    font_variant_caps: "font-variant-caps",
+    font_variant_east_asian: "font-variant-east-asian",
+    font_variant_ligatures: "font-variant-ligatures",
+    font_variant_numeric: "font-variant-numeric",
+    font_variant_position: "font-variant-position",
+    footnote_policy: "footnote-policy",
+    glyph_orientation_horizontal: "glyph-orientation-horizontal",
+    glyph_orientation_vertical: "glyph-orientation-vertical",
+    grid: "grid",
+    grid_auto_flow: "grid-auto-flow",
+    grid_auto_columns: "grid-auto-columns",
+    grid_auto_rows: "grid-auto-rows",
+    grid_template: "grid-template",
+    grid_template_areas: "grid-template-areas",
+    grid_template_columns: "grid-template-columns",
+    grid_template_rows: "grid-template-rows",
+    grid_area: "grid-area",
+    grid_column: "grid-column",
+    grid_column_start: "grid-column-start",
+    grid_column_end: "grid-column-end",
+    grid_row: "grid-row",
+    grid_row_start: "grid-row-start",
+    grid_row_end: "grid-row-end",
+    hanging_punctuation: "hanging-punctuation",
+    height: "height",
+    hyphenate_character: "hyphenate-character",
+    hyphenate_limit_chars: "hyphenate-limit-chars",
+    hyphenate_limit_last: "hyphenate-limit-last",
+    hyphenate_limit_lines: "hyphenate-limit-lines",
+    hyphenate_limit_zone: "hyphenate-limit-zone",
+    hyphens: "hyphens",
+    icon: "icon",
+    image_orientation: "image-orientation",
+    image_resolution: "image-resolution",
+    image_rendering: "image-rendering",
+    ime: "ime",
+    ime_align: "ime-align",
+    ime_mode: "ime-mode",
+    ime_offset: "ime-offset",
+    ime_width: "ime-width",
+    initial_letters: "initial-letters",
+    inline_box_align: "inline-box-align",
+    isolation: "isolation",
+    justify_content: "justify-content",
+    justify_items: "justify-items",
+    justify_self: "justify-self",
+    kerning: "kerning",
+    left: "left",
+    letter_spacing: "letter-spacing",
+    lighting_color: "lighting-color",
+    line_box_contain: "line-box-contain",
+    line_break: "line-break",
+    line_grid: "line-grid",
+    line_height: "line-height",
+    line_slack: "line-slack",
+    line_snap: "line-snap",
+    list_style: "list-style",
+    list_style_image: "list-style-image",
+    list_style_position: "list-style-position",
+    list_style_type: "list-style-type",
+    margin: "margin",
+    margin_bottom: "margin-bottom",
+    margin_left: "margin-left",
+    margin_right: "margin-right",
+    margin_top: "margin-top",
+    marker: "marker",
+    marker_end: "marker-end",
+    marker_mid: "marker-mid",
+    marker_pattern: "marker-pattern",
+    marker_segment: "marker-segment",
+    marker_start: "marker-start",
+    marker_knockout_left: "marker-knockout-left",
+    marker_knockout_right: "marker-knockout-right",
+    marker_side: "marker-side",
+    marks: "marks",
+    marquee_direction: "marquee-direction",
+    marquee_play_count: "marquee-play-count",
+    marquee_speed: "marquee-speed",
+    marquee_style: "marquee-style",
+    mask: "mask",
+    mask_image: "mask-image",
+    mask_repeat: "mask-repeat",
+    mask_position: "mask-position",
+    mask_clip: "mask-clip",
+    mask_origin: "mask-origin",
+    mask_size: "mask-size",
+    mask_box: "mask-box",
+    mask_box_outset: "mask-box-outset",
+    mask_box_repeat: "mask-box-repeat",
+    mask_box_slice: "mask-box-slice",
+    mask_box_source: "mask-box-source",
+    mask_box_width: "mask-box-width",
+    mask_type: "mask-type",
+    max_height: "max-height",
+    max_lines: "max-lines",
+    max_width: "max-width",
+    min_height: "min-height",
+    min_width: "min-width",
+    mix_blend_mode: "mix-blend-mode",
+    nav_down: "nav-down",
+    nav_index: "nav-index",
+    nav_left: "nav-left",
+    nav_right: "nav-right",
+    nav_up: "nav-up",
+    object_fit: "object-fit",
+    object_position: "object-position",
+    offset_after: "offset-after",
+    offset_before: "offset-before",
+    offset_end: "offset-end",
+    offset_start: "offset-start",
+    opacity: "opacity",
+    order: "order",
+    orphans: "orphans",
+    outline: "outline",
+    outline_color: "outline-color",
+    outline_style: "outline-style",
+    outline_width: "outline-width",
+    outline_offset: "outline-offset",
+    overflow: "overflow",
+    overflow_x: "overflow-x",
+    overflow_y: "overflow-y",
+    overflow_style: "overflow-style",
+    overflow_wrap: "overflow-wrap",
+    padding: "padding",
+    padding_bottom: "padding-bottom",
+    padding_left: "padding-left",
+    padding_right: "padding-right",
+    padding_top: "padding-top",
+    page: "page",
+    page_break_after: "page-break-after",
+    page_break_before: "page-break-before",
+    page_break_inside: "page-break-inside",
+    paint_order: "paint-order",
+    pause: "pause",
+    pause_after: "pause-after",
+    pause_before: "pause-before",
+    perspective: "perspective",
+    perspective_origin: "perspective-origin",
+    pitch: "pitch",
+    pitch_range: "pitch-range",
+    play_during: "play-during",
+    pointer_events: "pointer-events",
+    position: "position",
+    quotes: "quotes",
+    region_fragment: "region-fragment",
+    resize: "resize",
+    rest: "rest",
+    rest_after: "rest-after",
+    rest_before: "rest-before",
+    richness: "richness",
+    right: "right",
+    ruby_align: "ruby-align",
+    ruby_merge: "ruby-merge",
+    ruby_position: "ruby-position",
+    scroll_behavior: "scroll-behavior",
+    scroll_snap_coordinate: "scroll-snap-coordinate",
+    scroll_snap_destination: "scroll-snap-destination",
+    scroll_snap_points_x: "scroll-snap-points-x",
+    scroll_snap_points_y: "scroll-snap-points-y",
+    scroll_snap_type: "scroll-snap-type",
+    shape_image_threshold: "shape-image-threshold",
+    shape_inside: "shape-inside",
+    shape_margin: "shape-margin",
+    shape_outside: "shape-outside",
+    shape_padding: "shape-padding",
+    shape_rendering: "shape-rendering",
+    size: "size",
+    speak: "speak",
+    speak_as: "speak-as",
+    speak_header: "speak-header",
+    speak_numeral: "speak-numeral",
+    speak_punctuation: "speak-punctuation",
+    speech_rate: "speech-rate",
+    stop_color: "stop-color",
+    stop_opacity: "stop-opacity",
+    stress: "stress",
+    string_set: "string-set",
+    stroke: "stroke",
+    stroke_dasharray: "stroke-dasharray",
+    stroke_dashoffset: "stroke-dashoffset",
+    stroke_linecap: "stroke-linecap",
+    stroke_linejoin: "stroke-linejoin",
+    stroke_miterlimit: "stroke-miterlimit",
+    stroke_opacity: "stroke-opacity",
+    stroke_width: "stroke-width",
+    tab_size: "tab-size",
+    table_layout: "table-layout",
+    text_align: "text-align",
+    text_align_all: "text-align-all",
+    text_align_last: "text-align-last",
+    text_anchor: "text-anchor",
+    text_combine_upright: "text-combine-upright",
+    text_decoration: "text-decoration",
+    text_decoration_color: "text-decoration-color",
+    text_decoration_line: "text-decoration-line",
+    text_decoration_style: "text-decoration-style",
+    text_decoration_skip: "text-decoration-skip",
+    text_emphasis: "text-emphasis",
+    text_emphasis_color: "text-emphasis-color",
+    text_emphasis_style: "text-emphasis-style",
+    text_emphasis_position: "text-emphasis-position",
+    text_emphasis_skip: "text-emphasis-skip",
+    text_height: "text-height",
+    text_indent: "text-indent",
+    text_justify: "text-justify",
+    text_orientation: "text-orientation",
+    text_overflow: "text-overflow",
+    text_rendering: "text-rendering",
+    text_shadow: "text-shadow",
+    text_size_adjust: "text-size-adjust",
+    text_space_collapse: "text-space-collapse",
+    text_spacing: "text-spacing",
+    text_transform: "text-transform",
+    text_underline_position: "text-underline-position",
+    text_wrap: "text-wrap",
+    top: "top",
+    touch_action: "touch-action",
+    transform: "transform",
+    transform_box: "transform-box",
+    transform_origin: "transform-origin",
+    transform_style: "transform-style",
+    transition: "transition",
+    transition_delay: "transition-delay",
+    transition_duration: "transition-duration",
+    transition_property: "transition-property",
+    unicode_bidi: "unicode-bidi",
+    vector_effect: "vector-effect",
+    vertical_align: "vertical-align",
+    visibility: "visibility",
+    voice_balance: "voice-balance",
+    voice_duration: "voice-duration",
+    voice_family: "voice-family",
+    voice_pitch: "voice-pitch",
+    voice_range: "voice-range",
+    voice_rate: "voice-rate",
+    voice_stress: "voice-stress",
+    voice_volumn: "voice-volumn",
+    volume: "volume",
+    white_space: "white-space",
+    widows: "widows",
+    width: "width",
+    will_change: "will-change",
+    word_break: "word-break",
+    word_spacing: "word-spacing",
+    word_wrap: "word-wrap",
+    wrap_flow: "wrap-flow",
+    wrap_through: "wrap-through",
+    writing_mode: "writing-mode",
+    gap: "gap",
+    list_styler_type: "list-style-type",
+    row_gap: "row-gap",
+    transition_timing_function: "transition-timing-function",
+    user_select: "user-select",
+    webkit_user_select: "-webkit-user-select",
+    z_index : "z-index",
+}
+mapped_trait_methods! {
+    aria_current: "aria-current",
+    aria_details: "aria-details",
+    aria_disabled: "aria-disabled",
+    aria_hidden: "aria-hidden",
+    aria_invalid: "aria-invalid",
+    aria_keyshortcuts: "aria-keyshortcuts",
+    aria_label: "aria-label",
+    aria_roledescription: "aria-roledescription",
+    // Widget Attributes
+    aria_autocomplete: "aria-autocomplete",
+    aria_checked: "aria-checked",
+    aria_expanded: "aria-expanded",
+    aria_haspopup: "aria-haspopup",
+    aria_level: "aria-level",
+    aria_modal: "aria-modal",
+    aria_multiline: "aria-multiline",
+    aria_multiselectable: "aria-multiselectable",
+    aria_orientation: "aria-orientation",
+    aria_placeholder: "aria-placeholder",
+    aria_pressed: "aria-pressed",
+    aria_readonly: "aria-readonly",
+    aria_required: "aria-required",
+    aria_selected: "aria-selected",
+    aria_sort: "aria-sort",
+    aria_valuemax: "aria-valuemax",
+    aria_valuemin: "aria-valuemin",
+    aria_valuenow: "aria-valuenow",
+    aria_valuetext: "aria-valuetext",
+    // Live Region Attributes
+    aria_atomic: "aria-atomic",
+    aria_busy: "aria-busy",
+    aria_live: "aria-live",
+    aria_relevant: "aria-relevant",
+
+    aria_dropeffect: "aria-dropeffect",
+    aria_grabbed: "aria-grabbed",
+    // Relationship Attributes
+    aria_activedescendant: "aria-activedescendant",
+    aria_colcount: "aria-colcount",
+    aria_colindex: "aria-colindex",
+    aria_colspan: "aria-colspan",
+    aria_controls: "aria-controls",
+    aria_describedby: "aria-describedby",
+    aria_errormessage: "aria-errormessage",
+    aria_flowto: "aria-flowto",
+    aria_labelledby: "aria-labelledby",
+    aria_owns: "aria-owns",
+    aria_posinset: "aria-posinset",
+    aria_rowcount: "aria-rowcount",
+    aria_rowindex: "aria-rowindex",
+    aria_rowspan: "aria-rowspan",
+    aria_setsize: "aria-setsize",
+}
+
+pub mod svg {
+    mapped_trait_methods! {
+        accent_height: "accent-height",
+        accumulate: "accumulate",
+        additive: "additive",
+        alignment_baseline: "alignment-baseline",
+        alphabetic: "alphabetic",
+        amplitude: "amplitude",
+        arabic_form: "arabic-form",
+        ascent: "ascent",
+        attributeName: "attributeName",
+        attributeType: "attributeType",
+        azimuth: "azimuth",
+        baseFrequency: "baseFrequency",
+        baseline_shift: "baseline-shift",
+        baseProfile: "baseProfile",
+        bbox: "bbox",
+        begin: "begin",
+        bias: "bias",
+        by: "by",
+        calcMode: "calcMode",
+        cap_height: "cap-height",
+        class: "class",
+        clip: "clip",
+        clipPathUnits: "clipPathUnits",
+        clip_path: "clip-path",
+        clip_rule: "clip-rule",
+        color: "color",
+        color_interpolation: "color-interpolation",
+        color_interpolation_filters: "color-interpolation-filters",
+        color_profile: "color-profile",
+        color_rendering: "color-rendering",
+        contentScriptType: "contentScriptType",
+        contentStyleType: "contentStyleType",
+        crossorigin: "crossorigin",
+        cursor: "cursor",
+        cx: "cx",
+        cy: "cy",
+        d: "d",
+        decelerate: "decelerate",
+        descent: "descent",
+        diffuseConstant: "diffuseConstant",
+        direction: "direction",
+        display: "display",
+        divisor: "divisor",
+        dominant_baseline: "dominant-baseline",
+        dur: "dur",
+        dx: "dx",
+        dy: "dy",
+        edgeMode: "edgeMode",
+        elevation: "elevation",
+        enable_background: "enable-background",
+        end: "end",
+        exponent: "exponent",
+        fill: "fill",
+        fill_opacity: "fill-opacity",
+        fill_rule: "fill-rule",
+        filter: "filter",
+        filterRes: "filterRes",
+        filterUnits: "filterUnits",
+        flood_color: "flood-color",
+        flood_opacity: "flood-opacity",
+        font_family: "font-family",
+        font_size: "font-size",
+        font_size_adjust: "font-size-adjust",
+        font_stretch: "font-stretch",
+        font_style: "font-style",
+        font_variant: "font-variant",
+        font_weight: "font-weight",
+        format: "format",
+        from: "from",
+        fr: "fr",
+        fx: "fx",
+        fy: "fy",
+        g1: "g1",
+        g2: "g2",
+        glyph_name: "glyph-name",
+        glyph_orientation_horizontal: "glyph-orientation-horizontal",
+        glyph_orientation_vertical: "glyph-orientation-vertical",
+        glyphRef: "glyphRef",
+        gradientTransform: "gradientTransform",
+        gradientUnits: "gradientUnits",
+        hanging: "hanging",
+        height: "height",
+        href: "href",
+        hreflang: "hreflang",
+        horiz_adv_x: "horiz-adv-x",
+        horiz_origin_x: "horiz-origin-x",
+        id: "id",
+        ideographic: "ideographic",
+        image_rendering: "image-rendering",
+        _in: "_in",
+        in2: "in2",
+        intercept: "intercept",
+        k: "k",
+        k1: "k1",
+        k2: "k2",
+        k3: "k3",
+        k4: "k4",
+        kernelMatrix: "kernelMatrix",
+        kernelUnitLength: "kernelUnitLength",
+        kerning: "kerning",
+        keyPoints: "keyPoints",
+        keySplines: "keySplines",
+        keyTimes: "keyTimes",
+        lang: "lang",
+        lengthAdjust: "lengthAdjust",
+        letter_spacing: "letter-spacing",
+        lighting_color: "lighting-color",
+        limitingConeAngle: "limitingConeAngle",
+        local: "local",
+        marker_end: "marker-end",
+        marker_mid: "marker-mid",
+        marker_start: "marker_start",
+        markerHeight: "markerHeight",
+        markerUnits: "markerUnits",
+        markerWidth: "markerWidth",
+        mask: "mask",
+        maskContentUnits: "maskContentUnits",
+        maskUnits: "maskUnits",
+        mathematical: "mathematical",
+        max: "max",
+        media: "media",
+        method: "method",
+        min: "min",
+        mode: "mode",
+        name: "name",
+        numOctaves: "numOctaves",
+        offset: "offset",
+        opacity: "opacity",
+        operator: "operator",
+        order: "order",
+        orient: "orient",
+        orientation: "orientation",
+        origin: "origin",
+        overflow: "overflow",
+        overline_position: "overline-position",
+        overline_thickness: "overline-thickness",
+        panose_1: "panose-1",
+        paint_order: "paint-order",
+        path: "path",
+        pathLength: "pathLength",
+        patternContentUnits: "patternContentUnits",
+        patternTransform: "patternTransform",
+        patternUnits: "patternUnits",
+        ping: "ping",
+        pointer_events: "pointer-events",
+        points: "points",
+        pointsAtX: "pointsAtX",
+        pointsAtY: "pointsAtY",
+        pointsAtZ: "pointsAtZ",
+        preserveAlpha: "preserveAlpha",
+        preserveAspectRatio: "preserveAspectRatio",
+        primitiveUnits: "primitiveUnits",
+        r: "r",
+        radius: "radius",
+        referrerPolicy: "referrerPolicy",
+        refX: "refX",
+        refY: "refY",
+        rel: "rel",
+        rendering_intent: "rendering-intent",
+        repeatCount: "repeatCount",
+        repeatDur: "repeatDur",
+        requiredExtensions: "requiredExtensions",
+        requiredFeatures: "requiredFeatures",
+        restart: "restart",
+        result: "result",
+        role: "role",
+        rotate: "rotate",
+        rx: "rx",
+        ry: "ry",
+        scale: "scale",
+        seed: "seed",
+        shape_rendering: "shape-rendering",
+        slope: "slope",
+        spacing: "spacing",
+        specularConstant: "specularConstant",
+        specularExponent: "specularExponent",
+        speed: "speed",
+        spreadMethod: "spreadMethod",
+        startOffset: "startOffset",
+        stdDeviation: "stdDeviation",
+        stemh: "stemh",
+        stemv: "stemv",
+        stitchTiles: "stitchTiles",
+        stop_color: "stop_color",
+        stop_opacity: "stop_opacity",
+        strikethrough_position: "strikethrough-position",
+        strikethrough_thickness: "strikethrough-thickness",
+        string: "string",
+        stroke: "stroke",
+        stroke_dasharray: "stroke-dasharray",
+        stroke_dashoffset: "stroke-dashoffset",
+        stroke_linecap: "stroke-linecap",
+        stroke_linejoin: "stroke-linejoin",
+        stroke_miterlimit: "stroke-miterlimit",
+        stroke_opacity: "stroke-opacity",
+        stroke_width: "stroke-width",
+        style: "style",
+        surfaceScale: "surfaceScale",
+        systemLanguage: "systemLanguage",
+        tabindex: "tabindex",
+        tableValues: "tableValues",
+        target: "target",
+        targetX: "targetX",
+        targetY: "targetY",
+        text_anchor: "text-anchor",
+        text_decoration: "text-decoration",
+        text_rendering: "text-rendering",
+        textLength: "textLength",
+        to: "to",
+        transform: "transform",
+        transform_origin: "transform-origin",
+        r#type: "_type",
+        u1: "u1",
+        u2: "u2",
+        underline_position: "underline-position",
+        underline_thickness: "underline-thickness",
+        unicode: "unicode",
+        unicode_bidi: "unicode-bidi",
+        unicode_range: "unicode-range",
+        units_per_em: "units-per-em",
+        v_alphabetic: "v-alphabetic",
+        v_hanging: "v-hanging",
+        v_ideographic: "v-ideographic",
+        v_mathematical: "v-mathematical",
+        values: "values",
+        vector_effect: "vector-effect",
+        version: "version",
+        vert_adv_y: "vert-adv-y",
+        vert_origin_x: "vert-origin-x",
+        vert_origin_y: "vert-origin-y",
+        view_box: "viewBox",
+        view_target: "viewTarget",
+        visibility: "visibility",
+        width: "width",
+        widths: "widths",
+        word_spacing: "word-spacing",
+        writing_mode: "writing-mode",
+        x: "x",
+        x_height: "x-height",
+        x1: "x1",
+        x2: "x2",
+        xmlns: "xmlns",
+        x_channel_selector: "xChannelSelector",
+        y: "y",
+        y1: "y1",
+        y2: "y2",
+        y_channel_selector: "yChannelSelector",
+        z: "z",
+        zoomAndPan: "zoomAndPan",
+    }
+}

+ 137 - 0
packages/rsx_interperter/src/captuered_context.rs

@@ -0,0 +1,137 @@
+use dioxus_core::VNode;
+use dioxus_rsx::{BodyNode, CallBody, Component, ElementAttr, IfmtInput};
+use quote::{quote, ToTokens, TokenStreamExt};
+use std::collections::HashMap;
+use syn::Expr;
+
+#[derive(Default)]
+pub struct CapturedContextBuilder {
+    attributes: HashMap<String, IfmtInput>,
+    text: Vec<IfmtInput>,
+    components: Vec<Component>,
+    iterators: Vec<Expr>,
+}
+
+impl CapturedContextBuilder {
+    pub fn extend(&mut self, other: CapturedContextBuilder) {
+        self.attributes.extend(other.attributes);
+        self.text.extend(other.text);
+        self.components.extend(other.components);
+    }
+
+    pub fn from_call_body(body: CallBody) -> Self {
+        let mut new = Self::default();
+        for node in body.roots {
+            new.extend(Self::find_captured(node));
+        }
+        new
+    }
+
+    fn find_captured(node: BodyNode) -> Self {
+        let mut captured = CapturedContextBuilder::default();
+        match node {
+            BodyNode::Element(el) => {
+                for attr in el.attributes {
+                    let (name, value_tokens) = match attr.attr {
+                        ElementAttr::AttrText { name, value } => {
+                            (name.to_string(), value.to_token_stream())
+                        }
+                        ElementAttr::AttrExpression { name, value } => {
+                            (name.to_string(), value.to_token_stream())
+                        }
+                        ElementAttr::CustomAttrText { name, value } => {
+                            (name.value(), value.to_token_stream())
+                        }
+                        ElementAttr::CustomAttrExpression { name, value } => {
+                            (name.value(), value.to_token_stream())
+                        }
+                        _ => continue,
+                    };
+                    let formated: IfmtInput = syn::parse2(value_tokens).unwrap();
+                    captured.attributes.insert(name, formated);
+                }
+                for child in el.children {
+                    captured.extend(Self::find_captured(child));
+                }
+            }
+            BodyNode::Component(comp) => {
+                let fn_name = comp.name.segments.last().unwrap().ident.to_string();
+                captured.components.push(comp);
+            }
+            BodyNode::Text(t) => {
+                let tokens = t.to_token_stream();
+                let formated: IfmtInput = syn::parse2(tokens).unwrap();
+                captured.text.push(formated);
+            }
+            BodyNode::RawExpr(expr) => captured.iterators.push(expr),
+            BodyNode::Meta(_) => (),
+        }
+        captured
+    }
+}
+
+impl ToTokens for CapturedContextBuilder {
+    fn to_tokens(&self, tokens: &mut quote::__private::TokenStream) {
+        let CapturedContextBuilder {
+            attributes,
+            text,
+            components,
+            iterators,
+        } = self;
+        let captured: Vec<_> = attributes
+            .iter()
+            .map(|(_, fmt)| fmt.named_args.iter())
+            .chain(text.iter().map(|fmt| fmt.named_args.iter()))
+            .flatten()
+            .collect();
+        let captured_names = captured.iter().map(|(n, _)| n);
+        let captured_expr = captured.iter().map(|(_, e)| e);
+        tokens.append_all(quote! {
+            CapturedContext {
+                captured: IfmtArgs{
+                    named_args: &'static [#((#captured_names, #captured_expr)),*]
+                },
+                components: vec![#(#components),*],
+                iterators: vec![#(#iterators),*],
+            }
+        })
+    }
+}
+
+struct CapturedComponentBuilder {
+    name: syn::Path,
+    function: String,
+}
+
+pub struct CapturedContext<'a> {
+    // map of the attribute name to the formated value
+    captured: IfmtArgs,
+    // the only thing we can update in component is the children
+    components: Vec<VNode<'a>>,
+    // we can't reasonably interpert iterators, so they are staticly inserted
+    iterators: Vec<VNode<'a>>,
+}
+
+pub struct IfmtArgs {
+    // live reload only supports named arguments
+    pub named_args: &'static [(&'static str, String)],
+}
+
+enum IfmtSegment<'a> {
+    Static(&'a str),
+    Dynamic(&'a str),
+}
+
+enum RsxNode<'a> {
+    Element {
+        name: String,
+        attributes: Vec<(String, IfmtSegment<'a>)>,
+        children: Vec<RsxNode<'a>>,
+    },
+    Text {
+        text: Vec<IfmtSegment<'a>>,
+    },
+    Component {
+        children: Vec<RsxNode<'a>>,
+    },
+}

+ 1380 - 0
packages/rsx_interperter/src/elements.rs

@@ -0,0 +1,1380 @@
+// find the mapped attribute name
+pub fn element_to_static_str(element: &str) -> Option<(&'static str, Option<&'static str>)> {
+    ELEMENTS_WITH_MAPPED_ATTRIBUTES
+        .iter()
+        .find(|(el, _)| *el == element)
+        .map(|(el, _)| (*el, None))
+        .or_else(|| {
+            ELEMENTS_WITH_NAMESPACE
+                .iter()
+                .find(|(el, _, _)| *el == element)
+                .map(|(el, ns, _)| (*el, Some(*ns)))
+        })
+        .or_else(|| {
+            ELEMENTS_WITHOUT_NAMESPACE
+                .iter()
+                .find(|(el, _)| *el == element)
+                .map(|(el, _)| (*el, None))
+        })
+}
+
+macro_rules! builder_constructors {
+    (
+        $(
+            $(#[$attr:meta])*
+            $name:ident {
+                $(
+                    $(#[$attr_method:meta])*
+                    $fil:ident: $vil:ident,
+                )*
+            };
+         )*
+    ) => {
+        pub const ELEMENTS_WITHOUT_NAMESPACE: &'static [(&'static str, &'static [&'static str])] = &[
+            $(
+                (
+                    stringify!($name),
+                    &[
+                        $(
+                           stringify!($fil),
+                        )*
+                    ]
+                ),
+            )*
+            ];
+        };
+
+    ( $(
+        $(#[$attr:meta])*
+        $name:ident <> $namespace:tt {
+            $($fil:ident: $vil:ident,)*
+        };
+    )* ) => {
+        pub const ELEMENTS_WITH_NAMESPACE: &'static [(&'static str, &'static str, &'static [&'static str])] = &[
+            $(
+                (
+                    stringify!($name),
+                    stringify!($namespace),
+                    &[
+                        $(
+                            stringify!($fil),
+                        )*
+                    ]
+                ),
+            )*
+        ];
+    };
+}
+pub const ELEMENTS_WITH_MAPPED_ATTRIBUTES: &'static [(
+    &'static str,
+    &'static [(&'static str, &'static str)],
+)] = &[
+    ("script", &[("r#type", "type"), ("r#script", "script")]),
+    ("button", &[("r#type", "type")]),
+    ("select", &[("value", "value")]),
+    ("option", &[("selected", "selected")]),
+    ("textarea", &[("value", "value")]),
+    ("label", &[("r#for", "for")]),
+    ("input", &[("r#type", "type"), ("value", "value")]),
+];
+
+// Organized in the same order as
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Element
+//
+// Does not include obsolete elements.
+//
+// This namespace represents a collection of modern HTML-5 compatiable elements.
+//
+// This list does not include obsolete, deprecated, experimental, or poorly supported elements.
+builder_constructors! {
+    // Document metadata
+
+    /// Build a
+    /// [`<base>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base)
+    /// element.
+    ///
+    base {
+        href: Uri,
+        target: Target,
+    };
+
+    /// Build a
+    /// [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head)
+    /// element.
+    head {};
+
+    /// Build a
+    /// [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link)
+    /// element.
+    link {
+        // as: Mime,
+        crossorigin: CrossOrigin,
+        href: Uri,
+        hreflang: LanguageTag,
+        media: String, // FIXME media query
+        rel: LinkType,
+        sizes: String, // FIXME
+        title: String, // FIXME
+        r#type: Mime,
+        integrity: String,
+    };
+
+    /// Build a
+    /// [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta)
+    /// element.
+    meta {
+        charset: String, // FIXME IANA standard names
+        content: String,
+        http_equiv: HTTPEquiv,
+        name: Metadata,
+    };
+
+    /// Build a
+    /// [`<style>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style)
+    /// element.
+    style {
+        r#type: Mime,
+        media: String, // FIXME media query
+        nonce: Nonce,
+        title: String, // FIXME
+    };
+
+    /// Build a
+    /// [`<title>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title)
+    /// element.
+    title { };
+
+    // Sectioning root
+
+    /// Build a
+    /// [`<body>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/body)
+    /// element.
+    body {};
+
+    // ------------------
+    // Content sectioning
+    // ------------------
+
+    /// Build a
+    /// [`<address>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/address)
+    /// element.
+    address {};
+
+    /// Build a
+    /// [`<article>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/article)
+    /// element.
+    article {};
+
+    /// Build a
+    /// [`<aside>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/aside)
+    /// element.
+    aside {};
+
+    /// Build a
+    /// [`<footer>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/footer)
+    /// element.
+    footer {};
+
+    /// Build a
+    /// [`<header>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header)
+    /// element.
+    header {};
+
+    /// Build a
+    /// [`<h1>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h1)
+    /// element.
+    ///
+    /// # About
+    /// - The HTML `<h1>` element is found within the `<body>` tag.
+    /// - Headings can range from `<h1>` to `<h6>`.
+    /// - The most important heading is `<h1>` and the least important heading is `<h6>`.
+    /// - The `<h1>` heading is the first heading in the document.
+    /// - The `<h1>` heading is usually a large bolded font.
+    ///
+    /// # Usage
+    ///
+    /// ```
+    /// html!(<h1> A header element </h1>)
+    /// rsx!(h1 { "A header element" })
+    /// LazyNodes::new(|f| f.el(h1).children([f.text("A header element")]).finish())
+    /// ```
+    h1 {};
+
+
+    /// Build a
+    /// [`<h2>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h2)
+    /// element.
+    ///
+    /// # About
+    /// - The HTML `<h2>` element is found within the `<body>` tag.
+    /// - Headings can range from `<h1>` to `<h6>`.
+    /// - The most important heading is `<h1>` and the least important heading is `<h6>`.
+    /// - The `<h2>` heading is the second heading in the document.
+    /// - The `<h2>` heading is usually a large bolded font.
+    ///
+    /// # Usage
+    /// ```
+    /// html!(<h2> A header element </h2>)
+    /// rsx!(h2 { "A header element" })
+    /// LazyNodes::new(|f| f.el(h2).children([f.text("A header element")]).finish())
+    /// ```
+    h2 {};
+
+
+    /// Build a
+    /// [`<h3>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h3)
+    /// element.
+    ///
+    /// # About
+    /// - The HTML <h1> element is found within the <body> tag.
+    /// - Headings can range from <h1> to <h6>.
+    /// - The most important heading is <h1> and the least important heading is <h6>.
+    /// - The <h1> heading is the first heading in the document.
+    /// - The <h1> heading is usually a large bolded font.
+    h3 {};
+    /// Build a
+    /// [`<h4>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h4)
+    /// element.
+    h4 {};
+    /// Build a
+    /// [`<h5>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h5)
+    /// element.
+    h5 {};
+    /// Build a
+    /// [`<h6>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h6)
+    /// element.
+    h6 {};
+
+    /// Build a
+    /// [`<main>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/main)
+    /// element.
+    main {};
+    /// Build a
+    /// [`<nav>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/nav)
+    /// element.
+    nav {};
+    /// Build a
+    /// [`<section>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/section)
+    /// element.
+    section {};
+
+    // Text content
+
+    /// Build a
+    /// [`<blockquote>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote)
+    /// element.
+    blockquote {
+        cite: Uri,
+    };
+    /// Build a
+    /// [`<dd>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dd)
+    /// element.
+    dd {};
+
+    /// Build a
+    /// [`<div>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div)
+    /// element.
+    ///
+    /// Part of the HTML namespace. Only works in HTML-compatible renderers
+    ///
+    /// ## Definition and Usage
+    /// - The <div> tag defines a division or a section in an HTML document.
+    /// - The <div> tag is used as a container for HTML elements - which is then styled with CSS or manipulated with  JavaScript.
+    /// - The <div> tag is easily styled by using the class or id attribute.
+    /// - Any sort of content can be put inside the <div> tag!
+    ///
+    /// Note: By default, browsers always place a line break before and after the <div> element.
+    ///
+    /// ## Usage
+    /// ```
+    /// html!(<div> A header element </div>)
+    /// rsx!(div { "A header element" })
+    /// LazyNodes::new(|f| f.element(div, &[], &[], &[], None))
+    /// ```
+    ///
+    /// ## References:
+    /// - <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div>
+    /// - <https://www.w3schools.com/tags/tag_div.asp>
+    div {};
+
+    /// Build a
+    /// [`<dl>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl)
+    /// element.
+    dl {};
+
+    /// Build a
+    /// [`<dt>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dt)
+    /// element.
+    dt {};
+
+    /// Build a
+    /// [`<figcaption>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figcaption)
+    /// element.
+    figcaption {};
+
+    /// Build a
+    /// [`<figure>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure)
+    /// element.
+    figure {};
+
+    /// Build a
+    /// [`<hr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hr)
+    /// element.
+    hr {};
+
+    /// Build a
+    /// [`<li>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li)
+    /// element.
+    li {
+        value: isize,
+    };
+
+    /// Build a
+    /// [`<ol>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol)
+    /// element.
+    ol {
+        reversed: Bool,
+        start: isize,
+        r#type: OrderedListType,
+    };
+
+    /// Build a
+    /// [`<p>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p)
+    /// element.
+    p {};
+
+    /// Build a
+    /// [`<pre>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre)
+    /// element.
+    pre {};
+
+    /// Build a
+    /// [`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul)
+    /// element.
+    ul {};
+
+
+    // Inline text semantics
+
+    /// Build a
+    /// [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a)
+    /// element.
+    a {
+        download: String,
+        href: Uri,
+        hreflang: LanguageTag,
+        target: Target,
+        r#type: Mime,
+        // ping: SpacedList<Uri>,
+        // rel: SpacedList<LinkType>,
+        ping: SpacedList,
+        rel: SpacedList,
+    };
+
+    /// Build a
+    /// [`<abbr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/abbr)
+    /// element.
+    abbr {};
+
+    /// Build a
+    /// [`<b>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/b)
+    /// element.
+    b {};
+
+    /// Build a
+    /// [`<bdi>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdi)
+    /// element.
+    bdi {};
+
+    /// Build a
+    /// [`<bdo>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdo)
+    /// element.
+    bdo {};
+
+    /// Build a
+    /// [`<br>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/br)
+    /// element.
+    br {};
+
+    /// Build a
+    /// [`<cite>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/cite)
+    /// element.
+    cite {};
+
+    /// Build a
+    /// [`<code>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code)
+    /// element.
+    code {
+        language: String,
+    };
+
+    /// Build a
+    /// [`<data>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/data)
+    /// element.
+    data {
+        value: String,
+    };
+
+    /// Build a
+    /// [`<dfn>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dfn)
+    /// element.
+    dfn {};
+
+    /// Build a
+    /// [`<em>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/em)
+    /// element.
+    em {};
+
+    /// Build a
+    /// [`<i>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/i)
+    /// element.
+    i {};
+
+    /// Build a
+    /// [`<kbd>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/kbd)
+    /// element.
+    kbd {};
+
+    /// Build a
+    /// [`<mark>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark)
+    /// element.
+    mark {};
+
+    /// Build a
+    /// [`<menu>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu)
+    /// element.
+    menu {};
+
+    /// Build a
+    /// [`<q>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/q)
+    /// element.
+    q {
+        cite: Uri,
+    };
+
+
+    /// Build a
+    /// [`<rp>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rp)
+    /// element.
+    rp {};
+
+
+    /// Build a
+    /// [`<rt>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rt)
+    /// element.
+    rt {};
+
+
+    /// Build a
+    /// [`<ruby>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ruby)
+    /// element.
+    ruby {};
+
+    /// Build a
+    /// [`<s>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/s)
+    /// element.
+    s {};
+
+    /// Build a
+    /// [`<samp>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/samp)
+    /// element.
+    samp {};
+
+    /// Build a
+    /// [`<small>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/small)
+    /// element.
+    small {};
+
+    /// Build a
+    /// [`<span>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/span)
+    /// element.
+    span {};
+
+    /// Build a
+    /// [`<strong>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/strong)
+    /// element.
+    strong {};
+
+    /// Build a
+    /// [`<sub>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sub)
+    /// element.
+    sub {};
+
+    /// Build a
+    /// [`<sup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sup)
+    /// element.
+    sup {};
+
+    /// Build a
+    /// [`<time>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time)
+    /// element.
+    time {};
+
+    /// Build a
+    /// [`<u>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/u)
+    /// element.
+    u {};
+
+    /// Build a
+    /// [`<var>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/var)
+    /// element.
+    var {};
+
+    /// Build a
+    /// [`<wbr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/wbr)
+    /// element.
+    wbr {};
+
+
+    // Image and multimedia
+
+    /// Build a
+    /// [`<area>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/area)
+    /// element.
+    area {
+        alt: String,
+        coords: String, // TODO could perhaps be validated
+        download: Bool,
+        href: Uri,
+        hreflang: LanguageTag,
+        shape: AreaShape,
+        target: Target,
+        // ping: SpacedList<Uri>,
+        // rel: SpacedSet<LinkType>,
+    };
+
+    /// Build a
+    /// [`<audio>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio)
+    /// element.
+    audio {
+        autoplay: Bool,
+        controls: Bool,
+        crossorigin: CrossOrigin,
+        muted: Bool,
+        preload: Preload,
+        src: Uri,
+        r#loop: Bool,
+    };
+
+    /// Build a
+    /// [`<img>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img)
+    /// element.
+    img {
+        alt: String,
+        crossorigin: CrossOrigin,
+        decoding: ImageDecoding,
+        height: usize,
+        ismap: Bool,
+        src: Uri,
+        srcset: String, // FIXME this is much more complicated
+        usemap: String, // FIXME should be a fragment starting with '#'
+        width: usize,
+        referrerpolicy: String,
+        // sizes: SpacedList<String>, // FIXME it's not really just a string
+    };
+
+    /// Build a
+    /// [`<map>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/map)
+    /// element.
+    map {
+        name: Id,
+    };
+
+    /// Build a
+    /// [`<track>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track)
+    /// element.
+    track {
+        default: Bool,
+        kind: VideoKind,
+        label: String,
+        src: Uri,
+        srclang: LanguageTag,
+    };
+
+    /// Build a
+    /// [`<video>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video)
+    /// element.
+    video {
+        autoplay: Bool,
+        controls: Bool,
+        crossorigin: CrossOrigin,
+        height: usize,
+        r#loop: Bool,
+        muted: Bool,
+        preload: Preload,
+        playsinline: Bool,
+        poster: Uri,
+        src: Uri,
+        width: usize,
+    };
+
+
+    // Embedded content
+
+    /// Build a
+    /// [`<embed>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed)
+    /// element.
+    embed {
+        height: usize,
+        src: Uri,
+        r#type: Mime,
+        width: usize,
+    };
+
+    /// Build a
+    /// [`<iframe>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe)
+    /// element.
+    iframe {
+        allow: FeaturePolicy,
+        allowfullscreen: Bool,
+        allowpaymentrequest: Bool,
+        height: usize,
+        name: Id,
+        referrerpolicy: ReferrerPolicy,
+        src: Uri,
+        srcdoc: Uri,
+        width: usize,
+
+        marginWidth: String,
+        align: String,
+        longdesc: String,
+
+        scrolling: String,
+        marginHeight: String,
+        frameBorder: String,
+        // sandbox: SpacedSet<Sandbox>,
+    };
+
+    /// Build a
+    /// [`<object>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object)
+    /// element.
+    object {
+        data: Uri,
+        form: Id,
+        height: usize,
+        name: Id,
+        r#type: Mime,
+        typemustmatch: Bool,
+        usemap: String, // TODO should be a fragment starting with '#'
+        width: usize,
+    };
+
+    /// Build a
+    /// [`<param>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/param)
+    /// element.
+    param {
+        name: String,
+        value: String,
+    };
+
+    /// Build a
+    /// [`<picture>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture)
+    /// element.
+    picture {};
+
+    /// Build a
+    /// [`<source>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source)
+    /// element.
+    source {
+        src: Uri,
+        r#type: Mime,
+    };
+
+
+    // Scripting
+
+    /// Build a
+    /// [`<canvas>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas)
+    /// element.
+    canvas {
+        height: usize,
+        width: usize,
+    };
+
+    /// Build a
+    /// [`<noscript>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/noscript)
+    /// element.
+    noscript {};
+
+    /// Build a
+    /// [`<script>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script)
+    /// element.
+    ///
+    /// The [`script`] HTML element is used to embed executable code or data; this is typically used to embed or refer to
+    /// JavaScript code. The [`script`] element can also be used with other languages, such as WebGL's GLSL shader
+    /// programming language and JSON.
+    script {
+        /// Normal script elements pass minimal information to the window.onerror for scripts which do not pass the
+        /// standard CORS checks. To allow error logging for sites which use a separate domain for static media, use
+        /// this attribute. See CORS settings attributes for a more descriptive explanation of its valid arguments.
+        crossorigin: CrossOrigin,
+
+        /// This Boolean attribute is set to indicate to a browser that the script is meant to be executed after the
+        /// document has been parsed, but before firing DOMContentLoaded.
+        ///
+        /// Scripts with the defer attribute will prevent the DOMContentLoaded event from firing until the script has
+        /// loaded and finished evaluating.
+        ///
+        /// ----
+        /// ### Warning:
+        ///
+        /// This attribute must not be used if the src attribute is absent (i.e. for inline scripts), in this
+        /// case it would have no effect.
+        ///
+        /// ----
+        ///
+        /// The defer attribute has no effect on module scripts — they defer by default.
+        /// Scripts with the defer attribute will execute in the order in which they appear in the document.
+        ///
+        /// This attribute allows the elimination of parser-blocking JavaScript where the browser would have to load and
+        /// evaluate scripts before continuing to parse. async has a similar effect in this case.
+        defer: Bool,
+        integrity: Integrity,
+        nomodule: Bool,
+        nonce: Nonce,
+        src: Uri,
+        text: String,
+
+    };
+
+
+    // Demarcating edits
+
+    /// Build a
+    /// [`<del>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/del)
+    /// element.
+    del {
+        cite: Uri,
+        datetime: Datetime,
+    };
+
+    /// Build a
+    /// [`<ins>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ins)
+    /// element.
+    ins {
+        cite: Uri,
+        datetime: Datetime,
+    };
+
+
+    // Table content
+
+    /// Build a
+    /// [`<caption>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/caption)
+    /// element.
+    caption {};
+
+    /// Build a
+    /// [`<col>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/col)
+    /// element.
+    col {
+        span: usize,
+    };
+
+    /// Build a
+    /// [`<colgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/colgroup)
+    /// element.
+    colgroup {
+        span: usize,
+    };
+
+    /// Build a
+    /// [`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table)
+    /// element.
+    table {};
+
+    /// Build a
+    /// [`<tbody>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tbody)
+    /// element.
+    tbody {};
+
+    /// Build a
+    /// [`<td>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td)
+    /// element.
+    td {
+        colspan: usize,
+        rowspan: usize,
+        // headers: SpacedSet<Id>,
+    };
+
+    /// Build a
+    /// [`<tfoot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tfoot)
+    /// element.
+    tfoot {};
+
+    /// Build a
+    /// [`<th>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th)
+    /// element.
+    th {
+        abbr: String,
+        colspan: usize,
+        rowspan: usize,
+        scope: TableHeaderScope,
+        // headers: SpacedSet<Id>,
+    };
+
+    /// Build a
+    /// [`<thead>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/thead)
+    /// element.
+    thead {};
+
+    /// Build a
+    /// [`<tr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tr)
+    /// element.
+    tr {};
+
+
+    // Forms
+
+    /// Build a
+    /// [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button)
+    /// element.
+    button {
+        autofocus: Bool,
+        disabled: Bool,
+        form: Id,
+        formaction: Uri,
+        formenctype: FormEncodingType,
+        formmethod: FormMethod,
+        formnovalidate: Bool,
+        formtarget: Target,
+        name: Id,
+        value: String,
+    };
+
+    /// Build a
+    /// [`<datalist>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist)
+    /// element.
+    datalist {};
+
+    /// Build a
+    /// [`<fieldset>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset)
+    /// element.
+    fieldset {};
+
+    /// Build a
+    /// [`<form>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form)
+    /// element.
+    form {
+        // accept-charset: SpacedList<CharacterEncoding>,
+        action: Uri,
+        autocomplete: OnOff,
+        enctype: FormEncodingType,
+        method: FormMethod,
+        name: Id,
+        novalidate: Bool,
+        target: Target,
+    };
+
+    /// Build a
+    /// [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input)
+    /// element.
+    input {
+        accept: String,
+        alt: String,
+        autocomplete: String,
+        autofocus: Bool,
+        capture: String,
+        checked: Bool,
+        disabled: Bool,
+        form: Id,
+        formaction: Uri,
+        formenctype: FormEncodingType,
+        formmethod: FormDialogMethod,
+        formnovalidate: Bool,
+        formtarget: Target,
+        height: isize,
+        list: Id,
+        max: String,
+        maxlength: usize,
+        min: String,
+        minlength: usize,
+        multiple: Bool,
+        name: Id,
+        pattern: String,
+        placeholder: String,
+        readonly: Bool,
+        required: Bool,
+        size: usize,
+        spellcheck: Bool,
+        src: Uri,
+        step: String,
+        tabindex: usize,
+        width: isize,
+
+        // Manual implementations below...
+        // r#type: InputType,
+        // value: String,
+    };
+
+    /// Build a
+    /// [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label)
+    /// element.
+    label {
+        form: Id,
+        // r#for: Id,
+    };
+
+    /// Build a
+    /// [`<legend>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/legend)
+    /// element.
+    legend {};
+
+    /// Build a
+    /// [`<meter>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meter)
+    /// element.
+    meter {
+        value: isize,
+        min: isize,
+        max: isize,
+        low: isize,
+        high: isize,
+        optimum: isize,
+        form: Id,
+    };
+
+    /// Build a
+    /// [`<optgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup)
+    /// element.
+    optgroup {
+        disabled: Bool,
+        label: String,
+    };
+
+    /// Build a
+    /// [`<option>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option)
+    /// element.
+    option {
+        disabled: Bool,
+        label: String,
+
+
+        value: String,
+
+        // defined below
+        // selected: Bool,
+    };
+
+    /// Build a
+    /// [`<output>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/output)
+    /// element.
+    output {
+        form: Id,
+        name: Id,
+        // r#for: SpacedSet<Id>,
+    };
+
+    /// Build a
+    /// [`<progress>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress)
+    /// element.
+    progress {
+        max: f64,
+        value: f64,
+    };
+
+    /// Build a
+    /// [`<select>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select)
+    /// element.
+    select {
+        // defined below
+        // value: String,
+        autocomplete: String,
+        autofocus: Bool,
+        disabled: Bool,
+        form: Id,
+        multiple: Bool,
+        name: Id,
+        required: Bool,
+        size: usize,
+    };
+
+    /// Build a
+    /// [`<textarea>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea)
+    /// element.
+    textarea {
+        autocomplete: OnOff,
+        autofocus: Bool,
+        cols: usize,
+        disabled: Bool,
+        form: Id,
+        maxlength: usize,
+        minlength: usize,
+        name: Id,
+        placeholder: String,
+        readonly: Bool,
+        required: Bool,
+        rows: usize,
+        spellcheck: BoolOrDefault,
+        wrap: Wrap,
+    };
+
+
+    // Interactive elements
+
+    /// Build a
+    /// [`<details>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details)
+    /// element.
+    details {
+        open: Bool,
+    };
+
+
+
+    /// Build a
+    /// [`<summary>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary)
+    /// element.
+    summary {};
+
+    // Web components
+
+    /// Build a
+    /// [`<slot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot)
+    /// element.
+    slot {};
+
+    /// Build a
+    /// [`<template>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template)
+    /// element.
+    template {};
+}
+
+builder_constructors! {
+    // SVG components
+    /// Build a
+    /// [`<svg>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg)
+    /// element.
+    svg <> "http://www.w3.org/2000/svg" { };
+
+
+    // /// Build a
+    // /// [`<a>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/a)
+    // /// element.
+    // a <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<animate>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animate)
+    /// element.
+    animate <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<animateMotion>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animateMotion)
+    /// element.
+    animateMotion <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<animateTransform>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animateTransform)
+    /// element.
+    animateTransform <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<circle>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/circle)
+    /// element.
+    circle <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<clipPath>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/clipPath)
+    /// element.
+    clipPath <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<defs>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs)
+    /// element.
+    defs <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<desc>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/desc)
+    /// element.
+    desc <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<discard>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/discard)
+    /// element.
+    discard <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<ellipse>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/ellipse)
+    /// element.
+    ellipse <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feBlend>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feBlend)
+    /// element.
+    feBlend <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feColorMatrix>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feColorMatrix)
+    /// element.
+    feColorMatrix <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feComponentTransfer>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feComponentTransfer)
+    /// element.
+    feComponentTransfer <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feComposite>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feComposite)
+    /// element.
+    feComposite <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feConvolveMatrix>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feConvolveMatrix)
+    /// element.
+    feConvolveMatrix <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feDiffuseLighting>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDiffuseLighting)
+    /// element.
+    feDiffuseLighting <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feDisplacementMap>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDisplacementMap)
+    /// element.
+    feDisplacementMap <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feDistantLight>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDistantLight)
+    /// element.
+    feDistantLight <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feDropShadow>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDropShadow)
+    /// element.
+    feDropShadow <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feFlood>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFlood)
+    /// element.
+    feFlood <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feFuncA>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFuncA)
+    /// element.
+    feFuncA <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feFuncB>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFuncB)
+    /// element.
+    feFuncB <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feFuncG>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFuncG)
+    /// element.
+    feFuncG <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feFuncR>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feFuncR)
+    /// element.
+    feFuncR <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feGaussianBlur>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feGaussianBlur)
+    /// element.
+    feGaussianBlur <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feImage>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feImage)
+    /// element.
+    feImage <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feMerge>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feMerge)
+    /// element.
+    feMerge <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feMergeNode>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feMergeNode)
+    /// element.
+    feMergeNode <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feMorphology>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feMorphology)
+    /// element.
+    feMorphology <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feOffset>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feOffset)
+    /// element.
+    feOffset <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<fePointLight>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/fePointLight)
+    /// element.
+    fePointLight <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feSpecularLighting>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feSpecularLighting)
+    /// element.
+    feSpecularLighting <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feSpotLight>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feSpotLight)
+    /// element.
+    feSpotLight <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feTile>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feTile)
+    /// element.
+    feTile <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<feTurbulence>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feTurbulence)
+    /// element.
+    feTurbulence <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<filter>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/filter)
+    /// element.
+    filter <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<foreignObject>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject)
+    /// element.
+    foreignObject <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<g>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g)
+    /// element.
+    g <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<hatch>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/hatch)
+    /// element.
+    hatch <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<hatchpath>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/hatchpath)
+    /// element.
+    hatchpath <> "http://www.w3.org/2000/svg" {};
+
+    // /// Build a
+    // /// [`<image>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image)
+    // /// element.
+    // image <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<line>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/line)
+    /// element.
+    line <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<linearGradient>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/linearGradient)
+    /// element.
+    linearGradient <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<marker>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/marker)
+    /// element.
+    marker <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<mask>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/mask)
+    /// element.
+    mask <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<metadata>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/metadata)
+    /// element.
+    metadata <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<mpath>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/mpath)
+    /// element.
+    mpath <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<path>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path)
+    /// element.
+    path <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<pattern>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/pattern)
+    /// element.
+    pattern <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<polygon>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polygon)
+    /// element.
+    polygon <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<polyline>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polyline)
+    /// element.
+    polyline <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<radialGradient>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/radialGradient)
+    /// element.
+    radialGradient <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<rect>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/rect)
+    /// element.
+    rect <> "http://www.w3.org/2000/svg" {};
+
+    // /// Build a
+    // /// [`<script>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/script)
+    // /// element.
+    // script <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<set>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/set)
+    /// element.
+    set <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<stop>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/stop)
+    /// element.
+    stop <> "http://www.w3.org/2000/svg" {};
+
+    // /// Build a
+    // /// [`<style>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/style)
+    // /// element.
+    // style <> "http://www.w3.org/2000/svg" {};
+
+    // /// Build a
+    // /// [`<svg>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg)
+    // /// element.
+    // svg <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<switch>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/switch)
+    /// element.
+    switch <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<symbol>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/symbol)
+    /// element.
+    symbol <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<text>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/text)
+    /// element.
+    text <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<textPath>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/textPath)
+    /// element.
+    textPath <> "http://www.w3.org/2000/svg" {};
+
+    // /// Build a
+    // /// [`<title>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title)
+    // /// element.
+    // title <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<tspan>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/tspan)
+    /// element.
+    tspan <> "http://www.w3.org/2000/svg" {};
+
+    /// Build a
+    /// [`<view>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/view)
+    /// element.
+    view <> "http://www.w3.org/2000/svg" {};
+
+    // /// Build a
+    // /// [`<use>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use)
+    // /// element.
+    // use <> "http://www.w3.org/2000/svg" {};
+
+
+}

+ 88 - 0
packages/rsx_interperter/src/interperter.rs

@@ -0,0 +1,88 @@
+use dioxus_core::{Attribute, NodeFactory, VNode};
+use dioxus_rsx::{BodyNode, CallBody, ElementAttr, IfmtInput};
+use quote::ToTokens;
+use syn::parse2;
+
+use crate::attributes::attrbute_to_static_str;
+use crate::elements::element_to_static_str;
+
+pub fn build<'a>(rsx: CallBody, factory: &NodeFactory<'a>) -> VNode<'a> {
+    let children_built = factory.bump().alloc(Vec::new());
+    for (i, child) in rsx.roots.into_iter().enumerate() {
+        children_built.push(build_node(child, factory, i.to_string().as_str()));
+    }
+    factory.fragment_from_iter(children_built.iter())
+}
+
+fn build_node<'a>(node: BodyNode, factory: &NodeFactory<'a>, key: &str) -> Option<VNode<'a>> {
+    let bump = factory.bump();
+    match node {
+        BodyNode::Text(text) => {
+            let ifmt_input: IfmtInput = parse2(text.into_token_stream()).unwrap();
+            Some(factory.text(format_args!("{}", ifmt_input.format_literal.value())))
+        }
+        BodyNode::Element(el) => {
+            let attributes: &mut Vec<Attribute> = bump.alloc(Vec::new());
+            for attr in el.attributes {
+                let result: Option<(String, IfmtInput)> = match attr.attr {
+                    ElementAttr::AttrText { name, value } => {
+                        Some((name.to_string(), parse2(value.into_token_stream()).unwrap()))
+                    }
+
+                    ElementAttr::AttrExpression { name, value } => {
+                        Some((name.to_string(), parse2(value.into_token_stream()).unwrap()))
+                    }
+
+                    ElementAttr::CustomAttrText { name, value } => {
+                        Some((name.value(), parse2(value.into_token_stream()).unwrap()))
+                    }
+
+                    ElementAttr::CustomAttrExpression { name, value } => {
+                        Some((name.value(), parse2(value.into_token_stream()).unwrap()))
+                    }
+
+                    ElementAttr::EventTokens { .. } => None,
+
+                    ElementAttr::Meta(_) => None,
+                };
+                if let Some((name, value)) = result {
+                    if let Some((name, namespace)) = attrbute_to_static_str(&name) {
+                        let value = bump.alloc(value.format_literal.value());
+                        attributes.push(Attribute {
+                            name,
+                            value,
+                            is_static: true,
+                            is_volatile: false,
+                            namespace,
+                        })
+                    } else {
+                        return None;
+                    }
+                }
+            }
+            let children = bump.alloc(Vec::new());
+            for (i, child) in el.children.into_iter().enumerate() {
+                let node = build_node(child, factory, i.to_string().as_str());
+                if let Some(node) = node {
+                    children.push(node);
+                }
+            }
+            let tag = bump.alloc(el.name.to_string());
+            if let Some((tag, ns)) = element_to_static_str(tag) {
+                Some(factory.raw_element(
+                    tag,
+                    ns,
+                    &[],
+                    attributes.as_slice(),
+                    children.as_slice(),
+                    Some(format_args!("{}", key)),
+                ))
+            } else {
+                None
+            }
+        }
+        BodyNode::Component(_) => todo!(),
+        BodyNode::RawExpr(_) => todo!(),
+        BodyNode::Meta(_) => todo!(),
+    }
+}

+ 13 - 0
packages/rsx_interperter/src/lib.rs

@@ -0,0 +1,13 @@
+use crate::interperter::build;
+use dioxus_core::LazyNodes;
+use dioxus_rsx::CallBody;
+use syn::{parse_str, Result};
+
+mod attributes;
+pub mod captuered_context;
+mod elements;
+mod interperter;
+
+pub fn rsx_to_html(text: &str, context: &captuered_context::CapturedContext) ->  {
+    panic!()
+}

+ 2 - 0
src/lib.rs

@@ -39,6 +39,8 @@ pub mod events {
     pub use dioxus_html::{on::*, KeyCode};
 }
 
+pub use dioxus_rsx as rsx;
+
 pub mod prelude {
     pub use crate::hooks::*;
     pub use dioxus_core::prelude::*;