merge upstream

This commit is contained in:
Evan Almloff 2023-12-30 14:12:21 -06:00
parent f34053c18f
commit 0a7873fcd0
11 changed files with 166 additions and 28 deletions

View file

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

View file

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

View file

@ -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"]

View file

@ -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.

View file

@ -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");
}

View file

@ -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()))
}

View file

@ -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::*;

View file

@ -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);
}
}
}
}

View file

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

View file

@ -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),

View file

@ -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)?;
}
}
}