浏览代码

Merge branch 'master' into bump-salvo-utils

ealmloff 1 年之前
父节点
当前提交
060e9348af

+ 1 - 1
README.md

@@ -161,7 +161,7 @@ So... Dioxus is great, but why won't it work for me?
 ## Contributing
 - Check out the website [section on contributing](https://dioxuslabs.com/learn/0.4/contributing).
 - Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).
-- Join the discord and ask questions!
+- [Join](https://discord.gg/XgGxMSkvUM) the discord and ask questions!
 
 
 <a href="https://github.com/dioxuslabs/dioxus/graphs/contributors">

+ 3 - 0
packages/core-macro/src/component_body_deserializers/component.rs

@@ -58,8 +58,11 @@ impl ToTokens for ComponentDeserializerOutput {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let comp_fn = &self.comp_fn;
         let props_struct = &self.props_struct;
+        let fn_ident = &comp_fn.sig.ident;
 
+        let doc = format!("Properties for the [`{fn_ident}`] component.");
         tokens.append_all(quote! {
+            #[doc = #doc]
             #props_struct
             #[allow(non_snake_case)]
             #comp_fn

+ 2 - 1
packages/core/src/create.rs

@@ -205,7 +205,7 @@ impl<'b> VirtualDom {
         });
     }
 
-    /// We write all the descndent data for this element
+    /// We write all the descendent data for this element
     ///
     /// Elements can contain other nodes - and those nodes can be dynamic or static
     ///
@@ -405,6 +405,7 @@ impl<'b> VirtualDom {
     #[allow(unused_mut)]
     pub(crate) fn register_template(&mut self, mut template: Template<'static>) {
         let (path, byte_index) = template.name.rsplit_once(':').unwrap();
+
         let byte_index = byte_index.parse::<usize>().unwrap();
         // First, check if we've already seen this template
         if self

+ 1 - 1
packages/core/src/lazynodes.rs

@@ -18,7 +18,7 @@ use crate::{innerlude::VNode, ScopeState};
 
 /// A concrete type provider for closures that build [`VNode`] structures.
 ///
-/// This struct wraps lazy structs that build [`VNode`] trees Normally, we cannot perform a blanket implementation over
+/// This struct wraps lazy structs that build [`VNode`] trees. Normally, we cannot perform a blanket implementation over
 /// closures, but if we wrap the closure in a concrete type, we can use it for different branches in matching.
 ///
 ///

+ 11 - 0
packages/liveview/Cargo.toml

@@ -38,6 +38,10 @@ salvo = { version = "0.63.0", optional = true, features = ["websocket"] }
 once_cell = "1.17.1"
 async-trait = "0.1.71"
 
+# rocket
+rocket = { version = "0.5.0", optional = true }
+rocket_ws = { version = "0.1.0", optional = true }
+
 # actix is ... complicated?
 # actix-files = { version = "0.6.2", optional = true }
 # actix-web = { version = "4.2.1", optional = true }
@@ -50,12 +54,15 @@ dioxus = { workspace = true }
 warp = "0.3.3"
 axum = { version = "0.6.1", features = ["ws"] }
 salvo = { version = "0.63.0", features = ["affix", "websocket"] }
+rocket = "0.5.0"
+rocket_ws = "0.1.0"
 tower = "0.4.13"
 
 [features]
 default = ["hot-reload"]
 # actix = ["actix-files", "actix-web", "actix-ws"]
 hot-reload = ["dioxus-hot-reload"]
+rocket = ["dep:rocket", "dep:rocket_ws"]
 
 [[example]]
 name = "axum"
@@ -68,3 +75,7 @@ required-features = ["salvo"]
 [[example]]
 name = "warp"
 required-features = ["warp"]
+
+[[example]]
+name = "rocket"
+required-features = ["rocket"]

+ 1 - 0
packages/liveview/README.md

@@ -28,6 +28,7 @@ The current backend frameworks supported include:
 - Axum
 - Warp
 - Salvo
+- Rocket
 
 Dioxus-LiveView exports some primitives to wire up an app into an existing backend framework.
 

+ 76 - 0
packages/liveview/examples/rocket.rs

@@ -0,0 +1,76 @@
+#[macro_use]
+extern crate rocket;
+
+use dioxus::prelude::*;
+use dioxus_liveview::LiveViewPool;
+use rocket::response::content::RawHtml;
+use rocket::{Config, Rocket, State};
+use rocket_ws::{Channel, WebSocket};
+
+fn app(cx: Scope) -> Element {
+    let mut num = use_state(cx, || 0);
+
+    cx.render(rsx! {
+        div {
+            "hello Rocket! {num}"
+            button { onclick: move |_| num += 1, "Increment" }
+        }
+    })
+}
+
+fn index_page_with_glue(glue: &str) -> RawHtml<String> {
+    RawHtml(format!(
+        r#"
+        <!DOCTYPE html>
+        <html>
+            <head> <title>Dioxus LiveView with Rocket</title>  </head>
+            <body> <div id="main"></div> </body>
+            {glue}
+        </html>
+        "#,
+        glue = glue
+    ))
+}
+
+#[get("/")]
+async fn index(config: &Config) -> RawHtml<String> {
+    index_page_with_glue(&dioxus_liveview::interpreter_glue(&format!(
+        "ws://{addr}:{port}/ws",
+        addr = config.address,
+        port = config.port,
+    )))
+}
+
+#[get("/as-path")]
+async fn as_path() -> RawHtml<String> {
+    index_page_with_glue(&dioxus_liveview::interpreter_glue("/ws"))
+}
+
+#[get("/ws")]
+fn ws(ws: WebSocket, pool: &State<LiveViewPool>) -> Channel<'static> {
+    let pool = pool.inner().to_owned();
+
+    ws.channel(move |stream| {
+        Box::pin(async move {
+            let _ = pool
+                .launch(dioxus_liveview::rocket_socket(stream), app)
+                .await;
+            Ok(())
+        })
+    })
+}
+
+#[tokio::main]
+async fn main() {
+    let view = dioxus_liveview::LiveViewPool::new();
+
+    Rocket::build()
+        .manage(view)
+        .mount("/", routes![index, as_path, ws])
+        .ignite()
+        .await
+        .expect("Failed to ignite rocket")
+        .launch()
+        .await
+        .expect("Failed to launch rocket");
+}

+ 25 - 0
packages/liveview/src/adapters/rocket_adapter.rs

@@ -0,0 +1,25 @@
+use crate::{LiveViewError, LiveViewSocket};
+use rocket::futures::{SinkExt, StreamExt};
+use rocket_ws::{result::Error, stream::DuplexStream, Message};
+
+/// Convert a rocket websocket into a LiveViewSocket
+///
+/// This is required to launch a LiveView app using the rocket web framework
+pub fn rocket_socket(stream: DuplexStream) -> impl LiveViewSocket {
+    stream
+        .map(transform_rx)
+        .with(transform_tx)
+        .sink_map_err(|_| LiveViewError::SendingFailed)
+}
+
+fn transform_rx(message: Result<Message, Error>) -> Result<Vec<u8>, LiveViewError> {
+    message
+        .map_err(|_| LiveViewError::SendingFailed)?
+        .into_text()
+        .map(|s| s.into_bytes())
+        .map_err(|_| LiveViewError::SendingFailed)
+}
+
+async fn transform_tx(message: Vec<u8>) -> Result<Message, Error> {
+    Ok(Message::Text(String::from_utf8_lossy(&message).to_string()))
+}

+ 5 - 0
packages/liveview/src/lib.rs

@@ -18,6 +18,11 @@ pub mod adapters {
 
     #[cfg(feature = "salvo")]
     pub use salvo_adapter::*;
+
+    #[cfg(feature = "rocket")]
+    pub mod rocket_adapter;
+    #[cfg(feature = "rocket")]
+    pub use rocket_adapter::*;
 }
 
 pub use adapters::*;

+ 11 - 6
packages/rsx/src/lib.rs

@@ -204,12 +204,17 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
             None => quote! { None },
         };
 
-        let spndbg = format!("{:?}", self.roots[0].span());
-        let root_col = spndbg
-            .rsplit_once("..")
-            .and_then(|(_, after)| after.split_once(')').map(|(before, _)| before))
-            .unwrap_or_default();
-
+        let root_col = match self.roots.first() {
+            Some(first_root) => {
+                let first_root_span = format!("{:?}", first_root.span());
+                first_root_span
+                    .rsplit_once("..")
+                    .and_then(|(_, after)| after.split_once(')').map(|(before, _)| before))
+                    .unwrap_or_default()
+                    .to_string()
+            }
+            _ => "0".to_string(),
+        };
         let root_printer = self.roots.iter().enumerate().map(|(idx, root)| {
             context.current_path.push(idx as u8);
             let out = context.render_static_node(root);

+ 6 - 0
packages/signals/src/signal.rs

@@ -495,3 +495,9 @@ impl<T> Deref for ReadOnlySignal<T> {
         reference_to_closure as &Self::Target
     }
 }
+
+impl<T> From<Signal<T>> for ReadOnlySignal<T> {
+    fn from(signal: Signal<T>) -> Self {
+        Self::new(signal)
+    }
+}

+ 88 - 0
packages/ssr/src/renderer.rs

@@ -238,6 +238,94 @@ fn to_string_works() {
     assert_eq!(out, "<div class=\"asdasdasd\" class=\"asdasdasd\" id=\"id-123\">Hello world 1 --&gt;123&lt;-- Hello world 2<div>nest 1</div><div></div><div>nest 2</div>&lt;/diiiiiiiiv&gt;<div>finalize 0</div><div>finalize 1</div><div>finalize 2</div><div>finalize 3</div><div>finalize 4</div></div>");
 }
 
+#[test]
+fn empty_for_loop_works() {
+    use dioxus::prelude::*;
+
+    fn app(cx: Scope) -> Element {
+        render! {
+            div { class: "asdasdasd",
+                for _ in (0..5) {
+
+                }
+            }
+        }
+    }
+
+    let mut dom = VirtualDom::new(app);
+    _ = dom.rebuild();
+
+    let mut renderer = Renderer::new();
+    let out = renderer.render(&dom);
+
+    for item in renderer.template_cache.iter() {
+        if item.1.segments.len() > 5 {
+            assert_eq!(
+                item.1.segments,
+                vec![
+                    PreRendered("<div class=\"asdasdasd\"".into(),),
+                    Attr(0,),
+                    StyleMarker {
+                        inside_style_tag: false,
+                    },
+                    PreRendered(">".into()),
+                    InnerHtmlMarker,
+                    PreRendered("</div>".into(),),
+                ]
+            );
+        }
+    }
+
+    use Segment::*;
+
+    assert_eq!(out, "<div class=\"asdasdasd\"></div>");
+}
+
+#[test]
+fn empty_render_works() {
+    use dioxus::prelude::*;
+
+    fn app(cx: Scope) -> Element {
+        render! {}
+    }
+
+    let mut dom = VirtualDom::new(app);
+    _ = dom.rebuild();
+
+    let mut renderer = Renderer::new();
+    let out = renderer.render(&dom);
+
+    for item in renderer.template_cache.iter() {
+        if item.1.segments.len() > 5 {
+            assert_eq!(item.1.segments, vec![]);
+        }
+    }
+    assert_eq!(out, "");
+}
+
+#[test]
+fn empty_rsx_works() {
+    use dioxus::prelude::*;
+
+    fn app(_: Scope) -> Element {
+        rsx! {};
+        None
+    }
+
+    let mut dom = VirtualDom::new(app);
+    _ = dom.rebuild();
+
+    let mut renderer = Renderer::new();
+    let out = renderer.render(&dom);
+
+    for item in renderer.template_cache.iter() {
+        if item.1.segments.len() > 5 {
+            assert_eq!(item.1.segments, vec![]);
+        }
+    }
+    assert_eq!(out, "");
+}
+
 pub(crate) const BOOL_ATTRS: &[&str] = &[
     "allowfullscreen",
     "allowpaymentrequest",

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

@@ -256,17 +256,21 @@ impl WebsysDom {
         i.flush();
 
         for id in to_mount {
-            let node = get_node(id.0 as u32);
-            if let Some(element) = node.dyn_ref::<Element>() {
-                let data: MountedData = element.into();
-                let data = Rc::new(data);
-                let _ = self.event_channel.unbounded_send(UiEvent {
-                    name: "mounted".to_string(),
-                    bubbles: false,
-                    element: id,
-                    data,
-                });
-            }
+            self.send_mount_event(id);
+        }
+    }
+
+    pub(crate) fn send_mount_event(&self, id: ElementId) {
+        let node = get_node(id.0 as u32);
+        if let Some(element) = node.dyn_ref::<Element>() {
+            let data: MountedData = element.into();
+            let data = Rc::new(data);
+            let _ = self.event_channel.unbounded_send(UiEvent {
+                name: "mounted".to_string(),
+                bubbles: false,
+                element: id,
+                data,
+            });
         }
     }
 }

+ 14 - 5
packages/web/src/rehydrate.rs

@@ -125,6 +125,7 @@ impl WebsysDom {
                 children, attrs, ..
             } => {
                 let mut mounted_id = None;
+                let mut should_send_mount_event = true;
                 for attr in *attrs {
                     if let dioxus_core::TemplateAttribute::Dynamic { id } = attr {
                         let attribute = &vnode.dynamic_attrs[*id];
@@ -134,16 +135,24 @@ impl WebsysDom {
                         let name = attribute.name;
                         if let AttributeValue::Listener(_) = value {
                             let event_name = &name[2..];
-                            self.interpreter.new_event_listener(
-                                event_name,
-                                id.0 as u32,
-                                event_bubbles(event_name) as u8,
-                            );
+                            match event_name {
+                                "mounted" => should_send_mount_event = true,
+                                _ => {
+                                    self.interpreter.new_event_listener(
+                                        event_name,
+                                        id.0 as u32,
+                                        event_bubbles(event_name) as u8,
+                                    );
+                                }
+                            }
                         }
                     }
                 }
                 if let Some(id) = mounted_id {
                     set_node(hydrated, id, current_child.clone()?);
+                    if should_send_mount_event {
+                        self.send_mount_event(id);
+                    }
                 }
                 if !children.is_empty() {
                     let mut children_current_child = current_child