mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-26 14:10:20 +00:00
merge upstream
This commit is contained in:
parent
f34053c18f
commit
0a7873fcd0
11 changed files with 166 additions and 28 deletions
|
@ -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">
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
///
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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
packages/liveview/examples/rocket.rs
Normal file
76
packages/liveview/examples/rocket.rs
Normal 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");
|
||||
}
|
25
packages/liveview/src/adapters/rocket_adapter.rs
Normal file
25
packages/liveview/src/adapters/rocket_adapter.rs
Normal 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()))
|
||||
}
|
|
@ -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::*;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue