Skip to content

Commit

Permalink
Merge pull request #1948 from atty303/fullstack-wasm
Browse files Browse the repository at this point in the history
feat(fullstack): support wasm target
  • Loading branch information
ealmloff authored Feb 19, 2024
2 parents 3fef42a + c4a4a31 commit c0f2e83
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 160 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/cli/src/server/web/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,8 +367,8 @@ async fn start_server(
#[cfg(feature = "plugin")]
PluginManager::on_serve_start(_config)?;

// Parse address
let addr = format!("0.0.0.0:{}", port).parse().unwrap();
// Bind the server to `[::]` and it will LISTEN for both IPv4 and IPv6. (required IPv6 dual stack)
let addr = format!("[::]:{}", port).parse().unwrap();

// Open the browser
if start_browser {
Expand Down
9 changes: 6 additions & 3 deletions packages/fullstack/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,13 @@ dioxus-mobile = { workspace = true, optional = true }
tracing = { workspace = true }
tracing-futures = { workspace = true, optional = true }
once_cell = "1.17.1"
tokio = { workspace = true, features = ["rt", "sync", "rt-multi-thread"], optional = true }
tokio-util = { version = "0.7.8", features = ["rt"], optional = true }
anymap = { version = "0.12.1", optional = true }

serde = "1.0.159"
serde_json = { version = "1.0.95", optional = true }
tokio-stream = { version = "0.1.12", features = ["sync"], optional = true }
futures-util = { workspace = true, default-features = false, optional = true }
futures-util = { workspace = true, default-features = false }
ciborium = "0.2.1"
base64 = "0.21.0"

Expand All @@ -59,12 +58,16 @@ web-sys = { version = "0.3.61", optional = true, features = ["Window", "Document

dioxus-cli-config = { workspace = true, optional = true }

[target.'cfg(target_arch = "wasm32")'.dependencies]
tokio = { workspace = true, features = ["rt", "sync"], optional = true }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
dioxus-hot-reload = { workspace = true }
tokio = { workspace = true, features = ["rt", "sync", "rt-multi-thread"], optional = true }

[features]
default = ["hot-reload"]
hot-reload = ["serde_json", "futures-util"]
hot-reload = ["serde_json"]
web = ["dioxus-web", "web-sys"]
desktop = ["dioxus-desktop"]
mobile = ["dioxus-mobile"]
Expand Down
65 changes: 29 additions & 36 deletions packages/fullstack/src/axum_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ use axum::{
};
use dioxus_lib::prelude::VirtualDom;
use http::header::*;
use server_fn::error::NoCustomError;
use server_fn::error::ServerFnErrorSerde;

use std::sync::Arc;

use crate::{
Expand Down Expand Up @@ -453,20 +452,6 @@ pub async fn hot_reload_handler(ws: axum::extract::WebSocketUpgrade) -> impl Int
})
}

fn get_local_pool() -> tokio_util::task::LocalPoolHandle {
use once_cell::sync::OnceCell;
static LOCAL_POOL: OnceCell<tokio_util::task::LocalPoolHandle> = OnceCell::new();
LOCAL_POOL
.get_or_init(|| {
tokio_util::task::LocalPoolHandle::new(
std::thread::available_parallelism()
.map(Into::into)
.unwrap_or(1),
)
})
.clone()
}

/// A handler for Dioxus server functions. This will run the server function and return the result.
async fn handle_server_fns_inner(
path: &str,
Expand All @@ -475,15 +460,13 @@ async fn handle_server_fns_inner(
) -> impl IntoResponse {
use server_fn::middleware::Service;

let (tx, rx) = tokio::sync::oneshot::channel();
let path_string = path.to_string();

get_local_pool().spawn_pinned(move || async move {
let (parts, body) = req.into_parts();
let req = Request::from_parts(parts.clone(), body);

let future = move || async move {
let (parts, body) = req.into_parts();
let req = Request::from_parts(parts.clone(), body);

let res = if let Some(mut service) =
if let Some(mut service) =
server_fn::axum::get_server_fn_service(&path_string)
{

Expand Down Expand Up @@ -538,18 +521,28 @@ async fn handle_server_fns_inner(
}
)
}
.expect("could not build Response");

_ = tx.send(res);
});

rx.await.unwrap_or_else(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
ServerFnError::<NoCustomError>::ServerError(e.to_string())
.ser()
.unwrap_or_default(),
)
.into_response()
})
.expect("could not build Response")
};
#[cfg(target_arch = "wasm32")]
{
use futures_util::future::FutureExt;

let result = tokio::task::spawn_local(future);
let result = result.then(|f| async move { f.unwrap() });
result.await.unwrap_or_else(|e| {
use server_fn::error::NoCustomError;
use server_fn::error::ServerFnErrorSerde;
(
StatusCode::INTERNAL_SERVER_ERROR,
ServerFnError::<NoCustomError>::ServerError(e.to_string())
.ser()
.unwrap_or_default(),
)
.into_response()
})
}
#[cfg(not(target_arch = "wasm32"))]
{
future().await
}
}
2 changes: 1 addition & 1 deletion packages/fullstack/src/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub fn launch(
vdom
};

#[cfg(feature = "server")]
#[cfg(all(feature = "server", not(target_arch = "wasm32")))]
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async move {
Expand Down
7 changes: 6 additions & 1 deletion packages/fullstack/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ mod config;
mod hooks;
pub mod launch;

#[cfg(all(debug_assertions, feature = "hot-reload", feature = "server"))]
#[cfg(all(
debug_assertions,
feature = "hot-reload",
feature = "server",
not(target_arch = "wasm32")
))]
mod hot_reload;
pub use config::*;

Expand Down
188 changes: 98 additions & 90 deletions packages/fullstack/src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,33 @@ use dioxus_ssr::{
incremental::{IncrementalRendererConfig, RenderFreshness, WrapBody},
Renderer,
};
use std::future::Future;
use std::sync::Arc;
use std::sync::RwLock;
use tokio::task::spawn_blocking;
use tokio::task::JoinHandle;

use crate::prelude::*;
use dioxus_lib::prelude::*;

fn spawn_platform<Fut>(f: impl FnOnce() -> Fut + Send + 'static) -> JoinHandle<Fut::Output>
where
Fut: Future + 'static,
Fut::Output: Send + 'static,
{
#[cfg(not(target_arch = "wasm32"))]
{
tokio::task::spawn_blocking(move || {
tokio::runtime::Runtime::new()
.expect("couldn't spawn runtime")
.block_on(f())
})
}
#[cfg(target_arch = "wasm32")]
{
tokio::task::spawn_local(f())
}
}

enum SsrRendererPool {
Renderer(RwLock<Vec<Renderer>>),
Incremental(RwLock<Vec<dioxus_ssr::incremental::IncrementalRenderer>>),
Expand All @@ -37,53 +57,45 @@ impl SsrRendererPool {

let (tx, rx) = tokio::sync::oneshot::channel();

spawn_blocking(move || {
tokio::runtime::Runtime::new()
.expect("couldn't spawn runtime")
.block_on(async move {
let mut vdom = virtual_dom_factory();
let mut to = WriteBuffer { buffer: Vec::new() };
// before polling the future, we need to set the context
let prev_context =
SERVER_CONTEXT.with(|ctx| ctx.replace(server_context));
// poll the future, which may call server_context()
tracing::info!("Rebuilding vdom");
vdom.rebuild(&mut NoOpMutations);
vdom.wait_for_suspense().await;
tracing::info!("Suspense resolved");
// after polling the future, we need to restore the context
SERVER_CONTEXT.with(|ctx| ctx.replace(prev_context));

if let Err(err) = wrapper.render_before_body(&mut *to) {
let _ = tx.send(Err(err));
return;
}
if let Err(err) = renderer.render_to(&mut to, &vdom) {
let _ = tx.send(Err(
dioxus_ssr::incremental::IncrementalRendererError::RenderError(
err,
),
));
return;
}
if let Err(err) = wrapper.render_after_body(&mut *to) {
let _ = tx.send(Err(err));
return;
}
match String::from_utf8(to.buffer) {
Ok(html) => {
let _ =
tx.send(Ok((renderer, RenderFreshness::now(None), html)));
}
Err(err) => {
_ = tx.send(Err(
dioxus_ssr::incremental::IncrementalRendererError::Other(
Box::new(err),
),
));
}
}
});
spawn_platform(move || async move {
let mut vdom = virtual_dom_factory();
let mut to = WriteBuffer { buffer: Vec::new() };
// before polling the future, we need to set the context
let prev_context = SERVER_CONTEXT.with(|ctx| ctx.replace(server_context));
// poll the future, which may call server_context()
tracing::info!("Rebuilding vdom");
vdom.rebuild(&mut NoOpMutations);
vdom.wait_for_suspense().await;
tracing::info!("Suspense resolved");
// after polling the future, we need to restore the context
SERVER_CONTEXT.with(|ctx| ctx.replace(prev_context));

if let Err(err) = wrapper.render_before_body(&mut *to) {
let _ = tx.send(Err(err));
return;
}
if let Err(err) = renderer.render_to(&mut to, &vdom) {
let _ = tx.send(Err(
dioxus_ssr::incremental::IncrementalRendererError::RenderError(err),
));
return;
}
if let Err(err) = wrapper.render_after_body(&mut *to) {
let _ = tx.send(Err(err));
return;
}
match String::from_utf8(to.buffer) {
Ok(html) => {
let _ = tx.send(Ok((renderer, RenderFreshness::now(None), html)));
}
Err(err) => {
_ = tx.send(Err(
dioxus_ssr::incremental::IncrementalRendererError::Other(Box::new(
err,
)),
));
}
}
});
let (renderer, freshness, html) = rx.await.unwrap()?;
pool.write().unwrap().push(renderer);
Expand All @@ -98,53 +110,49 @@ impl SsrRendererPool {
let (tx, rx) = tokio::sync::oneshot::channel();

let server_context = server_context.clone();
spawn_blocking(move || {
tokio::runtime::Runtime::new()
.expect("couldn't spawn runtime")
.block_on(async move {
let mut to = WriteBuffer { buffer: Vec::new() };
match renderer
.render(
route,
virtual_dom_factory,
&mut *to,
|vdom| {
Box::pin(async move {
// before polling the future, we need to set the context
let prev_context = SERVER_CONTEXT
.with(|ctx| ctx.replace(Box::new(server_context)));
// poll the future, which may call server_context()
tracing::info!("Rebuilding vdom");
vdom.rebuild(&mut NoOpMutations);
vdom.wait_for_suspense().await;
tracing::info!("Suspense resolved");
// after polling the future, we need to restore the context
SERVER_CONTEXT.with(|ctx| ctx.replace(prev_context));
})
},
&wrapper,
)
.await
{
Ok(freshness) => {
match String::from_utf8(to.buffer).map_err(|err| {
dioxus_ssr::incremental::IncrementalRendererError::Other(
Box::new(err),
)
}) {
Ok(html) => {
let _ = tx.send(Ok((freshness, html)));
}
Err(err) => {
let _ = tx.send(Err(err));
}
}
spawn_platform(move || async move {
let mut to = WriteBuffer { buffer: Vec::new() };
match renderer
.render(
route,
virtual_dom_factory,
&mut *to,
|vdom| {
Box::pin(async move {
// before polling the future, we need to set the context
let prev_context = SERVER_CONTEXT
.with(|ctx| ctx.replace(Box::new(server_context)));
// poll the future, which may call server_context()
tracing::info!("Rebuilding vdom");
vdom.rebuild(&mut NoOpMutations);
vdom.wait_for_suspense().await;
tracing::info!("Suspense resolved");
// after polling the future, we need to restore the context
SERVER_CONTEXT.with(|ctx| ctx.replace(prev_context));
})
},
&wrapper,
)
.await
{
Ok(freshness) => {
match String::from_utf8(to.buffer).map_err(|err| {
dioxus_ssr::incremental::IncrementalRendererError::Other(Box::new(
err,
))
}) {
Ok(html) => {
let _ = tx.send(Ok((freshness, html)));
}
Err(err) => {
let _ = tx.send(Err(err));
}
}
})
}
Err(err) => {
let _ = tx.send(Err(err));
}
}
});
let (freshness, html) = rx.await.unwrap()?;

Expand Down
Loading

0 comments on commit c0f2e83

Please sign in to comment.