Browse Source

Merge pull request #425 from Demonthos/jk/rsx-refactor

Hot Reloading Rsx
Jon Kelley 3 years ago
parent
commit
478fea58d9

+ 9 - 3
Cargo.toml

@@ -9,7 +9,7 @@ repository = "https://github.com/DioxusLabs/dioxus/"
 homepage = "https://dioxuslabs.com"
 documentation = "https://dioxuslabs.com"
 keywords = ["dom", "ui", "gui", "react", "wasm"]
-rust-version = "1.56.0"
+rust-version = "1.60.0"
 
 [dependencies]
 dioxus-core = { path = "./packages/core", version = "^0.2.1" }
@@ -26,6 +26,8 @@ dioxus-router = { path = "./packages/router", version = "^0.2.3", optional = tru
 dioxus-interpreter-js = { path = "./packages/interpreter", version = "^0.2.1", optional = true }
 dioxus-tui = { path = "./packages/tui", version = "^0.2.2", optional = true }
 
+dioxus-rsx = { path = "./packages/rsx", optional = true }
+dioxus-rsx-interpreter = { path = "./packages/rsx_interpreter", optional = true }
 dioxus-liveview = { path = "./packages/liveview", version = "^0.1.0", optional = true }
 
 dioxus-native-core = { path = "./packages/native-core", version = "^0.2.0", optional = true }
@@ -38,15 +40,16 @@ dioxus-native-core-macro = { path = "./packages/native-core-macro", version = "^
 [features]
 default = ["macro", "hooks", "html"]
 
-macro = ["dioxus-core-macro"]
+macro = ["dioxus-core-macro", "dioxus-rsx"]
 hooks = ["dioxus-hooks"]
 html = ["dioxus-html"]
 ssr = ["dioxus-ssr"]
-web = ["dioxus-web", "dioxus-router/web"]
+web = ["dioxus-web", "dioxus-router?/web"]
 desktop = ["dioxus-desktop"]
 router = ["dioxus-router"]
 tui = ["dioxus-tui"]
 liveview = ["dioxus-liveview"]
+hot_reload = ["dioxus-core-macro/hot_reload", "dioxus-rsx-interpreter", "dioxus-desktop?/hot_reload", "dioxus-web?/hot_reload"]
 native-core = ["dioxus-native-core", "dioxus-native-core-macro"]
 
 
@@ -64,6 +67,8 @@ members = [
     "packages/fermi",
     "packages/tui",
     "packages/liveview",
+    "packages/rsx",
+    "packages/rsx_interpreter",
     "packages/native-core",
     "packages/native-core-macro",
 ]
@@ -86,6 +91,7 @@ criterion = "0.3.5"
 thiserror = "1.0.30"
 env_logger = "0.9.0"
 
+
 [[bench]]
 name = "create"
 harness = false

+ 1 - 0
docs/reference/src/SUMMARY.md

@@ -21,6 +21,7 @@
   - [Custom Renderer](guide/custom_renderer.md)
   - [Server-side components](guide/server_side_components.md)
   - [Bundling and Distributing](guide/bundline.md)
+  - [Hot Reloading Rsx](guide/hot_reloading.md)
 
 - [Reference Guide](reference/reference.md)
   - [Anti-patterns](reference/anti.md)

+ 24 - 0
docs/reference/src/guide/hot_reloading.md

@@ -0,0 +1,24 @@
+# Hot Reloading
+1. Hot reloading allows much faster iteration times inside of rsx calls by interperting them and streaming the edits.
+2. It is useful when changing the styling/layout of a program, but will not help with changing the logic of a program.
+3. Currently the cli only implements hot reloading for the web renderer.
+
+# Setup
+Install [dioxus-cli](https://github.com/DioxusLabs/cli).
+Enable the hot_reload feature on dioxus:
+```toml
+dioxus = { version = "*", features = ["web", "hot_reload"] }
+```
+
+# Usage
+1. run:
+```
+dioxus serve --hot-reload
+```
+2. change some code within a rsx macro
+3. open your localhost in a browser
+4. save and watch the style change without recompiling
+
+# Limitations
+1. The interperter can only use expressions that existed on the last full recompile. If you introduce a new variable or expression to the rsx call, it will trigger a full recompile to capture the expression.
+2. Components and Iterators can contain abritary rust code, and will trigger a full recompile when changed.

+ 8 - 3
packages/core-macro/Cargo.toml

@@ -15,12 +15,17 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 proc-macro = true
 
 [dependencies]
-proc-macro-error = "1"
-proc-macro2 = { version = "1.0.6" }
+proc-macro2 = { version = "1.0" }
 quote = "1.0"
-syn = { version = "1.0.11", features = ["full", "extra-traits"] }
+syn = { version = "1.0", features = ["full", "extra-traits"] }
+dioxus-rsx = {  path = "../rsx" }
+dioxus-rsx-interpreter = { path = "../rsx_interpreter", optional = true }
 
 # testing
 [dev-dependencies]
 rustversion = "1.0"
 trybuild = "1.0"
+
+[features]
+default = []
+hot_reload = ["dioxus-rsx-interpreter"]

+ 0 - 228
packages/core-macro/src/ifmt.rs

@@ -1,228 +0,0 @@
-use ::quote::{quote, ToTokens};
-use ::std::ops::Not;
-use ::syn::{
-    parse::{Parse, ParseStream},
-    punctuated::Punctuated,
-    *,
-};
-use proc_macro2::TokenStream;
-
-pub fn format_args_f_impl(input: IfmtInput) -> Result<TokenStream> {
-    let IfmtInput {
-        mut format_literal,
-        mut positional_args,
-        mut named_args,
-    } = input;
-
-    let s = format_literal.value();
-    let out_format_literal = &mut String::with_capacity(s.len());
-
-    let mut iterator = s.char_indices().peekable();
-    while let Some((i, c)) = iterator.next() {
-        out_format_literal.push(c);
-        if c != '{' {
-            continue;
-        }
-        // encountered `{`, let's see if it was `{{`
-        if let Some(&(_, '{')) = iterator.peek() {
-            let _ = iterator.next();
-            out_format_literal.push('{');
-            continue;
-        }
-        let (end, colon_or_closing_brace) =
-            iterator
-                .find(|&(_, c)| c == '}' || c == ':')
-                .expect(concat!(
-                    "Invalid format string literal\n",
-                    "note: if you intended to print `{`, ",
-                    "you can escape it using `{{`",
-                ));
-        // We use defer to ensure all the `continue`s append the closing char.
-        let mut out_format_literal = defer(&mut *out_format_literal, |it| {
-            it.push(colon_or_closing_brace)
-        });
-        let out_format_literal: &mut String = *out_format_literal;
-        let mut arg = s[i + 1..end].trim();
-        if let Some("=") = arg.get(arg.len().saturating_sub(1)..) {
-            assert_eq!(
-                out_format_literal.pop(), // Remove the opening brace
-                Some('{'),
-            );
-            arg = &arg[..arg.len() - 1];
-            out_format_literal.push_str(arg);
-            out_format_literal.push_str(" = {");
-        }
-        if arg.is_empty() {
-            continue;
-        }
-
-        enum Segment {
-            Ident(Ident),
-            LitInt(LitInt),
-        }
-        let segments: Vec<Segment> = {
-            impl Parse for Segment {
-                fn parse(input: ParseStream<'_>) -> Result<Self> {
-                    let lookahead = input.lookahead1();
-                    if lookahead.peek(Ident) {
-                        input.parse().map(Segment::Ident)
-                    } else if lookahead.peek(LitInt) {
-                        input.parse().map(Segment::LitInt)
-                    } else {
-                        Err(lookahead.error())
-                    }
-                }
-            }
-            match ::syn::parse::Parser::parse_str(
-                Punctuated::<Segment, Token![.]>::parse_separated_nonempty,
-                arg,
-            ) {
-                Ok(segments) => segments.into_iter().collect(),
-                Err(err) => return Err(err),
-            }
-        };
-        match segments.len() {
-            0 => unreachable!("`parse_separated_nonempty` returned empty"),
-            1 => {
-                out_format_literal.push_str(arg);
-                match { segments }.pop().unwrap() {
-                    Segment::LitInt(_) => {
-                        // found something like `{0}`, let `format_args!`
-                        // handle it.
-                        continue;
-                    }
-                    Segment::Ident(ident) => {
-                        // if `ident = ...` is not yet among the extra args
-                        if named_args.iter().all(|(it, _)| *it != ident) {
-                            named_args.push((
-                                ident.clone(),
-                                parse_quote!(#ident), // Expr
-                            ));
-                        }
-                    }
-                }
-            }
-            _ => {
-                ::std::fmt::Write::write_fmt(
-                    out_format_literal,
-                    format_args!("{}", positional_args.len()),
-                )
-                .expect("`usize` or `char` Display impl cannot panic");
-                let segments: Punctuated<TokenStream, Token![.]> = segments
-                    .into_iter()
-                    .map(|it| match it {
-                        Segment::Ident(ident) => ident.into_token_stream(),
-                        Segment::LitInt(literal) => literal.into_token_stream(),
-                    })
-                    .collect();
-                positional_args.push(parse_quote! {
-                    #segments
-                })
-            }
-        }
-    }
-
-    let named_args = named_args.into_iter().map(|(ident, expr)| {
-        quote! {
-            #ident = #expr
-        }
-    });
-    format_literal = LitStr::new(out_format_literal, format_literal.span());
-
-    Ok(quote! {
-        format_args!(
-            #format_literal
-            #(, #positional_args)*
-            #(, #named_args)*
-        )
-    })
-}
-
-#[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)>,
-}
-
-impl Parse for IfmtInput {
-    fn parse(input: ParseStream) -> Result<Self> {
-        let format_literal = input.parse()?;
-        let mut positional_args = vec![];
-        loop {
-            if input.parse::<Option<Token![,]>>()?.is_none() {
-                return Ok(Self {
-                    format_literal,
-                    positional_args,
-                    named_args: vec![],
-                });
-            }
-            if input.peek(Ident) && input.peek2(Token![=]) && input.peek3(Token![=]).not() {
-                // Found a positional parameter
-                break;
-            }
-            positional_args.push(input.parse()?);
-        }
-        let named_args = Punctuated::<_, Token![,]>::parse_terminated_with(input, |input| {
-            Ok({
-                let name: Ident = input.parse()?;
-                let _: Token![=] = input.parse()?;
-                let expr: Expr = input.parse()?;
-                (name, expr)
-            })
-        })?
-        .into_iter()
-        .collect();
-        Ok(Self {
-            format_literal,
-            positional_args,
-            named_args,
-        })
-    }
-}
-
-pub fn defer<'a, T: 'a, Drop: 'a>(x: T, drop: Drop) -> impl ::core::ops::DerefMut<Target = T> + 'a
-where
-    Drop: FnOnce(T),
-{
-    use ::core::mem::ManuallyDrop;
-    struct Ret<T, Drop>(ManuallyDrop<T>, ManuallyDrop<Drop>)
-    where
-        Drop: FnOnce(T);
-    impl<T, Drop> ::core::ops::Drop for Ret<T, Drop>
-    where
-        Drop: FnOnce(T),
-    {
-        fn drop(&'_ mut self) {
-            use ::core::ptr;
-            unsafe {
-                // # Safety
-                //
-                //   - This is the canonical example of using `ManuallyDrop`.
-                let value = ManuallyDrop::into_inner(ptr::read(&self.0));
-                let drop = ManuallyDrop::into_inner(ptr::read(&self.1));
-                drop(value);
-            }
-        }
-    }
-    impl<T, Drop> ::core::ops::Deref for Ret<T, Drop>
-    where
-        Drop: FnOnce(T),
-    {
-        type Target = T;
-        #[inline]
-        fn deref(&'_ self) -> &'_ Self::Target {
-            &self.0
-        }
-    }
-    impl<T, Drop> ::core::ops::DerefMut for Ret<T, Drop>
-    where
-        Drop: FnOnce(T),
-    {
-        #[inline]
-        fn deref_mut(&'_ mut self) -> &'_ mut Self::Target {
-            &mut self.0
-        }
-    }
-    Ret(ManuallyDrop::new(x), ManuallyDrop::new(drop))
-}

+ 37 - 5
packages/core-macro/src/lib.rs

@@ -2,14 +2,15 @@ use proc_macro::TokenStream;
 use quote::ToTokens;
 use syn::parse_macro_input;
 
-mod ifmt;
 mod inlineprops;
 mod props;
-mod rsx;
+
+// mod rsx;
+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())
@@ -31,12 +32,43 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
 /// ```
 #[doc = include_str!("../../../examples/rsx_usage.rs")]
 /// ```
-#[proc_macro_error::proc_macro_error]
 #[proc_macro]
 pub fn rsx(s: TokenStream) -> TokenStream {
+    #[cfg(feature = "hot_reload")]
+    let rsx_text = s.to_string();
     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_interpreter::captuered_context::CapturedContextBuilder;
+
+                match CapturedContextBuilder::from_call_body(body) {
+                    Ok(captured) => {
+                        let lazy = quote::quote! {
+                            LazyNodes::new(move |__cx|{
+                                let code_location = get_line_num!();
+                                let captured = #captured;
+                                let text = #rsx_text;
+
+                                resolve_scope(code_location, text, captured, __cx)
+                            })
+                        };
+                        if let Some(cx) = captured.custom_context {
+                            quote::quote! {
+                                #cx.render(#lazy)
+                            }
+                            .into()
+                        } else {
+                            lazy.into()
+                        }
+                    }
+                    Err(err) => err.into_compile_error().into(),
+                }
+            }
+            #[cfg(not(feature = "hot_reload"))]
+            body.to_token_stream().into()
+        }
     }
 }
 

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

@@ -1,31 +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"
-        )
-    };
-}
-
-macro_rules! component_path_cannot_have_arguments {
-    ($span:expr) => {
-        proc_macro_error::abort!(
-            $span,
-            "expected a path without arguments";
-            help = "try remove the path arguments"
-        )
-    };
-}
-
-macro_rules! invalid_component_path {
-    ($span:expr) => {
-        proc_macro_error::abort!($span, "Invalid component path syntax")
-    };
-}

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

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

+ 1 - 2
packages/core/Cargo.toml

@@ -48,5 +48,4 @@ backtrace = "0.3"
 [features]
 default = []
 serialize = ["serde"]
-debug_vdom = []
-
+debug_vdom = []

+ 8 - 0
packages/core/src/virtual_dom.rs

@@ -125,6 +125,9 @@ pub enum SchedulerMsg {
     /// Immediate updates from Components that mark them as dirty
     Immediate(ScopeId),
 
+    /// Mark all components as dirty and update them
+    DirtyAll,
+
     /// New tasks from components that should be polled when the next poll is ready
     NewTask(ScopeId),
 }
@@ -390,6 +393,11 @@ impl VirtualDom {
             SchedulerMsg::Immediate(s) => {
                 self.dirty_scopes.insert(s);
             }
+            SchedulerMsg::DirtyAll => {
+                for id in self.scopes.scopes.borrow().keys() {
+                    self.dirty_scopes.insert(*id);
+                }
+            }
         }
     }
 

+ 4 - 1
packages/desktop/Cargo.toml

@@ -15,6 +15,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 dioxus-core = { path = "../core", version = "^0.2.1", features = ["serialize"] }
 dioxus-html = { path = "../html", features = ["serialize"], version = "^0.2.1" }
 dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1" }
+dioxus-rsx-interpreter = { path = "../rsx_interpreter", optional = true }
 
 serde = "1.0.136"
 serde_json = "1.0.79"
@@ -32,6 +33,8 @@ webbrowser = "0.7.1"
 mime_guess = "2.0.3"
 dunce = "1.0.2"
 
+interprocess = { version = "1.1.1", optional = true }
+
 [target.'cfg(target_os = "macos")'.dependencies]
 core-foundation = "0.9.3"
 
@@ -41,7 +44,7 @@ tokio_runtime = ["tokio"]
 fullscreen = ["wry/fullscreen"]
 transparent = ["wry/transparent"]
 tray = ["wry/tray"]
-
+hot_reload = ["dioxus-rsx-interpreter", "interprocess"]
 
 [dev-dependencies]
 dioxus-core-macro = { path = "../core-macro" }

+ 88 - 0
packages/desktop/src/controller.rs

@@ -1,4 +1,5 @@
 use crate::desktop_context::{DesktopContext, UserWindowEvent};
+
 use dioxus_core::*;
 use std::{
     collections::HashMap,
@@ -49,6 +50,93 @@ impl DesktopController {
 
                 dom.base_scope().provide_context(window_context);
 
+                // allow other proccesses to send the new rsx text to the @dioxusin ipc channel and recieve erros on the @dioxusout channel
+                #[cfg(feature = "hot_reload")]
+                {
+                    use dioxus_rsx_interpreter::{
+                        error::Error, ErrorHandler, SetManyRsxMessage, RSX_CONTEXT,
+                    };
+                    use interprocess::local_socket::{LocalSocketListener, LocalSocketStream};
+                    use std::io::{BufRead, BufReader, Write};
+                    use std::time::Duration;
+
+                    fn handle_error(
+                        connection: std::io::Result<LocalSocketStream>,
+                    ) -> Option<LocalSocketStream> {
+                        connection
+                            .map_err(|error| eprintln!("Incoming connection failed: {}", error))
+                            .ok()
+                    }
+
+                    let latest_in_connection: Arc<Mutex<Option<BufReader<LocalSocketStream>>>> =
+                        Arc::new(Mutex::new(None));
+                    let latest_in_connection_handle = latest_in_connection.clone();
+                    let latest_out_connection: Arc<Mutex<Option<BufReader<LocalSocketStream>>>> =
+                        Arc::new(Mutex::new(None));
+                    let latest_out_connection_handle = latest_out_connection.clone();
+
+                    struct DesktopErrorHandler {
+                        latest_connection: Arc<Mutex<Option<BufReader<LocalSocketStream>>>>,
+                    }
+                    impl ErrorHandler for DesktopErrorHandler {
+                        fn handle_error(&self, err: Error) {
+                            if let Some(conn) = &mut *self.latest_connection.lock().unwrap() {
+                                conn.get_mut()
+                                    .write_all(
+                                        (serde_json::to_string(&err).unwrap() + "\n").as_bytes(),
+                                    )
+                                    .unwrap();
+                            }
+                        }
+                    }
+
+                    RSX_CONTEXT.set_error_handler(DesktopErrorHandler {
+                        latest_connection: latest_out_connection_handle,
+                    });
+                    RSX_CONTEXT.provide_scheduler_channel(dom.get_scheduler_channel());
+
+                    // connect to processes for incoming data
+                    std::thread::spawn(move || {
+                        if let Ok(listener) = LocalSocketListener::bind("@dioxusin") {
+                            for conn in listener.incoming().filter_map(handle_error) {
+                                *latest_in_connection_handle.lock().unwrap() =
+                                    Some(BufReader::new(conn));
+                            }
+                        }
+                    });
+
+                    // connect to processes for outgoing errors
+                    std::thread::spawn(move || {
+                        if let Ok(listener) = LocalSocketListener::bind("@dioxusout") {
+                            for conn in listener.incoming().filter_map(handle_error) {
+                                *latest_out_connection.lock().unwrap() = Some(BufReader::new(conn));
+                            }
+                        }
+                    });
+
+                    std::thread::spawn(move || {
+                        loop {
+                            if let Some(conn) = &mut *latest_in_connection.lock().unwrap() {
+                                let mut buf = String::new();
+                                match conn.read_line(&mut buf) {
+                                    Ok(_) => {
+                                        let msgs: SetManyRsxMessage =
+                                            serde_json::from_str(&buf).unwrap();
+                                        RSX_CONTEXT.extend(msgs);
+                                    }
+                                    Err(err) => {
+                                        if err.kind() != std::io::ErrorKind::WouldBlock {
+                                            break;
+                                        }
+                                    }
+                                }
+                            }
+                            // give the error handler time to take the mutex
+                            std::thread::sleep(Duration::from_millis(100));
+                        }
+                    });
+                }
+
                 let edits = dom.rebuild();
 
                 edit_queue

+ 12 - 0
packages/rsx/Cargo.toml

@@ -0,0 +1,12 @@
+[package]
+name = "dioxus-rsx"
+version = "0.0.0"
+edition = "2018"
+license = "MIT/Apache-2.0"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+proc-macro2 = { version = "1.0" }
+syn = { version = "1.0", features = ["full", "extra-traits"] }
+quote = { version = "1.0" }

+ 11 - 6
packages/core-macro/src/rsx/component.rs → packages/rsx/src/component.rs

@@ -18,9 +18,12 @@ use quote::{quote, ToTokens, TokenStreamExt};
 use syn::{
     ext::IdentExt,
     parse::{Parse, ParseBuffer, ParseStream},
-    token, AngleBracketedGenericArguments, Expr, Ident, LitStr, PathArguments, Result, Token,
+    spanned::Spanned,
+    token, AngleBracketedGenericArguments, Error, Expr, Ident, LitStr, PathArguments, Result,
+    Token,
 };
 
+#[derive(PartialEq, Eq)]
 pub struct Component {
     pub name: syn::Path,
     pub prop_gen_args: Option<AngleBracketedGenericArguments>,
@@ -39,7 +42,7 @@ impl Component {
             .take(path.segments.len() - 1)
             .any(|seg| seg.arguments != PathArguments::None)
         {
-            component_path_cannot_have_arguments!(path);
+            component_path_cannot_have_arguments!(path.span());
         }
 
         // ensure last segment only have value of None or AngleBracketed
@@ -47,7 +50,7 @@ impl Component {
             path.segments.last().unwrap().arguments,
             PathArguments::None | PathArguments::AngleBracketed(_)
         ) {
-            invalid_component_path!(path);
+            invalid_component_path!(path.span());
         }
 
         Ok(())
@@ -190,12 +193,14 @@ impl ToTokens for Component {
 }
 
 // the struct's fields info
+#[derive(PartialEq, Eq)]
 pub struct ComponentField {
-    name: Ident,
-    content: ContentField,
+    pub name: Ident,
+    pub content: ContentField,
 }
 
-enum ContentField {
+#[derive(PartialEq, Eq)]
+pub enum ContentField {
     ManExpr(Expr),
     Formatted(LitStr),
     OnHandlerRaw(Expr),

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

@@ -4,12 +4,13 @@ use proc_macro2::TokenStream as TokenStream2;
 use quote::{quote, ToTokens, TokenStreamExt};
 use syn::{
     parse::{Parse, ParseBuffer, ParseStream},
-    Expr, Ident, LitStr, Result, Token,
+    Error, Expr, Ident, LitStr, Result, Token,
 };
 
 // =======================================
 // Parse the VNode::Element type
 // =======================================
+#[derive(PartialEq, Eq)]
 pub struct Element {
     pub name: Ident,
     pub key: Option<LitStr>,
@@ -64,7 +65,7 @@ impl Parse for Element {
                 }
 
                 if content.parse::<Token![,]>().is_err() {
-                    missing_trailing_comma!(ident);
+                    missing_trailing_comma!(ident.span());
                 }
                 continue;
             }
@@ -122,7 +123,7 @@ impl Parse for Element {
 
                 // todo: add a message saying you need to include commas between fields
                 if content.parse::<Token![,]>().is_err() {
-                    missing_trailing_comma!(ident);
+                    missing_trailing_comma!(ident.span());
                 }
                 continue;
             }
@@ -189,6 +190,7 @@ impl ToTokens for Element {
     }
 }
 
+#[derive(PartialEq, Eq)]
 pub enum ElementAttr {
     /// attribute: "valuee {}"
     AttrText { name: Ident, value: LitStr },
@@ -208,6 +210,7 @@ pub enum ElementAttr {
     EventTokens { name: Ident, tokens: Expr },
 }
 
+#[derive(PartialEq, Eq)]
 pub struct ElementAttrNamed {
     pub el_name: Ident,
     pub attr: ElementAttr,

+ 26 - 0
packages/rsx/src/errors.rs

@@ -0,0 +1,26 @@
+macro_rules! missing_trailing_comma {
+    ($span:expr) => {
+        return Err(Error::new($span, "missing trailing comma"));
+    };
+}
+
+macro_rules! attr_after_element {
+    ($span:expr) => {
+        return Err(Error::new($span, "expected element\n  = help move the attribute above all the children and text elements"));
+    };
+}
+
+macro_rules! component_path_cannot_have_arguments {
+    ($span:expr) => {
+        return Err(Error::new(
+            $span,
+            "expected a path without arguments\n  = try remove the path arguments",
+        ));
+    };
+}
+
+macro_rules! invalid_component_path {
+    ($span:expr) => {
+        return Err(Error::new($span, "Invalid component path syntax"));
+    };
+}

+ 176 - 0
packages/rsx/src/ifmt.rs

@@ -0,0 +1,176 @@
+use std::str::FromStr;
+
+use proc_macro2::{Span, TokenStream};
+
+use quote::{quote, ToTokens};
+use syn::{
+    parse::{Parse, ParseStream},
+    *,
+};
+
+pub fn format_args_f_impl(input: IfmtInput) -> Result<TokenStream> {
+    // build format_literal
+    let mut format_literal = String::new();
+    let mut expr_counter = 0;
+    for segment in input.segments.iter() {
+        match segment {
+            Segment::Literal(s) => format_literal += s,
+            Segment::Formatted {
+                format_args,
+                segment,
+            } => {
+                format_literal += "{";
+                match segment {
+                    FormattedSegment::Expr(_) => {
+                        format_literal += &expr_counter.to_string();
+                        expr_counter += 1;
+                    }
+                    FormattedSegment::Ident(ident) => {
+                        format_literal += &ident.to_string();
+                    }
+                }
+                format_literal += ":";
+                format_literal += format_args;
+                format_literal += "}";
+            }
+        }
+    }
+
+    let positional_args = input.segments.iter().filter_map(|seg| {
+        if let Segment::Formatted {
+            segment: FormattedSegment::Expr(expr),
+            ..
+        } = seg
+        {
+            Some(expr)
+        } else {
+            None
+        }
+    });
+
+    let named_args = input.segments.iter().filter_map(|seg| {
+        if let Segment::Formatted {
+            segment: FormattedSegment::Ident(ident),
+            ..
+        } = seg
+        {
+            Some(quote! {#ident = #ident})
+        } else {
+            None
+        }
+    });
+
+    Ok(quote! {
+        format_args!(
+            #format_literal
+            #(, #positional_args)*
+            #(, #named_args)*
+        )
+    })
+}
+
+#[allow(dead_code)] // dumb compiler does not see the struct being used...
+#[derive(Debug)]
+pub struct IfmtInput {
+    pub segments: Vec<Segment>,
+}
+
+impl FromStr for IfmtInput {
+    type Err = syn::Error;
+
+    fn from_str(input: &str) -> Result<Self> {
+        let mut chars = input.chars().peekable();
+        let mut segments = Vec::new();
+        let mut current_literal = String::new();
+        while let Some(c) = chars.next() {
+            if c == '{' {
+                if let Some(c) = chars.next_if(|c| *c == '{') {
+                    current_literal.push(c);
+                    continue;
+                }
+                segments.push(Segment::Literal(current_literal));
+                current_literal = String::new();
+                let mut current_captured = String::new();
+                while let Some(c) = chars.next() {
+                    if c == ':' {
+                        let mut current_format_args = String::new();
+                        for c in chars.by_ref() {
+                            if c == '}' {
+                                segments.push(Segment::Formatted {
+                                    format_args: current_format_args,
+                                    segment: FormattedSegment::parse(&current_captured)?,
+                                });
+                                break;
+                            }
+                            current_format_args.push(c);
+                        }
+                        break;
+                    }
+                    if c == '}' {
+                        segments.push(Segment::Formatted {
+                            format_args: String::new(),
+                            segment: FormattedSegment::parse(&current_captured)?,
+                        });
+                        break;
+                    }
+                    current_captured.push(c);
+                }
+            } else {
+                current_literal.push(c);
+            }
+        }
+        segments.push(Segment::Literal(current_literal));
+        Ok(Self { segments })
+    }
+}
+
+#[derive(Debug)]
+pub enum Segment {
+    Literal(String),
+    Formatted {
+        format_args: String,
+        segment: FormattedSegment,
+    },
+}
+
+#[derive(Debug)]
+pub enum FormattedSegment {
+    Expr(Box<Expr>),
+    Ident(Ident),
+}
+
+impl FormattedSegment {
+    fn parse(input: &str) -> Result<Self> {
+        if let Ok(ident) = parse_str::<Ident>(input) {
+            if ident == input {
+                return Ok(Self::Ident(ident));
+            }
+        }
+        // if let Ok(expr) = parse_str(&("{".to_string() + input + "}")) {
+        if let Ok(expr) = parse_str(input) {
+            Ok(Self::Expr(Box::new(expr)))
+        } else {
+            Err(Error::new(
+                Span::call_site(),
+                "Expected Ident or Expression",
+            ))
+        }
+    }
+}
+
+impl ToTokens for FormattedSegment {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        match self {
+            Self::Expr(expr) => expr.to_tokens(tokens),
+            Self::Ident(ident) => ident.to_tokens(tokens),
+        }
+    }
+}
+
+impl Parse for IfmtInput {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let input: LitStr = input.parse()?;
+        let input_str = input.value();
+        IfmtInput::from_str(&input_str)
+    }
+}

+ 2 - 2
packages/core-macro/src/rsx/mod.rs → packages/rsx/src/lib.rs

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

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

@@ -15,6 +15,7 @@ Parse
 -> "text {with_args}"
 -> (0..10).map(|f| rsx!("asd")),  // <--- notice the comma - must be a complete expr
 */
+#[derive(PartialEq, Eq)]
 pub enum BodyNode {
     Element(Element),
     Component(Component),
@@ -22,6 +23,12 @@ pub enum BodyNode {
     RawExpr(Expr),
 }
 
+impl BodyNode {
+    pub fn is_litstr(&self) -> bool {
+        matches!(self, BodyNode::Text(_))
+    }
+}
+
 impl Parse for BodyNode {
     fn parse(stream: ParseStream) -> Result<Self> {
         if stream.peek(LitStr) {

+ 23 - 0
packages/rsx_interpreter/Cargo.toml

@@ -0,0 +1,23 @@
+[package]
+name = "dioxus-rsx-interpreter"
+version = "0.1.0"
+edition = "2021"
+license = "MIT/Apache-2.0"
+
+[dependencies]
+syn = { version = "1.0", features = ["extra-traits"] }
+proc-macro2 = { version = "1.0.39", features = ["span-locations"] }
+quote = "1.0"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+lazy_static = "1.4.0"
+
+dioxus-rsx = { path = "../rsx", default-features = false }
+dioxus-ssr = { path = "../ssr" }
+dioxus-core = { path = "../core" }
+dioxus-html = { path = "../html" }
+dioxus-hooks = { path = "../hooks"}
+
+[dev-dependencies]
+dioxus-core-macro = { path = "../core-macro" }
+bumpalo = { version = "3.6", features = ["collections", "boxed"] }

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

@@ -0,0 +1,869 @@
+use crate::elements::*;
+
+// map the rsx name of the attribute to the html name of the attribute and the namespace that contains it
+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",
+    }
+}

+ 176 - 0
packages/rsx_interpreter/src/captuered_context.rs

@@ -0,0 +1,176 @@
+use dioxus_core::{Listener, VNode};
+use dioxus_rsx::{
+    BodyNode, CallBody, Component, ElementAttr, ElementAttrNamed, IfmtInput, Segment,
+};
+use quote::{quote, ToTokens, TokenStreamExt};
+use syn::{Expr, Ident, Result};
+
+use crate::CodeLocation;
+#[derive(Default)]
+pub struct CapturedContextBuilder {
+    pub ifmted: Vec<IfmtInput>,
+    pub components: Vec<Component>,
+    pub iterators: Vec<BodyNode>,
+    pub captured_expressions: Vec<Expr>,
+    pub listeners: Vec<ElementAttrNamed>,
+    pub custom_context: Option<Ident>,
+}
+
+impl CapturedContextBuilder {
+    pub fn extend(&mut self, other: CapturedContextBuilder) {
+        self.ifmted.extend(other.ifmted);
+        self.components.extend(other.components);
+        self.iterators.extend(other.iterators);
+        self.listeners.extend(other.listeners);
+        self.captured_expressions.extend(other.captured_expressions);
+    }
+
+    pub fn from_call_body(body: CallBody) -> Result<Self> {
+        let mut new = Self {
+            custom_context: body.custom_context,
+            ..Default::default()
+        };
+        for node in body.roots {
+            new.extend(Self::find_captured(node)?);
+        }
+        Ok(new)
+    }
+
+    fn find_captured(node: BodyNode) -> Result<Self> {
+        let mut captured = CapturedContextBuilder::default();
+        match node {
+            BodyNode::Element(el) => {
+                for attr in el.attributes {
+                    match attr.attr {
+                        ElementAttr::AttrText { value, .. }
+                        | ElementAttr::CustomAttrText { value, .. } => {
+                            let value_tokens = value.to_token_stream();
+                            let formated: IfmtInput = syn::parse2(value_tokens)?;
+                            captured.ifmted.push(formated);
+                        }
+                        ElementAttr::AttrExpression { name: _, value } => {
+                            captured.captured_expressions.push(value);
+                        }
+                        ElementAttr::CustomAttrExpression { name: _, value } => {
+                            captured.captured_expressions.push(value);
+                        }
+                        ElementAttr::EventTokens { .. } => captured.listeners.push(attr),
+                    }
+                }
+
+                if let Some(key) = el.key {
+                    let value_tokens = key.to_token_stream();
+                    let formated: IfmtInput = syn::parse2(value_tokens)?;
+                    captured.ifmted.push(formated);
+                }
+
+                for child in el.children {
+                    captured.extend(Self::find_captured(child)?);
+                }
+            }
+            BodyNode::Component(comp) => {
+                captured.components.push(comp);
+            }
+            BodyNode::Text(t) => {
+                let tokens = t.to_token_stream();
+                let formated: IfmtInput = syn::parse2(tokens).unwrap();
+                captured.ifmted.push(formated);
+            }
+            BodyNode::RawExpr(_) => captured.iterators.push(node),
+        }
+        Ok(captured)
+    }
+}
+
+impl ToTokens for CapturedContextBuilder {
+    fn to_tokens(&self, tokens: &mut quote::__private::TokenStream) {
+        let CapturedContextBuilder {
+            ifmted,
+            components,
+            iterators,
+            captured_expressions,
+            listeners,
+            custom_context: _,
+        } = self;
+        let listeners_str = listeners
+            .iter()
+            .map(|comp| comp.to_token_stream().to_string());
+        let compontents_str = components
+            .iter()
+            .map(|comp| comp.to_token_stream().to_string());
+        let iterators_str = iterators.iter().map(|node| match node {
+            BodyNode::RawExpr(expr) => expr.to_token_stream().to_string(),
+            _ => unreachable!(),
+        });
+        let captured: Vec<_> = ifmted
+            .iter()
+            .flat_map(|input| input.segments.iter())
+            .filter_map(|seg| match seg {
+                Segment::Formatted {
+                    format_args,
+                    segment,
+                } => {
+                    let expr = segment.to_token_stream();
+                    let as_string = expr.to_string();
+                    let format_expr = if format_args.is_empty() {
+                        "{".to_string() + format_args + "}"
+                    } else {
+                        "{".to_string() + ":" + format_args + "}"
+                    };
+                    Some(quote! {
+                        FormattedArg{
+                            expr: #as_string,
+                            format_args: #format_args,
+                            result: format!(#format_expr, #expr)
+                        }
+                    })
+                }
+                _ => None,
+            })
+            .collect();
+        let captured_attr_expressions_text = captured_expressions
+            .iter()
+            .map(|e| format!("{}", e.to_token_stream()));
+        tokens.append_all(quote! {
+            CapturedContext {
+                captured: IfmtArgs{
+                    named_args: vec![#(#captured),*]
+                },
+                components: vec![#((#compontents_str, #components)),*],
+                iterators: vec![#((#iterators_str, #iterators)),*],
+                expressions: vec![#((#captured_attr_expressions_text, #captured_expressions.to_string())),*],
+                listeners: vec![#((#listeners_str, #listeners)),*],
+                location: code_location.clone()
+            }
+        })
+    }
+}
+
+pub struct CapturedContext<'a> {
+    // map of the variable name to the formated value
+    pub captured: IfmtArgs,
+    // map of the attribute name and element path to the formated value
+    // pub captured_attribute_values: IfmtArgs,
+    // the only thing we can update in component is the children
+    pub components: Vec<(&'static str, VNode<'a>)>,
+    // we can't reasonably interpert iterators, so they are staticly inserted
+    pub iterators: Vec<(&'static str, VNode<'a>)>,
+    // map expression to the value resulting from the expression
+    pub expressions: Vec<(&'static str, String)>,
+    // map listener code to the resulting listener
+    pub listeners: Vec<(&'static str, Listener<'a>)>,
+    // used to provide better error messages
+    pub location: CodeLocation,
+}
+
+pub struct IfmtArgs {
+    // All expressions that have been resolved
+    pub named_args: Vec<FormattedArg>,
+}
+
+/// A formated segment that has been resolved
+pub struct FormattedArg {
+    pub expr: &'static str,
+    pub format_args: &'static str,
+    pub result: String,
+}

+ 1377 - 0
packages/rsx_interpreter/src/elements.rs

@@ -0,0 +1,1377 @@
+// map the rsx name of the element to the html name of the element and the namespace that contains it
+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: &[(&str, &[(&str, &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" {};
+
+
+}

+ 39 - 0
packages/rsx_interpreter/src/error.rs

@@ -0,0 +1,39 @@
+use serde::{Deserialize, Serialize};
+
+use crate::CodeLocation;
+
+/// An error produced when interperting the rsx
+#[derive(Debug, Serialize, Deserialize)]
+pub enum Error {
+    ParseError(ParseError),
+    RecompileRequiredError(RecompileReason),
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub enum RecompileReason {
+    CapturedVariable(String),
+    CapturedExpression(String),
+    CapturedComponent(String),
+    CapturedListener(String),
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct ParseError {
+    pub message: String,
+    pub location: CodeLocation,
+}
+
+impl ParseError {
+    pub fn new(error: syn::Error, mut location: CodeLocation) -> Self {
+        let message = error.to_string();
+        let syn_call_site = error.span().start();
+        location.line += syn_call_site.line as u32;
+        if syn_call_site.line == 0 {
+            location.column += syn_call_site.column as u32;
+        } else {
+            location.column = syn_call_site.column as u32;
+        }
+        location.column += 1;
+        ParseError { message, location }
+    }
+}

+ 251 - 0
packages/rsx_interpreter/src/interperter.rs

@@ -0,0 +1,251 @@
+use dioxus_core::{Attribute, AttributeValue, NodeFactory, VNode};
+use dioxus_rsx::{BodyNode, CallBody, ElementAttr, IfmtInput, Segment};
+use quote::ToTokens;
+use quote::__private::Span;
+use std::str::FromStr;
+use syn::{parse2, parse_str, Expr};
+
+use crate::attributes::attrbute_to_static_str;
+use crate::captuered_context::{CapturedContext, IfmtArgs};
+use crate::elements::element_to_static_str;
+use crate::error::{Error, ParseError, RecompileReason};
+
+fn resolve_ifmt(ifmt: &IfmtInput, captured: &IfmtArgs) -> Result<String, Error> {
+    let mut result = String::new();
+    for seg in &ifmt.segments {
+        match seg {
+            Segment::Formatted {
+                segment,
+                format_args,
+            } => {
+                let expr = segment.to_token_stream();
+                let expr: Expr = parse2(expr).unwrap();
+                let search = captured.named_args.iter().find(|fmted| {
+                    parse_str::<Expr>(fmted.expr).unwrap() == expr
+                        && fmted.format_args == format_args
+                });
+                match search {
+                    Some(formatted) => {
+                        result.push_str(&formatted.result);
+                    }
+                    None => {
+                        let expr_str = segment.to_token_stream().to_string();
+                        return Err(Error::RecompileRequiredError(
+                            RecompileReason::CapturedExpression(format!(
+                                "could not resolve {{{}:{}}}",
+                                expr_str, format_args
+                            )),
+                        ));
+                    }
+                }
+            }
+            Segment::Literal(lit) => result.push_str(lit),
+        }
+    }
+    Ok(result)
+}
+
+pub fn build<'a>(
+    rsx: CallBody,
+    mut ctx: CapturedContext<'a>,
+    factory: &NodeFactory<'a>,
+) -> Result<VNode<'a>, Error> {
+    let children_built = factory.bump().alloc(Vec::new());
+    for child in rsx.roots {
+        children_built.push(build_node(child, &mut ctx, factory)?);
+    }
+
+    if children_built.len() == 1 {
+        Ok(children_built.pop().unwrap())
+    } else {
+        Ok(factory.fragment_from_iter(children_built.iter()))
+    }
+}
+
+fn build_node<'a>(
+    node: BodyNode,
+    ctx: &mut CapturedContext<'a>,
+    factory: &NodeFactory<'a>,
+) -> Result<VNode<'a>, Error> {
+    let bump = factory.bump();
+    match node {
+        BodyNode::Text(text) => {
+            let ifmt = IfmtInput::from_str(&text.value())
+                .map_err(|err| Error::ParseError(ParseError::new(err, ctx.location.clone())))?;
+            let text = bump.alloc(resolve_ifmt(&ifmt, &ctx.captured)?);
+            Ok(factory.text(format_args!("{}", text)))
+        }
+        BodyNode::Element(el) => {
+            let attributes: &mut Vec<Attribute> = bump.alloc(Vec::new());
+            for attr in &el.attributes {
+                match &attr.attr {
+                    ElementAttr::AttrText { .. } | ElementAttr::CustomAttrText { .. } => {
+                        let (name, value, span): (String, IfmtInput, Span) = match &attr.attr {
+                            ElementAttr::AttrText { name, value } => (
+                                name.to_string(),
+                                IfmtInput::from_str(&value.value()).map_err(|err| {
+                                    Error::ParseError(ParseError::new(err, ctx.location.clone()))
+                                })?,
+                                name.span(),
+                            ),
+                            ElementAttr::CustomAttrText { name, value } => (
+                                name.value(),
+                                IfmtInput::from_str(&value.value()).map_err(|err| {
+                                    Error::ParseError(ParseError::new(err, ctx.location.clone()))
+                                })?,
+                                name.span(),
+                            ),
+                            _ => unreachable!(),
+                        };
+
+                        if let Some((name, namespace)) = attrbute_to_static_str(&name) {
+                            let value = bump.alloc(resolve_ifmt(&value, &ctx.captured)?);
+                            attributes.push(Attribute {
+                                name,
+                                value: AttributeValue::Text(value),
+                                is_static: true,
+                                is_volatile: false,
+                                namespace,
+                            });
+                        } else {
+                            return Err(Error::ParseError(ParseError::new(
+                                syn::Error::new(span, format!("unknown attribute: {}", name)),
+                                ctx.location.clone(),
+                            )));
+                        }
+                    }
+
+                    ElementAttr::AttrExpression { .. }
+                    | ElementAttr::CustomAttrExpression { .. } => {
+                        let (name, value) = match &attr.attr {
+                            ElementAttr::AttrExpression { name, value } => {
+                                (name.to_string(), value)
+                            }
+                            ElementAttr::CustomAttrExpression { name, value } => {
+                                (name.value(), value)
+                            }
+                            _ => unreachable!(),
+                        };
+                        if let Some((_, resulting_value)) = ctx
+                            .expressions
+                            .iter()
+                            .find(|(n, _)| parse_str::<Expr>(*n).unwrap() == *value)
+                        {
+                            if let Some((name, namespace)) = attrbute_to_static_str(&name) {
+                                let value = bump.alloc(resulting_value.clone());
+                                attributes.push(Attribute {
+                                    name,
+                                    value: AttributeValue::Text(value),
+                                    is_static: true,
+                                    is_volatile: false,
+                                    namespace,
+                                });
+                            }
+                        } else {
+                            return Err(Error::RecompileRequiredError(
+                                RecompileReason::CapturedExpression(
+                                    value.into_token_stream().to_string(),
+                                ),
+                            ));
+                        }
+                    }
+                    _ => (),
+                };
+            }
+            let children = bump.alloc(Vec::new());
+            for child in el.children {
+                let node = build_node(child, ctx, factory)?;
+                children.push(node);
+            }
+            let listeners = bump.alloc(Vec::new());
+            for attr in el.attributes {
+                if let ElementAttr::EventTokens { .. } = attr.attr {
+                    let expr: Expr = parse2(attr.to_token_stream()).map_err(|err| {
+                        Error::ParseError(ParseError::new(err, ctx.location.clone()))
+                    })?;
+                    if let Some(idx) = ctx.listeners.iter().position(|(code, _)| {
+                        if let Ok(parsed) = parse_str::<Expr>(*code) {
+                            parsed == expr
+                        } else {
+                            false
+                        }
+                    }) {
+                        let (_, listener) = ctx.listeners.remove(idx);
+                        listeners.push(listener)
+                    } else {
+                        return Err(Error::RecompileRequiredError(
+                            RecompileReason::CapturedListener(expr.to_token_stream().to_string()),
+                        ));
+                    }
+                }
+            }
+            let tag = bump.alloc(el.name.to_string());
+            if let Some((tag, ns)) = element_to_static_str(tag) {
+                match el.key {
+                    None => Ok(factory.raw_element(
+                        tag,
+                        ns,
+                        listeners,
+                        attributes.as_slice(),
+                        children.as_slice(),
+                        None,
+                    )),
+                    Some(lit) => {
+                        let ifmt: IfmtInput = parse_str(&lit.value()).map_err(|err| {
+                            Error::ParseError(ParseError::new(err, ctx.location.clone()))
+                        })?;
+                        let key = bump.alloc(resolve_ifmt(&ifmt, &ctx.captured)?);
+
+                        Ok(factory.raw_element(
+                            tag,
+                            ns,
+                            listeners,
+                            attributes.as_slice(),
+                            children.as_slice(),
+                            Some(format_args!("{}", key)),
+                        ))
+                    }
+                }
+            } else {
+                Err(Error::ParseError(ParseError::new(
+                    syn::Error::new(el.name.span(), format!("unknown element: {}", tag)),
+                    ctx.location.clone(),
+                )))
+            }
+        }
+        BodyNode::Component(comp) => {
+            let expr: Expr = parse2(comp.to_token_stream())
+                .map_err(|err| Error::ParseError(ParseError::new(err, ctx.location.clone())))?;
+            if let Some(idx) = ctx.components.iter().position(|(code, _)| {
+                if let Ok(parsed) = parse_str::<Expr>(*code) {
+                    parsed == expr
+                } else {
+                    false
+                }
+            }) {
+                let (_, vnode) = ctx.components.remove(idx);
+                Ok(vnode)
+            } else {
+                Err(Error::RecompileRequiredError(
+                    RecompileReason::CapturedComponent(comp.name.to_token_stream().to_string()),
+                ))
+            }
+        }
+        BodyNode::RawExpr(iterator) => {
+            if let Some(idx) = ctx.iterators.iter().position(|(code, _)| {
+                if let Ok(parsed) = parse_str::<Expr>(*code) {
+                    parsed == iterator
+                } else {
+                    false
+                }
+            }) {
+                let (_, vnode) = ctx.iterators.remove(idx);
+                Ok(vnode)
+            } else {
+                Err(Error::RecompileRequiredError(
+                    RecompileReason::CapturedExpression(iterator.to_token_stream().to_string()),
+                ))
+            }
+        }
+    }
+}

+ 182 - 0
packages/rsx_interpreter/src/lib.rs

@@ -0,0 +1,182 @@
+use captuered_context::CapturedContext;
+use dioxus_core::{NodeFactory, SchedulerMsg, VNode};
+use dioxus_hooks::UnboundedSender;
+use error::{Error, ParseError};
+use interperter::build;
+use lazy_static::lazy_static;
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+use std::sync::{RwLock, RwLockReadGuard};
+use syn::parse_str;
+
+mod attributes;
+pub mod captuered_context;
+mod elements;
+pub mod error;
+mod interperter;
+
+lazy_static! {
+    /// This a a global store of the current rsx text for each call to rsx
+    // Global mutable data is genrally not great, but it allows users to not worry about passing down the text RsxContex every time they switch to hot reloading.
+    pub static ref RSX_CONTEXT: RsxContext = RsxContext::new(RsxData::default());
+}
+
+// the location of the code relative to the current crate based on [std::panic::Location]
+#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
+pub struct CodeLocation {
+    pub file: String,
+    pub line: u32,
+    pub column: u32,
+}
+
+/// Get the resolved rsx given the origional rsx, a captured context of dynamic components, and a factory to build the resulting node
+#[cfg_attr(
+    not(debug_assertions),
+    deprecated(
+        note = "The hot reload feature is enabled in release mode. This feature should be disabled for production builds."
+    )
+)]
+pub fn resolve_scope<'a>(
+    location: CodeLocation,
+    rsx: &'static str,
+    captured: CapturedContext<'a>,
+    factory: NodeFactory<'a>,
+) -> VNode<'a> {
+    let rsx_text_index = &*RSX_CONTEXT;
+    // only the insert the rsx text once
+    if !rsx_text_index.read().hm.contains_key(&location) {
+        rsx_text_index.insert(location.clone(), rsx.to_string());
+    }
+    if let Some(text) = {
+        let read = rsx_text_index.read();
+        // clone prevents deadlock on nested rsx calls
+        read.hm.get(&location).cloned()
+    } {
+        match interpert_rsx(factory, &text, captured) {
+            Ok(vnode) => vnode,
+            Err(err) => {
+                rsx_text_index.report_error(err);
+                factory.text(format_args!(""))
+            }
+        }
+    } else {
+        panic!("rsx: line number {:?} not found in rsx index", location);
+    }
+}
+
+fn interpert_rsx<'a, 'b>(
+    factory: dioxus_core::NodeFactory<'a>,
+    text: &str,
+    context: captuered_context::CapturedContext<'a>,
+) -> Result<VNode<'a>, Error> {
+    build(
+        parse_str(text)
+            .map_err(|err| Error::ParseError(ParseError::new(err, context.location.clone())))?,
+        context,
+        &factory,
+    )
+}
+
+/// get the code location of the code that called this function
+#[macro_export]
+macro_rules! get_line_num {
+    () => {{
+        let line = line!();
+        let column = column!();
+        let file = file!();
+
+        #[cfg(windows)]
+        let file = env!("CARGO_MANIFEST_DIR").to_string() + "\\" + file!();
+        #[cfg(unix)]
+        let file = env!("CARGO_MANIFEST_DIR").to_string() + "/" + file!();
+        CodeLocation {
+            file: file.to_string(),
+            line: line,
+            column: column,
+        }
+    }};
+}
+
+/// A handle to the rsx context with interior mutability
+#[derive(Debug)]
+pub struct RsxContext {
+    data: RwLock<RsxData>,
+}
+
+/// A store of the text for the rsx macro for each call to rsx
+#[derive(Default)]
+pub struct RsxData {
+    pub hm: HashMap<CodeLocation, String>,
+    pub error_handler: Option<Box<dyn ErrorHandler>>,
+    pub scheduler_channel: Option<UnboundedSender<SchedulerMsg>>,
+}
+
+impl std::fmt::Debug for RsxData {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("RsxData").field("hm", &self.hm).finish()
+    }
+}
+
+impl RsxContext {
+    pub fn new(data: RsxData) -> Self {
+        Self {
+            data: RwLock::new(data),
+        }
+    }
+
+    /// Set the text for an rsx call at some location
+    pub fn insert(&self, loc: CodeLocation, text: String) {
+        let mut write = self.data.write().unwrap();
+        write.hm.insert(loc, text);
+        if let Some(channel) = &mut write.scheduler_channel {
+            channel.unbounded_send(SchedulerMsg::DirtyAll).unwrap()
+        }
+    }
+
+    /// Set the text for many rsx calls
+    pub fn extend(&self, msg: SetManyRsxMessage) {
+        let mut write = self.data.write().unwrap();
+        for rsx in msg.0 {
+            write.hm.insert(rsx.location, rsx.new_text);
+        }
+        if let Some(channel) = &mut write.scheduler_channel {
+            channel.unbounded_send(SchedulerMsg::DirtyAll).unwrap()
+        }
+    }
+
+    fn read(&self) -> RwLockReadGuard<RsxData> {
+        self.data.read().unwrap()
+    }
+
+    fn report_error(&self, error: Error) {
+        if let Some(handler) = &self.data.write().unwrap().error_handler {
+            handler.handle_error(error)
+        }
+    }
+
+    /// Set the handler for errors interperting the rsx
+    pub fn set_error_handler(&self, handler: impl ErrorHandler + 'static) {
+        self.data.write().unwrap().error_handler = Some(Box::new(handler));
+    }
+
+    /// Provide the scduler channel from [dioxus_code::VirtualDom::get_scheduler_channel].
+    /// The channel allows the interpreter to force re-rendering of the dom when the rsx is changed.
+    pub fn provide_scheduler_channel(&self, channel: UnboundedSender<SchedulerMsg>) {
+        self.data.write().unwrap().scheduler_channel = Some(channel)
+    }
+}
+
+/// A error handler for errors reported by the rsx interperter
+pub trait ErrorHandler: Send + Sync {
+    fn handle_error(&self, err: Error);
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct SetRsxMessage {
+    pub location: CodeLocation,
+    pub new_text: String,
+}
+
+/// Set many rsx texts at once to avoid duplicate errors
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct SetManyRsxMessage(pub Vec<SetRsxMessage>);

+ 353 - 0
packages/rsx_interpreter/tests/render.rs

@@ -0,0 +1,353 @@
+use dioxus_core::prelude::*;
+use dioxus_core_macro::*;
+use dioxus_html as dioxus_elements;
+use dioxus_rsx_interpreter::{
+    captuered_context::{CapturedContext, FormattedArg, IfmtArgs},
+    CodeLocation,
+};
+
+#[test]
+#[allow(non_snake_case)]
+fn render_basic() {
+    fn Base(cx: Scope) -> Element {
+        rsx!(cx, div {})
+    }
+
+    let dom = VirtualDom::new(Base);
+    let static_vnodes = rsx!(div{"hello world"});
+    let location = CodeLocation {
+        file: String::new(),
+        line: 0,
+        column: 0,
+    };
+    let empty_context = CapturedContext {
+        captured: IfmtArgs {
+            named_args: Vec::new(),
+        },
+        components: Vec::new(),
+        iterators: Vec::new(),
+        expressions: Vec::new(),
+        listeners: Vec::new(),
+        location: location.clone(),
+    };
+    let interperted_vnodes = LazyNodes::new(|factory| {
+        dioxus_rsx_interpreter::resolve_scope(
+            location,
+            "div{\"hello world\"}",
+            empty_context,
+            factory,
+        )
+    });
+
+    let interperted_vnodes = dom.render_vnodes(interperted_vnodes);
+    let static_vnodes = dom.render_vnodes(static_vnodes);
+    assert!(check_eq(interperted_vnodes, static_vnodes));
+}
+
+#[test]
+#[allow(non_snake_case)]
+fn render_nested() {
+    fn Base(cx: Scope) -> Element {
+        rsx!(cx, div {})
+    }
+
+    let dom = VirtualDom::new(Base);
+    let static_vnodes = rsx! {
+        div {
+            p { "hello world" }
+            div {
+                p { "hello world" }
+            }
+        }
+    };
+    let location = CodeLocation {
+        file: String::new(),
+        line: 1,
+        column: 0,
+    };
+    let empty_context = CapturedContext {
+        captured: IfmtArgs {
+            named_args: Vec::new(),
+        },
+        components: Vec::new(),
+        iterators: Vec::new(),
+        expressions: Vec::new(),
+        listeners: Vec::new(),
+        location: location.clone(),
+    };
+    let interperted_vnodes = LazyNodes::new(|factory| {
+        dioxus_rsx_interpreter::resolve_scope(
+            location,
+            r#"div {
+                p { "hello world" }
+                div {
+                    p { "hello world" }
+                }
+            }"#,
+            empty_context,
+            factory,
+        )
+    });
+
+    let interperted_vnodes = dom.render_vnodes(interperted_vnodes);
+    let static_vnodes = dom.render_vnodes(static_vnodes);
+    assert!(check_eq(interperted_vnodes, static_vnodes));
+}
+
+#[test]
+#[allow(non_snake_case)]
+fn render_component() {
+    fn Comp(cx: Scope) -> Element {
+        rsx!(cx, div {})
+    }
+
+    fn Base(cx: Scope) -> Element {
+        rsx!(cx, div {})
+    }
+
+    let dom = VirtualDom::new(Base);
+    let static_vnodes = rsx! {
+        div {
+            Comp {}
+        }
+    };
+    let location = CodeLocation {
+        file: String::new(),
+        line: 2,
+        column: 0,
+    };
+
+    let interperted_vnodes = LazyNodes::new(|factory| {
+        let context = CapturedContext {
+            captured: IfmtArgs {
+                named_args: Vec::new(),
+            },
+            components: vec![(
+                r#"__cx.component(Comp, fc_to_builder(Comp).build(), None, "Comp")"#,
+                factory.component(Comp, (), None, "Comp"),
+            )],
+            iterators: Vec::new(),
+            expressions: Vec::new(),
+            listeners: Vec::new(),
+            location: location.clone(),
+        };
+        dioxus_rsx_interpreter::resolve_scope(
+            location,
+            r#"div {
+                Comp {}
+            }"#,
+            context,
+            factory,
+        )
+    });
+
+    let interperted_vnodes = dom.render_vnodes(interperted_vnodes);
+    let static_vnodes = dom.render_vnodes(static_vnodes);
+    println!("{:#?}", interperted_vnodes);
+    println!("{:#?}", static_vnodes);
+    assert!(check_eq(interperted_vnodes, static_vnodes));
+}
+
+#[test]
+#[allow(non_snake_case)]
+fn render_iterator() {
+    fn Base(cx: Scope) -> Element {
+        rsx!(cx, div {})
+    }
+
+    let dom = VirtualDom::new(Base);
+    let iter = (0..10).map(|i| dom.render_vnodes(rsx! {"{i}"}));
+    let static_vnodes = rsx! {
+        div {
+            iter
+        }
+    };
+    let location = CodeLocation {
+        file: String::new(),
+        line: 3,
+        column: 0,
+    };
+
+    let interperted_vnodes = LazyNodes::new(|factory| {
+        let context = CapturedContext {
+            captured: IfmtArgs {
+                named_args: Vec::new(),
+            },
+            components: Vec::new(),
+            iterators: vec![(
+                r#"
+            (0..10).map(|i| dom.render_vnodes(rsx!{"{i}"}))"#,
+                factory.fragment_from_iter((0..10).map(|i| factory.text(format_args!("{i}")))),
+            )],
+            expressions: Vec::new(),
+            listeners: Vec::new(),
+            location: location.clone(),
+        };
+        dioxus_rsx_interpreter::resolve_scope(
+            location,
+            r#"div {
+                (0..10).map(|i| dom.render_vnodes(rsx!{"{i}"}))
+            }"#,
+            context,
+            factory,
+        )
+    });
+
+    let interperted_vnodes = dom.render_vnodes(interperted_vnodes);
+    let static_vnodes = dom.render_vnodes(static_vnodes);
+    println!("{:#?}", interperted_vnodes);
+    println!("{:#?}", static_vnodes);
+    assert!(check_eq(interperted_vnodes, static_vnodes));
+}
+
+#[test]
+#[allow(non_snake_case)]
+fn render_captured_variable() {
+    fn Base(cx: Scope) -> Element {
+        rsx!(cx, div {})
+    }
+
+    let dom = VirtualDom::new(Base);
+
+    let x = 10;
+    let static_vnodes = rsx! {
+        div {
+            "{x}"
+        }
+    };
+    let location = CodeLocation {
+        file: String::new(),
+        line: 4,
+        column: 0,
+    };
+
+    let interperted_vnodes = LazyNodes::new(|factory| {
+        let context = CapturedContext {
+            captured: IfmtArgs {
+                named_args: vec![FormattedArg {
+                    expr: "x",
+                    format_args: "",
+                    result: x.to_string(),
+                }],
+            },
+            components: Vec::new(),
+            iterators: Vec::new(),
+            expressions: Vec::new(),
+            listeners: Vec::new(),
+            location: location.clone(),
+        };
+        dioxus_rsx_interpreter::resolve_scope(
+            location,
+            r#"div {
+                "{x}"
+            }"#,
+            context,
+            factory,
+        )
+    });
+
+    let interperted_vnodes = dom.render_vnodes(interperted_vnodes);
+    let static_vnodes = dom.render_vnodes(static_vnodes);
+    println!("{:#?}", interperted_vnodes);
+    println!("{:#?}", static_vnodes);
+    assert!(check_eq(interperted_vnodes, static_vnodes));
+}
+
+#[test]
+#[allow(non_snake_case)]
+fn render_listener() {
+    fn Base(cx: Scope) -> Element {
+        rsx!(cx, div {})
+    }
+
+    let dom = VirtualDom::new(Base);
+    let static_vnodes = rsx! {
+        div {
+            onclick: |_| println!("clicked")
+        }
+    };
+    let location = CodeLocation {
+        file: String::new(),
+        line: 5,
+        column: 0,
+    };
+
+    let interperted_vnodes = LazyNodes::new(|factory| {
+        let f = |_| println!("clicked");
+        let f = factory.bump().alloc(f);
+        let context = CapturedContext {
+            captured: IfmtArgs {
+                named_args: Vec::new(),
+            },
+            components: Vec::new(),
+            iterators: Vec::new(),
+            expressions: Vec::new(),
+            listeners: vec![(
+                r#"dioxus_elements::on::onclick(__cx, |_| println!("clicked"))"#,
+                dioxus_elements::on::onclick(factory, f),
+            )],
+            location: location.clone(),
+        };
+        dioxus_rsx_interpreter::resolve_scope(
+            location,
+            r#"div {
+                onclick: |_| println!("clicked")
+            }"#,
+            context,
+            factory,
+        )
+    });
+
+    let interperted_vnodes = dom.render_vnodes(interperted_vnodes);
+    let static_vnodes = dom.render_vnodes(static_vnodes);
+    println!("{:#?}", interperted_vnodes);
+    println!("{:#?}", static_vnodes);
+    assert!(check_eq(interperted_vnodes, static_vnodes));
+}
+
+fn check_eq<'a>(a: &'a VNode<'a>, b: &'a VNode<'a>) -> bool {
+    match (a, b) {
+        (VNode::Text(t_a), VNode::Text(t_b)) => t_a.text == t_b.text,
+        (VNode::Element(e_a), VNode::Element(e_b)) => {
+            e_a.attributes
+                .iter()
+                .zip(e_b.attributes.iter())
+                .all(|(a, b)| {
+                    a.is_static == b.is_static
+                        && a.is_volatile == b.is_volatile
+                        && a.name == b.name
+                        && a.value == b.value
+                        && a.namespace == b.namespace
+                })
+                && e_a
+                    .children
+                    .iter()
+                    .zip(e_b.children.iter())
+                    .all(|(a, b)| check_eq(a, b))
+                && e_a.key == e_b.key
+                && e_a.tag == e_b.tag
+                && e_a.namespace == e_b.namespace
+                && e_a
+                    .listeners
+                    .iter()
+                    .zip(e_b.listeners.iter())
+                    .all(|(a, b)| a.event == b.event)
+        }
+        (VNode::Fragment(f_a), VNode::Fragment(f_b)) => {
+            f_a.key == f_b.key
+                && f_a
+                    .children
+                    .iter()
+                    .zip(f_b.children.iter())
+                    .all(|(a, b)| check_eq(a, b))
+        }
+        (VNode::Component(c_a), VNode::Component(c_b)) => {
+            c_a.can_memoize == c_b.can_memoize
+                && c_a.key == c_b.key
+                && c_a.fn_name == c_b.fn_name
+                && c_a.user_fc == c_b.user_fc
+        }
+        (VNode::Placeholder(_), VNode::Placeholder(_)) => true,
+        _ => false,
+    }
+}

+ 3 - 0
packages/web/Cargo.toml

@@ -16,6 +16,7 @@ dioxus-html = { path = "../html", version = "^0.2.1", features = ["wasm-bind"] }
 dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1", features = [
     "web"
 ] }
+dioxus-rsx-interpreter = { path = "../rsx_interpreter", version = "*", optional = true }
 
 js-sys = "0.3.56"
 wasm-bindgen = { version = "0.2.79", features = ["enable-interning"] }
@@ -30,6 +31,7 @@ futures-util = "0.3.19"
 smallstr = "0.2.0"
 serde-wasm-bindgen = "0.4.2"
 futures-channel = "0.3.21"
+serde_json = { version = "1.0", optional = true }
 
 [dependencies.web-sys]
 version = "0.3.56"
@@ -75,6 +77,7 @@ features = [
 [features]
 default = ["panic_hook"]
 panic_hook = ["console_error_panic_hook"]
+hot_reload = ["dioxus-rsx-interpreter", "web-sys/WebSocket", "web-sys/Location", "web-sys/MessageEvent", "web-sys/console", "serde_json"]
 
 [dev-dependencies]
 dioxus-core-macro = { path = "../core-macro" }

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

@@ -217,6 +217,70 @@ pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T
 
     let mut work_loop = ric_raf::RafLoop::new();
 
+    #[cfg(feature = "hot_reload")]
+    {
+        use dioxus_rsx_interpreter::error::Error;
+        use dioxus_rsx_interpreter::{ErrorHandler, SetManyRsxMessage, RSX_CONTEXT};
+        use futures_channel::mpsc::unbounded;
+        use futures_channel::mpsc::UnboundedSender;
+        use futures_util::StreamExt;
+        use wasm_bindgen::closure::Closure;
+        use wasm_bindgen::JsCast;
+        use web_sys::{MessageEvent, WebSocket};
+
+        let window = web_sys::window().unwrap();
+
+        let protocol = if window.location().protocol().unwrap() == "https:" {
+            "wss:"
+        } else {
+            "ws:"
+        };
+
+        let url = format!(
+            "{protocol}//{}/_dioxus/hot_reload",
+            window.location().host().unwrap()
+        );
+
+        let ws = WebSocket::new(&url).unwrap();
+
+        // change the rsx when new data is received
+        let cl = Closure::wrap(Box::new(|e: MessageEvent| {
+            if let Ok(text) = e.data().dyn_into::<js_sys::JsString>() {
+                let msgs: SetManyRsxMessage = serde_json::from_str(&format!("{text}")).unwrap();
+                RSX_CONTEXT.extend(msgs);
+            }
+        }) as Box<dyn FnMut(MessageEvent)>);
+
+        ws.set_onmessage(Some(cl.as_ref().unchecked_ref()));
+        cl.forget();
+
+        let (error_channel_sender, mut error_channel_receiver) = unbounded();
+
+        struct WebErrorHandler {
+            sender: UnboundedSender<Error>,
+        }
+
+        impl ErrorHandler for WebErrorHandler {
+            fn handle_error(&self, err: dioxus_rsx_interpreter::error::Error) {
+                self.sender.unbounded_send(err).unwrap();
+            }
+        }
+
+        RSX_CONTEXT.set_error_handler(WebErrorHandler {
+            sender: error_channel_sender,
+        });
+
+        RSX_CONTEXT.provide_scheduler_channel(dom.get_scheduler_channel());
+
+        // forward stream to the websocket
+        dom.base_scope().spawn_forever(async move {
+            while let Some(err) = error_channel_receiver.next().await {
+                ws.send_with_str(serde_json::to_string(&err).unwrap().as_str())
+                    .unwrap();
+            }
+        });
+    }
+
     loop {
         log::trace!("waiting for work");
         // if virtualdom has nothing, wait for it to have something before requesting idle time

+ 11 - 0
src/lib.rs

@@ -44,6 +44,11 @@ pub mod events {
     pub use dioxus_html::{on::*, KeyCode};
 }
 
+pub use dioxus_rsx as rsx;
+
+#[cfg(feature = "hot_reload")]
+pub use dioxus_rsx_interpreter as rsx_interpreter;
+
 pub mod prelude {
     pub use crate::hooks::*;
     pub use dioxus_core::prelude::*;
@@ -56,4 +61,10 @@ pub mod prelude {
 
     #[cfg(feature = "fermi")]
     pub use fermi::{use_atom_ref, use_init_atom_root, use_read, use_set, Atom, AtomRef};
+
+    #[cfg(feature = "hot_reload")]
+    pub use dioxus_rsx_interpreter::{
+        captuered_context::{CapturedContext, FormattedArg, IfmtArgs},
+        get_line_num, resolve_scope, CodeLocation, RsxContext,
+    };
 }