فهرست منبع

Merge branch 'DioxusLabs:master' into expand-fuzzing

ealmloff 2 سال پیش
والد
کامیت
d9a1b702df

+ 1 - 1
docs/guide/src/en/interactivity/dynamic_rendering.md

@@ -64,7 +64,7 @@ Example: suppose you have a list of comments you want to render. Then, you can r
 
 
 ### Inline for loops
 ### Inline for loops
 
 
-Because of how common it is to render a list of items, Dioxus provides a shorthand for this. Instead of using `.iter, `.map`, and `rsx`, you can use a `for` loop with a body of rsx code:
+Because of how common it is to render a list of items, Dioxus provides a shorthand for this. Instead of using `.iter`, `.map`, and `rsx`, you can use a `for` loop with a body of rsx code:
 
 
 ```rust
 ```rust
 {{#include ../../../examples/rendering_lists.rs:render_list_for_loop}}
 {{#include ../../../examples/rendering_lists.rs:render_list_for_loop}}

+ 3 - 1
packages/autofmt/src/buffer.rs

@@ -8,6 +8,8 @@ use std::fmt::{Result, Write};
 
 
 use dioxus_rsx::IfmtInput;
 use dioxus_rsx::IfmtInput;
 
 
+use crate::write_ifmt;
+
 /// The output buffer that tracks indent and string
 /// The output buffer that tracks indent and string
 #[derive(Debug, Default)]
 #[derive(Debug, Default)]
 pub struct Buffer {
 pub struct Buffer {
@@ -48,7 +50,7 @@ impl Buffer {
     }
     }
 
 
     pub fn write_text(&mut self, text: &IfmtInput) -> Result {
     pub fn write_text(&mut self, text: &IfmtInput) -> Result {
-        write!(self.buf, "\"{}\"", text.source.as_ref().unwrap().value())
+        write_ifmt(text, &mut self.buf)
     }
     }
 }
 }
 
 

+ 4 - 4
packages/autofmt/src/component.rs

@@ -1,4 +1,4 @@
-use crate::{writer::Location, Writer};
+use crate::{ifmt_to_string, writer::Location, Writer};
 use dioxus_rsx::*;
 use dioxus_rsx::*;
 use quote::ToTokens;
 use quote::ToTokens;
 use std::fmt::{Result, Write};
 use std::fmt::{Result, Write};
@@ -170,9 +170,9 @@ impl Writer<'_> {
                 ContentField::Formatted(s) => {
                 ContentField::Formatted(s) => {
                     write!(
                     write!(
                         self.out,
                         self.out,
-                        "{}: \"{}\"",
+                        "{}: {}",
                         name,
                         name,
-                        s.source.as_ref().unwrap().value()
+                        s.source.as_ref().unwrap().to_token_stream()
                     )?;
                     )?;
                 }
                 }
                 ContentField::OnHandlerRaw(exp) => {
                 ContentField::OnHandlerRaw(exp) => {
@@ -215,7 +215,7 @@ impl Writer<'_> {
         let attr_len = fields
         let attr_len = fields
             .iter()
             .iter()
             .map(|field| match &field.content {
             .map(|field| match &field.content {
-                ContentField::Formatted(s) => s.source.as_ref().unwrap().value().len() ,
+                ContentField::Formatted(s) => ifmt_to_string(s).len() ,
                 ContentField::OnHandlerRaw(exp) | ContentField::ManExpr(exp) => {
                 ContentField::OnHandlerRaw(exp) | ContentField::ManExpr(exp) => {
                     let formatted = prettyplease::unparse_expr(exp);
                     let formatted = prettyplease::unparse_expr(exp);
                     let len = if formatted.contains('\n') {
                     let len = if formatted.contains('\n') {

+ 11 - 18
packages/autofmt/src/element.rs

@@ -1,6 +1,7 @@
-use crate::Writer;
+use crate::{ifmt_to_string, Writer};
 use dioxus_rsx::*;
 use dioxus_rsx::*;
 use proc_macro2::Span;
 use proc_macro2::Span;
+use quote::ToTokens;
 use std::{
 use std::{
     fmt::Result,
     fmt::Result,
     fmt::{self, Write},
     fmt::{self, Write},
@@ -175,11 +176,7 @@ impl Writer<'_> {
             if !sameline {
             if !sameline {
                 self.out.indented_tabbed_line()?;
                 self.out.indented_tabbed_line()?;
             }
             }
-            write!(
-                self.out,
-                "key: \"{}\"",
-                key.source.as_ref().unwrap().value()
-            )?;
+            write!(self.out, "key: {}", ifmt_to_string(key))?;
             if !attributes.is_empty() {
             if !attributes.is_empty() {
                 write!(self.out, ",")?;
                 write!(self.out, ",")?;
                 if sameline {
                 if sameline {
@@ -216,11 +213,7 @@ impl Writer<'_> {
     fn write_attribute(&mut self, attr: &ElementAttrNamed) -> Result {
     fn write_attribute(&mut self, attr: &ElementAttrNamed) -> Result {
         match &attr.attr {
         match &attr.attr {
             ElementAttr::AttrText { name, value } => {
             ElementAttr::AttrText { name, value } => {
-                write!(
-                    self.out,
-                    "{name}: \"{value}\"",
-                    value = value.source.as_ref().unwrap().value()
-                )?;
+                write!(self.out, "{name}: {value}", value = ifmt_to_string(value))?;
             }
             }
             ElementAttr::AttrExpression { name, value } => {
             ElementAttr::AttrExpression { name, value } => {
                 let out = prettyplease::unparse_expr(value);
                 let out = prettyplease::unparse_expr(value);
@@ -230,15 +223,15 @@ impl Writer<'_> {
             ElementAttr::CustomAttrText { name, value } => {
             ElementAttr::CustomAttrText { name, value } => {
                 write!(
                 write!(
                     self.out,
                     self.out,
-                    "\"{name}\": \"{value}\"",
-                    name = name.value(),
-                    value = value.source.as_ref().unwrap().value()
+                    "{name}: {value}",
+                    name = name.to_token_stream(),
+                    value = ifmt_to_string(value)
                 )?;
                 )?;
             }
             }
 
 
             ElementAttr::CustomAttrExpression { name, value } => {
             ElementAttr::CustomAttrExpression { name, value } => {
                 let out = prettyplease::unparse_expr(value);
                 let out = prettyplease::unparse_expr(value);
-                write!(self.out, "\"{}\": {}", name.value(), out)?;
+                write!(self.out, "{}: {}", name.to_token_stream(), out)?;
             }
             }
 
 
             ElementAttr::EventTokens { name, tokens } => {
             ElementAttr::EventTokens { name, tokens } => {
@@ -315,7 +308,7 @@ impl Writer<'_> {
         }
         }
 
 
         match children {
         match children {
-            [BodyNode::Text(ref text)] => Some(text.source.as_ref().unwrap().value().len()),
+            [BodyNode::Text(ref text)] => Some(ifmt_to_string(text).len()),
             [BodyNode::Component(ref comp)] => {
             [BodyNode::Component(ref comp)] => {
                 let attr_len = self.field_len(&comp.fields, &comp.manual_props);
                 let attr_len = self.field_len(&comp.fields, &comp.manual_props);
 
 
@@ -338,7 +331,7 @@ impl Writer<'_> {
 
 
                 if el.children.len() == 1 {
                 if el.children.len() == 1 {
                     if let BodyNode::Text(ref text) = el.children[0] {
                     if let BodyNode::Text(ref text) = el.children[0] {
-                        let value = text.source.as_ref().unwrap().value();
+                        let value = ifmt_to_string(text);
 
 
                         if value.len() + el.name.to_string().len() + attr_len < 80 {
                         if value.len() + el.name.to_string().len() + attr_len < 80 {
                             return Some(value.len() + el.name.to_string().len() + attr_len);
                             return Some(value.len() + el.name.to_string().len() + attr_len);
@@ -356,7 +349,7 @@ impl Writer<'_> {
                     match item {
                     match item {
                         BodyNode::Component(_) | BodyNode::Element(_) => return None,
                         BodyNode::Component(_) | BodyNode::Element(_) => return None,
                         BodyNode::Text(text) => {
                         BodyNode::Text(text) => {
-                            total_count += text.source.as_ref().unwrap().value().len()
+                            total_count += ifmt_to_string(text).len();
                         }
                         }
                         BodyNode::RawExpr(expr) => match get_expr_length(expr) {
                         BodyNode::RawExpr(expr) => match get_expr_length(expr) {
                             Some(len) => total_count += len,
                             Some(len) => total_count += len,

+ 25 - 3
packages/autofmt/src/lib.rs

@@ -1,7 +1,10 @@
+use std::fmt::{Display, Write};
+
 use crate::writer::*;
 use crate::writer::*;
 use collect_macros::byte_offset;
 use collect_macros::byte_offset;
-use dioxus_rsx::{BodyNode, CallBody};
+use dioxus_rsx::{BodyNode, CallBody, IfmtInput};
 use proc_macro2::LineColumn;
 use proc_macro2::LineColumn;
+use quote::ToTokens;
 use syn::{ExprMacro, MacroDelimiter};
 use syn::{ExprMacro, MacroDelimiter};
 
 
 mod buffer;
 mod buffer;
@@ -130,8 +133,6 @@ pub fn write_block_out(body: CallBody) -> Option<String> {
 }
 }
 
 
 fn write_body(buf: &mut Writer, body: &CallBody) {
 fn write_body(buf: &mut Writer, body: &CallBody) {
-    use std::fmt::Write;
-
     if buf.is_short_children(&body.roots).is_some() {
     if buf.is_short_children(&body.roots).is_some() {
         // write all the indents with spaces and commas between
         // write all the indents with spaces and commas between
         for idx in 0..body.roots.len() - 1 {
         for idx in 0..body.roots.len() - 1 {
@@ -208,3 +209,24 @@ pub fn apply_formats(input: &str, blocks: Vec<FormattedBlock>) -> String {
 
 
     out
     out
 }
 }
+
+struct DisplayIfmt<'a>(&'a IfmtInput);
+
+impl Display for DisplayIfmt<'_> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let inner_tokens = self.0.source.as_ref().unwrap().to_token_stream();
+        inner_tokens.fmt(f)
+    }
+}
+
+pub(crate) fn ifmt_to_string(input: &IfmtInput) -> String {
+    let mut buf = String::new();
+    let display = DisplayIfmt(input);
+    write!(&mut buf, "{}", display).unwrap();
+    buf
+}
+
+pub(crate) fn write_ifmt(input: &IfmtInput, writable: &mut impl Write) -> std::fmt::Result {
+    let display = DisplayIfmt(input);
+    write!(writable, "{}", display)
+}

+ 4 - 3
packages/autofmt/src/writer.rs

@@ -8,6 +8,7 @@ use std::{
 use syn::{spanned::Spanned, Expr, ExprIf};
 use syn::{spanned::Spanned, Expr, ExprIf};
 
 
 use crate::buffer::Buffer;
 use crate::buffer::Buffer;
+use crate::ifmt_to_string;
 
 
 #[derive(Debug)]
 #[derive(Debug)]
 pub struct Writer<'a> {
 pub struct Writer<'a> {
@@ -147,16 +148,16 @@ impl<'a> Writer<'a> {
 
 
             total += match &attr.attr {
             total += match &attr.attr {
                 ElementAttr::AttrText { value, name } => {
                 ElementAttr::AttrText { value, name } => {
-                    value.source.as_ref().unwrap().value().len() + name.span().line_length() + 6
+                    ifmt_to_string(value).len() + name.span().line_length() + 6
                 }
                 }
                 ElementAttr::AttrExpression { name, value } => {
                 ElementAttr::AttrExpression { name, value } => {
                     value.span().line_length() + name.span().line_length() + 6
                     value.span().line_length() + name.span().line_length() + 6
                 }
                 }
                 ElementAttr::CustomAttrText { value, name } => {
                 ElementAttr::CustomAttrText { value, name } => {
-                    value.source.as_ref().unwrap().value().len() + name.value().len() + 6
+                    ifmt_to_string(value).len() + name.to_token_stream().to_string().len() + 6
                 }
                 }
                 ElementAttr::CustomAttrExpression { name, value } => {
                 ElementAttr::CustomAttrExpression { name, value } => {
-                    name.value().len() + value.span().line_length() + 6
+                    name.to_token_stream().to_string().len() + value.span().line_length() + 6
                 }
                 }
                 ElementAttr::EventTokens { tokens, name } => {
                 ElementAttr::EventTokens { tokens, name } => {
                     let location = Location::new(tokens.span().start());
                     let location = Location::new(tokens.span().start());

+ 21 - 17
packages/autofmt/tests/samples.rs

@@ -4,8 +4,8 @@ macro_rules! twoway {
 
 
             // doc attrs
             // doc attrs
             $( #[doc = $doc:expr] )*
             $( #[doc = $doc:expr] )*
-            $name:ident
-        ),*
+            $name:ident,
+        )*
     ) => {
     ) => {
         $(
         $(
             $( #[doc = $doc] )*
             $( #[doc = $doc] )*
@@ -14,6 +14,9 @@ macro_rules! twoway {
                 let src = include_str!(concat!("./samples/", stringify!($name), ".rsx"));
                 let src = include_str!(concat!("./samples/", stringify!($name), ".rsx"));
                 let formatted = dioxus_autofmt::fmt_file(src);
                 let formatted = dioxus_autofmt::fmt_file(src);
                 let out = dioxus_autofmt::apply_formats(src, formatted);
                 let out = dioxus_autofmt::apply_formats(src, formatted);
+                // normalize line endings
+                let out = out.replace("\r", "");
+                let src = src.replace("\r", "");
                 pretty_assertions::assert_eq!(&src, &out);
                 pretty_assertions::assert_eq!(&src, &out);
             }
             }
         )*
         )*
@@ -21,24 +24,25 @@ macro_rules! twoway {
 }
 }
 
 
 twoway![
 twoway![
-    simple,
-    comments,
     attributes,
     attributes,
-    manual_props,
-    complex,
-    tiny,
-    tinynoopt,
-    long,
-    key,
-    multirsx,
+    collapse_expr,
+    comments,
     commentshard,
     commentshard,
+    complex,
     emoji,
     emoji,
-    messy_indent,
-    long_exprs,
     ifchain_forloop,
     ifchain_forloop,
-    t2,
-    reallylong,
     immediate_expr,
     immediate_expr,
-    collapse_expr,
-    trailing_expr
+    key,
+    long_exprs,
+    long,
+    manual_props,
+    messy_indent,
+    multirsx,
+    raw_strings,
+    reallylong,
+    simple,
+    t2,
+    tiny,
+    tinynoopt,
+    trailing_expr,
 ];
 ];

+ 3 - 1
packages/autofmt/tests/samples/complex.rsx

@@ -13,7 +13,9 @@ rsx! {
 
 
     // Complex nesting with handlers
     // Complex nesting with handlers
     li {
     li {
-        Link { class: "flex items-center pl-3 py-3 pr-4 {active_class} rounded", to: "{to}",
+        Link {
+            class: "flex items-center pl-3 py-3 pr-4 {active_class} rounded",
+            to: "{to}",
             span { class: "inline-block mr-3", icons::icon_0 {} }
             span { class: "inline-block mr-3", icons::icon_0 {} }
             span { "{name}" }
             span { "{name}" }
             children.is_some().then(|| rsx! {
             children.is_some().then(|| rsx! {

+ 12 - 0
packages/autofmt/tests/samples/raw_strings.rsx

@@ -0,0 +1,12 @@
+rsx! {
+    // Raw text strings
+    button { r#"Click me"#, r##"Click me"##, r######"Click me"######, r#"dynamic {1}"# }
+
+    // Raw attribute strings
+    div {
+        width: r#"10px"#,
+        height: r##"{10}px"##,
+        "raw-attr": r###"raw-attr"###,
+        "raw-attr2": r###"{100}"###
+    }
+}

+ 5 - 0
packages/autofmt/tests/wrong.rs

@@ -6,6 +6,11 @@ macro_rules! twoway {
             let src_wrong = include_str!(concat!("./wrong/", $val, ".wrong.rsx"));
             let src_wrong = include_str!(concat!("./wrong/", $val, ".wrong.rsx"));
             let formatted = dioxus_autofmt::fmt_file(src_wrong);
             let formatted = dioxus_autofmt::fmt_file(src_wrong);
             let out = dioxus_autofmt::apply_formats(src_wrong, formatted);
             let out = dioxus_autofmt::apply_formats(src_wrong, formatted);
+
+            // normalize line endings
+            let out = out.replace("\r", "");
+            let src_right = src_right.replace("\r", "");
+
             pretty_assertions::assert_eq!(&src_right, &out);
             pretty_assertions::assert_eq!(&src_right, &out);
         }
         }
     };
     };

+ 3 - 5
packages/core/src/diff.rs

@@ -7,7 +7,7 @@ use crate::{
     nodes::{DynamicNode, VNode},
     nodes::{DynamicNode, VNode},
     scopes::ScopeId,
     scopes::ScopeId,
     virtual_dom::VirtualDom,
     virtual_dom::VirtualDom,
-    Attribute, AttributeValue, TemplateNode,
+    Attribute, TemplateNode,
 };
 };
 
 
 use rustc_hash::{FxHashMap, FxHashSet};
 use rustc_hash::{FxHashMap, FxHashSet};
@@ -125,10 +125,8 @@ impl<'b> VirtualDom {
                     .mounted_element
                     .mounted_element
                     .set(left_attr.mounted_element.get());
                     .set(left_attr.mounted_element.get());
 
 
-                // We want to make sure anything listener that gets pulled is valid
-                if let AttributeValue::Listener(_) = right_attr.value {
-                    self.update_template(left_attr.mounted_element.get(), right_template);
-                }
+                // We want to make sure anything that gets pulled is valid
+                self.update_template(left_attr.mounted_element.get(), right_template);
 
 
                 // If the attributes are different (or volatile), we need to update them
                 // If the attributes are different (or volatile), we need to update them
                 if left_attr.value != right_attr.value || left_attr.volatile {
                 if left_attr.value != right_attr.value || left_attr.volatile {

+ 51 - 45
packages/core/src/virtual_dom.rs

@@ -389,64 +389,70 @@ impl VirtualDom {
             // Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent.
             // Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent.
             while let Some(el_ref) = parent_path {
             while let Some(el_ref) = parent_path {
                 // safety: we maintain references of all vnodes in the element slab
                 // safety: we maintain references of all vnodes in the element slab
-                let template = unsafe { el_ref.template.unwrap().as_ref() };
-                let node_template = template.template.get();
-                let target_path = el_ref.path;
-
-                for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
-                    let this_path = node_template.attr_paths[idx];
-
-                    // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
-                    if attr.name.trim_start_matches("on") == name
-                        && target_path.is_decendant(&this_path)
-                    {
-                        listeners.push(&attr.value);
-
-                        // Break if this is the exact target element.
-                        // This means we won't call two listeners with the same name on the same element. This should be
-                        // documented, or be rejected from the rsx! macro outright
-                        if target_path == this_path {
-                            break;
+                if let Some(template) = el_ref.template {
+                    let template = unsafe { template.as_ref() };
+                    let node_template = template.template.get();
+                    let target_path = el_ref.path;
+
+                    for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
+                        let this_path = node_template.attr_paths[idx];
+
+                        // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
+                        if attr.name.trim_start_matches("on") == name
+                            && target_path.is_decendant(&this_path)
+                        {
+                            listeners.push(&attr.value);
+
+                            // Break if this is the exact target element.
+                            // This means we won't call two listeners with the same name on the same element. This should be
+                            // documented, or be rejected from the rsx! macro outright
+                            if target_path == this_path {
+                                break;
+                            }
                         }
                         }
                     }
                     }
-                }
 
 
-                // Now that we've accumulated all the parent attributes for the target element, call them in reverse order
-                // We check the bubble state between each call to see if the event has been stopped from bubbling
-                for listener in listeners.drain(..).rev() {
-                    if let AttributeValue::Listener(listener) = listener {
-                        if let Some(cb) = listener.borrow_mut().as_deref_mut() {
-                            cb(uievent.clone());
-                        }
+                    // Now that we've accumulated all the parent attributes for the target element, call them in reverse order
+                    // We check the bubble state between each call to see if the event has been stopped from bubbling
+                    for listener in listeners.drain(..).rev() {
+                        if let AttributeValue::Listener(listener) = listener {
+                            if let Some(cb) = listener.borrow_mut().as_deref_mut() {
+                                cb(uievent.clone());
+                            }
 
 
-                        if !uievent.propagates.get() {
-                            return;
+                            if !uievent.propagates.get() {
+                                return;
+                            }
                         }
                         }
                     }
                     }
-                }
 
 
-                parent_path = template.parent.and_then(|id| self.elements.get(id.0));
+                    parent_path = dbg!(template.parent).and_then(|id| self.elements.get(id.0));
+                } else {
+                    break;
+                }
             }
             }
         } else {
         } else {
             // Otherwise, we just call the listener on the target element
             // Otherwise, we just call the listener on the target element
             if let Some(el_ref) = parent_path {
             if let Some(el_ref) = parent_path {
                 // safety: we maintain references of all vnodes in the element slab
                 // safety: we maintain references of all vnodes in the element slab
-                let template = unsafe { el_ref.template.unwrap().as_ref() };
-                let node_template = template.template.get();
-                let target_path = el_ref.path;
-
-                for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
-                    let this_path = node_template.attr_paths[idx];
-
-                    // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
-                    // Only call the listener if this is the exact target element.
-                    if attr.name.trim_start_matches("on") == name && target_path == this_path {
-                        if let AttributeValue::Listener(listener) = &attr.value {
-                            if let Some(cb) = listener.borrow_mut().as_deref_mut() {
-                                cb(uievent.clone());
+                if let Some(template) = el_ref.template {
+                    let template = unsafe { template.as_ref() };
+                    let node_template = template.template.get();
+                    let target_path = el_ref.path;
+
+                    for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
+                        let this_path = node_template.attr_paths[idx];
+
+                        // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
+                        // Only call the listener if this is the exact target element.
+                        if attr.name.trim_start_matches("on") == name && target_path == this_path {
+                            if let AttributeValue::Listener(listener) = &attr.value {
+                                if let Some(cb) = listener.borrow_mut().as_deref_mut() {
+                                    cb(uievent.clone());
+                                }
+
+                                break;
                             }
                             }
-
-                            break;
                         }
                         }
                     }
                     }
                 }
                 }

+ 7 - 2
packages/desktop/src/protocol.rs

@@ -1,4 +1,4 @@
-use dioxus_interpreter_js::INTERPRETER_JS;
+use dioxus_interpreter_js::{COMMON_JS, INTERPRETER_JS};
 use std::{
 use std::{
     borrow::Cow,
     borrow::Cow,
     path::{Path, PathBuf},
     path::{Path, PathBuf},
@@ -37,7 +37,7 @@ fn module_loader(root_name: &str) -> String {
     );
     );
     format!(
     format!(
         r#"
         r#"
-<script>
+<script type="module">
     {js}
     {js}
 
 
     let rootname = "{root_name}";
     let rootname = "{root_name}";
@@ -84,6 +84,11 @@ pub(super) fn desktop_handler(
             .header("Content-Type", "text/html")
             .header("Content-Type", "text/html")
             .body(Cow::from(body))
             .body(Cow::from(body))
             .map_err(From::from);
             .map_err(From::from);
+    } else if request.uri().path() == "/common.js" {
+        return Response::builder()
+            .header("Content-Type", "text/javascript")
+            .body(Cow::from(COMMON_JS.as_bytes()))
+            .map_err(From::from);
     }
     }
 
 
     // Else, try to serve a file from the filesystem.
     // Else, try to serve a file from the filesystem.

+ 1 - 0
packages/html/src/elements.rs

@@ -1154,6 +1154,7 @@ builder_constructors! {
         r#type: InputType "type",
         r#type: InputType "type",
         // value: String,
         // value: String,
         value: String volatile,
         value: String volatile,
+        initial_value: String DEFAULT,
     };
     };
 
 
     /// Build a
     /// Build a

+ 1 - 0
packages/interpreter/Cargo.toml

@@ -25,3 +25,4 @@ default = []
 serialize = ["serde"]
 serialize = ["serde"]
 web = ["wasm-bindgen", "js-sys", "web-sys"]
 web = ["wasm-bindgen", "js-sys", "web-sys"]
 sledgehammer = ["wasm-bindgen", "js-sys", "web-sys", "sledgehammer_bindgen", "sledgehammer_utils"]
 sledgehammer = ["wasm-bindgen", "js-sys", "web-sys", "sledgehammer_bindgen", "sledgehammer_utils"]
+minimal_bindings = []

+ 68 - 0
packages/interpreter/src/common.js

@@ -0,0 +1,68 @@
+const bool_attrs = {
+  allowfullscreen: true,
+  allowpaymentrequest: true,
+  async: true,
+  autofocus: true,
+  autoplay: true,
+  checked: true,
+  controls: true,
+  default: true,
+  defer: true,
+  disabled: true,
+  formnovalidate: true,
+  hidden: true,
+  ismap: true,
+  itemscope: true,
+  loop: true,
+  multiple: true,
+  muted: true,
+  nomodule: true,
+  novalidate: true,
+  open: true,
+  playsinline: true,
+  readonly: true,
+  required: true,
+  reversed: true,
+  selected: true,
+  truespeed: true,
+};
+
+export function setAttributeInner(node, field, value, ns) {
+  const name = field;
+  if (ns === "style") {
+    // ????? why do we need to do this
+    if (node.style === undefined) {
+      node.style = {};
+    }
+    node.style[name] = value;
+  } else if (ns != null && ns != undefined) {
+    node.setAttributeNS(ns, name, value);
+  } else {
+    switch (name) {
+      case "value":
+        if (value !== node.value) {
+          node.value = value;
+        }
+        break;
+      case "initial_value":
+        node.defaultValue = value;
+        break;
+      case "checked":
+        node.checked = value === "true" || value === true;
+        break;
+      case "selected":
+        node.selected = value === "true" || value === true;
+        break;
+      case "dangerous_inner_html":
+        node.innerHTML = value;
+        break;
+      default:
+        // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
+        if (value === "false" && bool_attrs.hasOwnProperty(name)) {
+          node.removeAttribute(name);
+        } else {
+          node.setAttribute(name, value);
+        }
+    }
+  }
+}

+ 4 - 66
packages/interpreter/src/interpreter.js

@@ -1,3 +1,5 @@
+import { setAttributeInner } from "./common.js";
+
 class ListenerMap {
 class ListenerMap {
   constructor(root) {
   constructor(root) {
     // bubbling events can listen at the root element
     // bubbling events can listen at the root element
@@ -153,43 +155,7 @@ class Interpreter {
       this.RemoveAttribute(id, field, ns);
       this.RemoveAttribute(id, field, ns);
     } else {
     } else {
       const node = this.nodes[id];
       const node = this.nodes[id];
-      this.SetAttributeInner(node, field, value, ns);
-    }
-  }
-  SetAttributeInner(node, field, value, ns) {
-    const name = field;
-    if (ns === "style") {
-      // ????? why do we need to do this
-      if (node.style === undefined) {
-        node.style = {};
-      }
-      node.style[name] = value;
-    } else if (ns != null && ns != undefined) {
-      node.setAttributeNS(ns, name, value);
-    } else {
-      switch (name) {
-        case "value":
-          if (value !== node.value) {
-            node.value = value;
-          }
-          break;
-        case "checked":
-          node.checked = value === "true" || value === true;
-          break;
-        case "selected":
-          node.selected = value === "true" || value === true;
-          break;
-        case "dangerous_inner_html":
-          node.innerHTML = value;
-          break;
-        default:
-          // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
-          if (value === "false" && bool_attrs.hasOwnProperty(name)) {
-            node.removeAttribute(name);
-          } else {
-            node.setAttribute(name, value);
-          }
-      }
+      setAttributeInner(node, field, value, ns);
     }
     }
   }
   }
   RemoveAttribute(root, field, ns) {
   RemoveAttribute(root, field, ns) {
@@ -291,7 +257,7 @@ class Interpreter {
 
 
         for (let attr of node.attrs) {
         for (let attr of node.attrs) {
           if (attr.type == "Static") {
           if (attr.type == "Static") {
-            this.SetAttributeInner(el, attr.name, attr.value, attr.namespace);
+            setAttributeInner(el, attr.name, attr.value, attr.namespace);
           }
           }
         }
         }
 
 
@@ -794,34 +760,6 @@ function serialize_event(event) {
 function serializeIpcMessage(method, params = {}) {
 function serializeIpcMessage(method, params = {}) {
   return JSON.stringify({ method, params });
   return JSON.stringify({ method, params });
 }
 }
-const bool_attrs = {
-  allowfullscreen: true,
-  allowpaymentrequest: true,
-  async: true,
-  autofocus: true,
-  autoplay: true,
-  checked: true,
-  controls: true,
-  default: true,
-  defer: true,
-  disabled: true,
-  formnovalidate: true,
-  hidden: true,
-  ismap: true,
-  itemscope: true,
-  loop: true,
-  multiple: true,
-  muted: true,
-  nomodule: true,
-  novalidate: true,
-  open: true,
-  playsinline: true,
-  readonly: true,
-  required: true,
-  reversed: true,
-  selected: true,
-  truespeed: true,
-};
 
 
 function is_element_node(node) {
 function is_element_node(node) {
   return node.nodeType == 1;
   return node.nodeType == 1;

+ 11 - 0
packages/interpreter/src/lib.rs

@@ -1,4 +1,5 @@
 pub static INTERPRETER_JS: &str = include_str!("./interpreter.js");
 pub static INTERPRETER_JS: &str = include_str!("./interpreter.js");
+pub static COMMON_JS: &str = include_str!("./common.js");
 
 
 #[cfg(feature = "sledgehammer")]
 #[cfg(feature = "sledgehammer")]
 mod sledgehammer_bindings;
 mod sledgehammer_bindings;
@@ -10,3 +11,13 @@ mod bindings;
 
 
 #[cfg(feature = "web")]
 #[cfg(feature = "web")]
 pub use bindings::Interpreter;
 pub use bindings::Interpreter;
+
+// Common bindings for minimal usage.
+#[cfg(feature = "minimal_bindings")]
+pub mod minimal_bindings {
+    use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
+    #[wasm_bindgen(module = "/src/common.js")]
+    extern "C" {
+        pub fn setAttributeInner(node: JsValue, name: &str, value: JsValue, ns: Option<&str>);
+    }
+}

+ 3 - 0
packages/interpreter/src/sledgehammer_bindings.rs

@@ -74,6 +74,9 @@ mod js {
                         node.value = value;
                         node.value = value;
                     }
                     }
                     break;
                     break;
+                case "initial_value":
+                    node.defaultValue = value;
+                    break;
                 case "checked":
                 case "checked":
                     node.checked = value === "true";
                     node.checked = value === "true";
                     break;
                     break;

+ 1 - 1
packages/liveview/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 [package]
 name = "dioxus-liveview"
 name = "dioxus-liveview"
-version = "0.3.0"
+version = "0.3.1"
 edition = "2021"
 edition = "2021"
 repository = "https://github.com/DioxusLabs/dioxus/"
 repository = "https://github.com/DioxusLabs/dioxus/"
 homepage = "https://dioxuslabs.com"
 homepage = "https://dioxuslabs.com"

+ 9 - 1
packages/liveview/src/lib.rs

@@ -78,7 +78,13 @@ static INTERPRETER_JS: Lazy<String> = Lazy::new(|| {
       }
       }
     }"#;
     }"#;
 
 
-    interpreter.replace("/*POST_EVENT_SERIALIZATION*/", serialize_file_uploads)
+    let interpreter = interpreter.replace("/*POST_EVENT_SERIALIZATION*/", serialize_file_uploads);
+    interpreter.replace("import { setAttributeInner } from \"./common.js\";", "")
+});
+
+static COMMON_JS: Lazy<String> = Lazy::new(|| {
+    let common = dioxus_interpreter_js::COMMON_JS;
+    common.replace("export", "")
 });
 });
 
 
 static MAIN_JS: &str = include_str!("./main.js");
 static MAIN_JS: &str = include_str!("./main.js");
@@ -89,11 +95,13 @@ static MAIN_JS: &str = include_str!("./main.js");
 /// processing user events and returning edits to the liveview instance
 /// processing user events and returning edits to the liveview instance
 pub fn interpreter_glue(url: &str) -> String {
 pub fn interpreter_glue(url: &str) -> String {
     let js = &*INTERPRETER_JS;
     let js = &*INTERPRETER_JS;
+    let common = &*COMMON_JS;
     format!(
     format!(
         r#"
         r#"
 <script>
 <script>
     var WS_ADDR = "{url}";
     var WS_ADDR = "{url}";
     {js}
     {js}
+    {common}
     {MAIN_JS}
     {MAIN_JS}
     main();
     main();
 </script>
 </script>

+ 2 - 1
packages/web/Cargo.toml

@@ -14,7 +14,8 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 dioxus-core = { path = "../core", version = "^0.3.0", features = ["serialize"] }
 dioxus-core = { path = "../core", version = "^0.3.0", features = ["serialize"] }
 dioxus-html = { path = "../html", version = "^0.3.0", features = ["wasm-bind"] }
 dioxus-html = { path = "../html", version = "^0.3.0", features = ["wasm-bind"] }
 dioxus-interpreter-js = { path = "../interpreter", version = "^0.3.0", features = [
 dioxus-interpreter-js = { path = "../interpreter", version = "^0.3.0", features = [
-    "sledgehammer"
+    "sledgehammer",
+    "minimal_bindings",
 ] }
 ] }
 
 
 js-sys = "0.3.56"
 js-sys = "0.3.56"

+ 9 - 11
packages/web/src/dom.rs

@@ -11,13 +11,13 @@ use dioxus_core::{
     BorrowedAttributeValue, ElementId, Mutation, Template, TemplateAttribute, TemplateNode,
     BorrowedAttributeValue, ElementId, Mutation, Template, TemplateAttribute, TemplateNode,
 };
 };
 use dioxus_html::{event_bubbles, CompositionData, FileEngine, FormData, MountedData};
 use dioxus_html::{event_bubbles, CompositionData, FileEngine, FormData, MountedData};
-use dioxus_interpreter_js::{get_node, save_template, Channel};
+use dioxus_interpreter_js::{get_node, minimal_bindings, save_template, Channel};
 use futures_channel::mpsc;
 use futures_channel::mpsc;
 use js_sys::Array;
 use js_sys::Array;
 use rustc_hash::FxHashMap;
 use rustc_hash::FxHashMap;
 use std::{any::Any, rc::Rc, sync::Arc};
 use std::{any::Any, rc::Rc, sync::Arc};
-use wasm_bindgen::{closure::Closure, prelude::wasm_bindgen, JsCast};
-use web_sys::{console, Document, Element, Event, HtmlElement};
+use wasm_bindgen::{closure::Closure, prelude::wasm_bindgen, JsCast, JsValue};
+use web_sys::{console, Document, Element, Event};
 
 
 use crate::{file_engine::WebFileEngine, Config};
 use crate::{file_engine::WebFileEngine, Config};
 
 
@@ -135,14 +135,12 @@ impl WebsysDom {
                         namespace,
                         namespace,
                     } = attr
                     } = attr
                     {
                     {
-                        match namespace {
-                            Some(ns) if *ns == "style" => {
-                                el.dyn_ref::<HtmlElement>()
-                                    .map(|f| f.style().set_property(name, value));
-                            }
-                            Some(ns) => el.set_attribute_ns(Some(ns), name, value).unwrap(),
-                            None => el.set_attribute(name, value).unwrap(),
-                        }
+                        minimal_bindings::setAttributeInner(
+                            el.clone().into(),
+                            name,
+                            JsValue::from_str(value),
+                            *namespace,
+                        );
                     }
                     }
                 }
                 }
                 for child in *children {
                 for child in *children {