Bladeren bron

merge upstream

Evan Almloff 1 jaar geleden
bovenliggende
commit
0a7873fcd0

+ 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">

+ 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.
 ///
 ///

+ 12 - 1
packages/liveview/Cargo.toml

@@ -38,6 +38,10 @@ salvo = { version = "0.44.1", optional = true, features = ["ws"] }
 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 }
@@ -49,13 +53,16 @@ tokio = { workspace = true, features = ["full"] }
 dioxus = { workspace = true }
 warp = "0.3.3"
 axum = { version = "0.6.1", features = ["ws"] }
-salvo = { version = "0.44.1", features = ["affix", "ws"] }
+# salvo = { version = "0.44.1", features = ["affix", "ws"] }
+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::*;

+ 4 - 1
packages/ssr/src/renderer.rs

@@ -106,7 +106,10 @@ impl Renderer {
 
                     if self.pre_render {
                         if let AttributeValue::Listener(_) = &attr.value {
-                            accumulated_listeners.push(attr.name);
+                            // The onmounted event doesn't need a DOM listener
+                            if attr.name != "onmounted" {
+                                accumulated_listeners.push(attr.name);
+                            }
                         }
                     }
                 }

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

@@ -255,17 +255,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,
+            });
         }
     }
 }

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

@@ -253,13 +253,6 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
             pin_mut!(work);
 
             #[cfg(all(feature = "hot_reload", debug_assertions))]
-            // futures_util::select! {
-            //     _ = work => (None, None),
-            //     new_template = hotreload_rx.next() => {
-            //         (None, new_template)
-            //     }
-            //     evt = rx.next() =>
-            // }
             match select(work, select(hotreload_rx.next(), rx.next())).await {
                 Either::Left((_, _)) => (None, None),
                 Either::Right((Either::Left((new_template, _)), _)) => (None, new_template),

+ 26 - 6
packages/web/src/rehydrate.rs

@@ -14,12 +14,17 @@ impl WebsysDom {
     pub fn rehydrate(&mut self, dom: &VirtualDom) -> Result<(), RehydrationError> {
         let root_scope = dom.base_scope();
         let mut ids = Vec::new();
+        let mut to_mount = Vec::new();
 
         // Recursively rehydrate the dom from the VirtualDom
-        self.rehydrate_scope(root_scope, dom, &mut ids)?;
+        self.rehydrate_scope(root_scope, dom, &mut ids, &mut to_mount)?;
 
         dioxus_interpreter_js::hydrate(ids);
 
+        for id in to_mount {
+            self.send_mount_event(id);
+        }
+
         Ok(())
     }
 
@@ -28,12 +33,13 @@ impl WebsysDom {
         scope: &ScopeState,
         dom: &VirtualDom,
         ids: &mut Vec<u32>,
+        to_mount: &mut Vec<ElementId>,
     ) -> Result<(), RehydrationError> {
         let vnode = match scope.root_node() {
             dioxus_core::RenderReturn::Ready(ready) => ready,
             _ => return Err(VNodeNotInitialized),
         };
-        self.rehydrate_vnode(dom, vnode, ids)
+        self.rehydrate_vnode(dom, vnode, ids, to_mount)
     }
 
     fn rehydrate_vnode(
@@ -41,6 +47,7 @@ impl WebsysDom {
         dom: &VirtualDom,
         vnode: &VNode,
         ids: &mut Vec<u32>,
+        to_mount: &mut Vec<ElementId>,
     ) -> Result<(), RehydrationError> {
         for (i, root) in vnode.template.get().roots.iter().enumerate() {
             self.rehydrate_template_node(
@@ -48,6 +55,7 @@ impl WebsysDom {
                 vnode,
                 root,
                 ids,
+                to_mount,
                 Some(*vnode.root_ids.borrow().get(i).ok_or(VNodeNotInitialized)?),
             )?;
         }
@@ -60,6 +68,7 @@ impl WebsysDom {
         vnode: &VNode,
         node: &TemplateNode,
         ids: &mut Vec<u32>,
+        to_mount: &mut Vec<ElementId>,
         root_id: Option<ElementId>,
     ) -> Result<(), RehydrationError> {
         tracing::trace!("rehydrate template node: {:?}", node);
@@ -73,6 +82,11 @@ impl WebsysDom {
                         let attribute = &vnode.dynamic_attrs[*id];
                         let id = attribute.mounted_element();
                         mounted_id = Some(id);
+                        if let dioxus_core::AttributeValue::Listener(_) = attribute.value {
+                            if attribute.name == "onmounted" {
+                                to_mount.push(id);
+                            }
+                        }
                     }
                 }
                 if let Some(id) = mounted_id {
@@ -80,12 +94,12 @@ impl WebsysDom {
                 }
                 if !children.is_empty() {
                     for child in *children {
-                        self.rehydrate_template_node(dom, vnode, child, ids, None)?;
+                        self.rehydrate_template_node(dom, vnode, child, ids, to_mount, None)?;
                     }
                 }
             }
             TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => {
-                self.rehydrate_dynamic_node(dom, &vnode.dynamic_nodes[*id], ids)?;
+                self.rehydrate_dynamic_node(dom, &vnode.dynamic_nodes[*id], ids, to_mount)?;
             }
             _ => {}
         }
@@ -97,6 +111,7 @@ impl WebsysDom {
         dom: &VirtualDom,
         dynamic: &DynamicNode,
         ids: &mut Vec<u32>,
+        to_mount: &mut Vec<ElementId>,
     ) -> Result<(), RehydrationError> {
         tracing::trace!("rehydrate dynamic node: {:?}", dynamic);
         match dynamic {
@@ -108,11 +123,16 @@ impl WebsysDom {
             }
             dioxus_core::DynamicNode::Component(comp) => {
                 let scope = comp.mounted_scope().ok_or(VNodeNotInitialized)?;
-                self.rehydrate_scope(dom.get_scope(scope).ok_or(VNodeNotInitialized)?, dom, ids)?;
+                self.rehydrate_scope(
+                    dom.get_scope(scope).ok_or(VNodeNotInitialized)?,
+                    dom,
+                    ids,
+                    to_mount,
+                )?;
             }
             dioxus_core::DynamicNode::Fragment(fragment) => {
                 for vnode in *fragment {
-                    self.rehydrate_vnode(dom, vnode, ids)?;
+                    self.rehydrate_vnode(dom, vnode, ids, to_mount)?;
                 }
             }
         }