瀏覽代碼

create server launch macro

Evan Almloff 2 年之前
父節點
當前提交
99674fcf94

+ 1 - 1
packages/fullstack/Cargo.toml

@@ -59,7 +59,7 @@ dioxus-hot-reload = { path = "../hot-reload" }
 web-sys = { version = "0.3.61", features = ["Window", "Document", "Element", "HtmlDocument", "Storage", "console"] }
 
 [features]
-default = ["hot-reload", "default-tls", "router"]
+default = ["hot-reload", "default-tls"]
 router = ["dioxus-router"]
 hot-reload = ["serde_json", "tokio-stream", "futures-util"]
 warp = ["dep:warp", "http-body", "ssr"]

+ 2 - 2
packages/fullstack/examples/axum-desktop/src/client.rs

@@ -1,6 +1,6 @@
 // Run with:
 // ```bash
-// cargo run --bin client --features="desktop"
+// cargo run --bin client --features desktop
 // ```
 
 use axum_desktop::*;
@@ -8,6 +8,6 @@ use dioxus_fullstack::prelude::server_fn::set_server_url;
 
 fn main() {
     // Set the url of the server where server functions are hosted.
-    set_server_url("http://localhost:8080");
+    set_server_url("http://127.0.0.0:8080");
     dioxus_desktop::launch(app)
 }

+ 1 - 2
packages/fullstack/examples/axum-desktop/src/server.rs

@@ -1,9 +1,8 @@
 // Run with:
 // ```bash
-// cargo run --bin server --features="ssr"
+// cargo run --bin server --features ssr
 // ```
 
-use axum_desktop::*;
 use dioxus_fullstack::prelude::*;
 
 #[tokio::main]

+ 1 - 1
packages/fullstack/examples/axum-hello-world/Cargo.toml

@@ -16,6 +16,6 @@ serde = "1.0.159"
 execute = "0.2.12"
 
 [features]
-default = ["web"]
+default = []
 ssr = ["axum", "tokio", "dioxus-fullstack/axum"]
 web = ["dioxus-web"]

+ 10 - 44
packages/fullstack/examples/axum-hello-world/src/main.rs

@@ -2,55 +2,14 @@
 //!
 //! ```sh
 //! dioxus build --features web
-//! cargo run --features ssr --no-default-features
+//! cargo run --features ssr
 //! ```
 
-#![allow(non_snake_case)]
+#![allow(non_snake_case, unused)]
 use dioxus::prelude::*;
-use dioxus_fullstack::prelude::*;
+use dioxus_fullstack::{launch, prelude::*};
 use serde::{Deserialize, Serialize};
 
-fn main() {
-    #[cfg(feature = "web")]
-    dioxus_web::launch_with_props(
-        app,
-        get_root_props_from_document().unwrap_or_default(),
-        dioxus_web::Config::new().hydrate(true),
-    );
-    #[cfg(feature = "ssr")]
-    {
-        // Start hot reloading
-        hot_reload_init!(dioxus_hot_reload::Config::new().with_rebuild_callback(|| {
-            execute::shell("dioxus build --features web")
-                .spawn()
-                .unwrap()
-                .wait()
-                .unwrap();
-            execute::shell("cargo run --features ssr --no-default-features")
-                .spawn()
-                .unwrap();
-            true
-        }));
-
-        tokio::runtime::Runtime::new()
-            .unwrap()
-            .block_on(async move {
-                let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080));
-                axum::Server::bind(&addr)
-                    .serve(
-                        axum::Router::new()
-                            .serve_dioxus_application(
-                                "",
-                                ServeConfigBuilder::new(app, AppProps { count: 12345 }).build(),
-                            )
-                            .into_make_service(),
-                    )
-                    .await
-                    .unwrap();
-            });
-    }
-}
-
 #[derive(Props, PartialEq, Debug, Default, Serialize, Deserialize, Clone)]
 struct AppProps {
     count: i32,
@@ -97,3 +56,10 @@ async fn post_server_data(cx: DioxusServerContext, data: String) -> Result<(), S
 async fn get_server_data() -> Result<String, ServerFnError> {
     Ok("Hello from the server!".to_string())
 }
+
+fn main() {
+    launch!(@[([127, 0, 0, 1], 8080)], app, {
+        server_cfg: ServeConfigBuilder::new(app, (AppProps { count: 0 })),
+        incremental,
+    });
+}

+ 5 - 8
packages/fullstack/examples/axum-router/Cargo.toml

@@ -7,18 +7,15 @@ publish = false
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-dioxus-web = { path = "../../../web", features=["hydrate"], optional = true }
+dioxus-web = { path = "../../../web", features = ["hydrate"], optional = true }
 dioxus = { path = "../../../dioxus" }
 dioxus-router = { path = "../../../router" }
-dioxus-fullstack = { path = "../../" }
+dioxus-fullstack = { path = "../../", features = ["router"] }
 axum = { version = "0.6.12", optional = true }
 tokio = { version = "1.27.0", features = ["full"], optional = true }
-serde = "1.0.159"
-tower-http = { version = "0.4.0", features = ["fs"], optional = true }
-http = { version = "0.2.9", optional = true }
-execute = "0.2.12"
+serde = { version = "1.0.159", features = ["derive"] }
 
 [features]
-default = ["web"]
-ssr = ["axum", "tokio", "dioxus-fullstack/axum", "tower-http", "http"]
+default = []
+ssr = ["axum", "tokio", "dioxus-fullstack/axum"]
 web = ["dioxus-web", "dioxus-router/web"]

+ 17 - 51
packages/fullstack/examples/axum-router/src/main.rs

@@ -2,76 +2,37 @@
 //!
 //! ```sh
 //! dioxus build --features web
-//! cargo run --features ssr --no-default-features
+//! cargo run --features ssr
 //! ```
 
 #![allow(non_snake_case)]
 use dioxus::prelude::*;
 use dioxus_fullstack::prelude::*;
 use dioxus_router::prelude::*;
-use serde::{Deserialize, Serialize};
 
 fn main() {
-    #[cfg(feature = "web")]
-    dioxus_web::launch_with_props(
-        Router,
-        Default::default(),
-        dioxus_web::Config::new().hydrate(true),
-    );
-    #[cfg(feature = "ssr")]
-    {
-        // Start hot reloading
-        hot_reload_init!(dioxus_hot_reload::Config::new().with_rebuild_callback(|| {
-            execute::shell("dioxus build --features web")
-                .spawn()
-                .unwrap()
-                .wait()
-                .unwrap();
-            execute::shell("cargo run --features ssr --no-default-features")
-                .spawn()
-                .unwrap();
-            true
-        }));
-
-        tokio::runtime::Runtime::new()
-            .unwrap()
-            .block_on(async move {
-                let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080));
-
-                axum::Server::bind(&addr)
-                    .serve(
-                        axum::Router::new()
-                            .serve_dioxus_application(
-                                "",
-                                ServeConfigBuilder::new_with_router(
-                                    dioxus_fullstack::prelude::FullstackRouterConfig::<Route>::default()).incremental(IncrementalRendererConfig::default())
-                                .build(),
-                            )
-                            .into_make_service(),
-                    )
-                    .await
-                    .unwrap();
-            });
-    }
+    launch_router!(@[([127, 0, 0, 1], 8080)], Route, {
+        incremental,
+    });
 }
 
-#[derive(Clone, Routable, Serialize, Deserialize, Debug, PartialEq)]
+#[derive(Clone, Routable, Debug, PartialEq)]
 enum Route {
     #[route("/")]
     Home {},
-    #[route("/blog")]
-    Blog {},
+    #[route("/blog/:id")]
+    Blog { id: i32 },
 }
 
 #[inline_props]
-fn Blog(cx: Scope) -> Element {
+fn Blog(cx: Scope, id: i32) -> Element {
     render! {
         Link { target: Route::Home {}, "Go to counter" }
         table {
             tbody {
-                for _ in 0..100 {
+                for _ in 0..*id {
                     tr {
-                        for _ in 0..100 {
+                        for _ in 0..*id {
                             td { "hello world!" }
                         }
                     }
@@ -87,7 +48,12 @@ fn Home(cx: Scope) -> Element {
     let text = use_state(cx, || "...".to_string());
 
     cx.render(rsx! {
-        Link { target: Route::Blog {}, "Go to blog" }
+        Link {
+            target: Route::Blog {
+                id: *count.get()
+            },
+            "Go to blog"
+        }
         div {
             h1 { "High-Five counter: {count}" }
             button { onclick: move |_| count += 1, "Up high!" }
@@ -103,7 +69,7 @@ fn Home(cx: Scope) -> Element {
                         }
                     }
                 },
-                "Run a server function"
+                "Run server function!"
             }
             "Server said: {text}"
         }

+ 1 - 1
packages/fullstack/examples/salvo-hello-world/Cargo.toml

@@ -16,6 +16,6 @@ salvo = { version = "0.37.9", optional = true }
 execute = "0.2.12"
 
 [features]
-default = ["web"]
+default = []
 ssr = ["salvo", "tokio", "dioxus-fullstack/salvo"]
 web = ["dioxus-web"]

+ 5 - 36
packages/fullstack/examples/salvo-hello-world/src/main.rs

@@ -2,49 +2,18 @@
 //!
 //! ```sh
 //! dioxus build --features web
-//! cargo run --features ssr --no-default-features
+//! cargo run --features ssr
 //! ```
 
-#![allow(non_snake_case)]
+#![allow(non_snake_case, unused)]
 use dioxus::prelude::*;
 use dioxus_fullstack::prelude::*;
 use serde::{Deserialize, Serialize};
 
 fn main() {
-    #[cfg(feature = "web")]
-    dioxus_web::launch_with_props(
-        app,
-        get_root_props_from_document().unwrap_or_default(),
-        dioxus_web::Config::new().hydrate(true),
-    );
-    #[cfg(feature = "ssr")]
-    {
-        // Start hot reloading
-        hot_reload_init!(dioxus_hot_reload::Config::new().with_rebuild_callback(|| {
-            execute::shell("dioxus build --features web")
-                .spawn()
-                .unwrap()
-                .wait()
-                .unwrap();
-            execute::shell("cargo run --features ssr --no-default-features")
-                .spawn()
-                .unwrap();
-            true
-        }));
-
-        use salvo::prelude::*;
-        tokio::runtime::Runtime::new()
-            .unwrap()
-            .block_on(async move {
-                let router = Router::new().serve_dioxus_application(
-                    "",
-                    ServeConfigBuilder::new(app, AppProps { count: 12345 }),
-                );
-                Server::new(TcpListener::bind("127.0.0.1:8080"))
-                    .serve(router)
-                    .await;
-            });
-    }
+    launch!(@[([127, 0, 0, 1], 8080)], app, (AppProps { count: 5 }), {
+        incremental,
+    });
 }
 
 #[derive(Props, PartialEq, Debug, Default, Serialize, Deserialize, Clone)]

+ 1 - 1
packages/fullstack/examples/warp-hello-world/Cargo.toml

@@ -16,6 +16,6 @@ warp = { version = "0.3.3", optional = true }
 execute = "0.2.12"
 
 [features]
-default = ["web"]
+default = []
 ssr = ["warp", "tokio", "dioxus-fullstack/warp"]
 web = ["dioxus-web"]

+ 6 - 34
packages/fullstack/examples/warp-hello-world/src/main.rs

@@ -2,46 +2,18 @@
 //!
 //! ```sh
 //! dioxus build --features web
-//! cargo run --features ssr --no-default-features
+//! cargo run --features ssr
 //! ```
 
-#![allow(non_snake_case)]
+#![allow(non_snake_case, unused)]
 use dioxus::prelude::*;
 use dioxus_fullstack::prelude::*;
 use serde::{Deserialize, Serialize};
 
 fn main() {
-    #[cfg(feature = "web")]
-    dioxus_web::launch_with_props(
-        app,
-        get_root_props_from_document().unwrap_or_default(),
-        dioxus_web::Config::new().hydrate(true),
-    );
-    #[cfg(feature = "ssr")]
-    {
-        // Start hot reloading
-        hot_reload_init!(dioxus_hot_reload::Config::new().with_rebuild_callback(|| {
-            execute::shell("dioxus build --features web")
-                .spawn()
-                .unwrap()
-                .wait()
-                .unwrap();
-            execute::shell("cargo run --features ssr --no-default-features")
-                .spawn()
-                .unwrap();
-            true
-        }));
-
-        tokio::runtime::Runtime::new()
-            .unwrap()
-            .block_on(async move {
-                let routes = serve_dioxus_application(
-                    "",
-                    ServeConfigBuilder::new(app, AppProps { count: 12345 }),
-                );
-                warp::serve(routes).run(([127, 0, 0, 1], 8080)).await;
-            });
-    }
+    launch!(@[([127, 0, 0, 1], 8080)], app, (AppProps { count: 5 }), {
+        incremental,
+    });
 }
 
 #[derive(Props, PartialEq, Debug, Default, Serialize, Deserialize, Clone)]
@@ -56,7 +28,7 @@ fn app(cx: Scope<AppProps>) -> Element {
 
     cx.render(rsx! {
         h1 { "High-Five counter: {count}" }
-        button { onclick: move |_| count += 10, "Up high!" }
+        button { onclick: move |_| count += 1, "Up high!" }
         button { onclick: move |_| count -= 1, "Down low!" }
         button {
             onclick: move |_| {

+ 3 - 3
packages/fullstack/src/adapters/axum_adapter.rs

@@ -56,7 +56,7 @@
 
 use axum::{
     body::{self, Body, BoxBody, Full},
-    extract::{State, WebSocketUpgrade},
+    extract::State,
     handler::Handler,
     http::{Request, Response, StatusCode},
     response::IntoResponse,
@@ -327,7 +327,7 @@ where
                 Router::new()
                     .route(
                         "/disconnect",
-                        get(|ws: WebSocketUpgrade| async {
+                        get(|ws: axum::extract::WebSocketUpgrade| async {
                             ws.on_upgrade(|mut ws| async move {
                                 use axum::extract::ws::Message;
                                 let _ = ws.send(Message::Text("connected".into())).await;
@@ -452,7 +452,7 @@ fn report_err<E: Error>(e: E) -> Response<BoxBody> {
 
 /// A handler for Dioxus web hot reload websocket. This will send the updated static parts of the RSX to the client when they change.
 #[cfg(all(debug_assertions, feature = "hot-reload", feature = "ssr"))]
-pub async fn hot_reload_handler(ws: WebSocketUpgrade) -> impl IntoResponse {
+pub async fn hot_reload_handler(ws: axum::extract::WebSocketUpgrade) -> impl IntoResponse {
     use axum::extract::ws::Message;
     use futures_util::StreamExt;
 

+ 10 - 7
packages/fullstack/src/adapters/salvo_adapter.rs

@@ -49,7 +49,6 @@
 //! }
 //! ```
 
-use dioxus_core::VirtualDom;
 use hyper::{http::HeaderValue, StatusCode};
 use salvo::{
     async_trait, handler,
@@ -325,18 +324,22 @@ impl<P: Clone + serde::Serialize + Send + Sync + 'static> Handler for SSRHandler
         let renderer_pool = if let Some(renderer) = depot.obtain::<SSRState>() {
             renderer.clone()
         } else {
-            let renderer = SSRState::default();
+            let renderer = SSRState::new(&self.cfg);
             depot.inject(renderer.clone());
             renderer
         };
         let parts: Arc<RequestParts> = Arc::new(extract_parts(req));
+        let route = parts.uri.path().to_string();
         let server_context = DioxusServerContext::new(parts);
-        let mut vdom = VirtualDom::new_with_props(self.cfg.app, self.cfg.props.clone())
-            .with_root_context(server_context.clone());
-        let _ = vdom.rebuild();
 
-        res.write_body(renderer_pool.render_vdom(&vdom, &self.cfg))
-            .unwrap();
+        res.write_body(
+            renderer_pool
+                .render(route, &self.cfg, |vdom| {
+                    vdom.base_scope().provide_context(server_context.clone());
+                })
+                .unwrap(),
+        )
+        .unwrap();
 
         *res.headers_mut() = server_context.take_response_headers();
     }

+ 17 - 13
packages/fullstack/src/adapters/warp_adapter.rs

@@ -50,7 +50,6 @@ use crate::{
     prelude::*, render::SSRState, serve_config::ServeConfig, server_fn::DioxusServerFnRegistry,
 };
 
-use dioxus_core::VirtualDom;
 use server_fn::{Encoding, Payload, ServerFunctionRegistry};
 use std::error::Error;
 use std::sync::Arc;
@@ -168,8 +167,13 @@ pub fn serve_dioxus_application<P: Clone + serde::Serialize + Send + Sync + 'sta
     let serve_dir = warp::fs::dir(cfg.assets_path);
 
     connect_hot_reload()
+        // First register the server functions
         .or(register_server_fns(server_fn_route))
+        // Then the index route
+        .or(path::end().and(render_ssr(cfg.clone())))
+        // Then the static assets
         .or(serve_dir)
+        // Then all other routes
         .or(render_ssr(cfg))
         .boxed()
 }
@@ -180,17 +184,16 @@ pub fn render_ssr<P: Clone + serde::Serialize + Send + Sync + 'static>(
 ) -> impl Filter<Extract = (impl Reply,), Error = warp::Rejection> + Clone {
     warp::get()
         .and(request_parts())
-        .and(with_ssr_state())
-        .map(move |parts, renderer: SSRState| {
+        .and(with_ssr_state(&cfg))
+        .map(move |parts: RequestParts, renderer: SSRState| {
+            let route = parts.uri.path().to_string();
             let parts = Arc::new(parts);
 
             let server_context = DioxusServerContext::new(parts);
 
-            let mut vdom = VirtualDom::new_with_props(cfg.app, cfg.props.clone())
-                .with_root_context(server_context.clone());
-            let _ = vdom.rebuild();
-
-            let html = renderer.render_vdom(&vdom, &cfg);
+            let html = renderer.render(route, &cfg, |vdom| {
+                vdom.base_scope().provide_context(server_context.clone());
+            });
 
             let mut res = Response::builder();
 
@@ -198,7 +201,7 @@ pub fn render_ssr<P: Clone + serde::Serialize + Send + Sync + 'static>(
                 server_context.take_response_headers();
 
             res.header("Content-Type", "text/html")
-                .body(Bytes::from(html))
+                .body(Bytes::from(html.unwrap()))
                 .unwrap()
         })
 }
@@ -230,10 +233,11 @@ pub fn request_parts(
         })
 }
 
-fn with_ssr_state() -> impl Filter<Extract = (SSRState,), Error = std::convert::Infallible> + Clone
-{
-    let state = SSRState::default();
-    warp::any().map(move || state.clone())
+fn with_ssr_state<P: Clone + serde::Serialize + Send + Sync + 'static>(
+    cfg: &ServeConfig<P>,
+) -> impl Filter<Extract = (SSRState,), Error = std::convert::Infallible> + Clone {
+    let renderer = SSRState::new(cfg);
+    warp::any().map(move || renderer.clone())
 }
 
 #[derive(Debug)]

+ 158 - 0
packages/fullstack/src/launch.rs

@@ -0,0 +1,158 @@
+//! Launch helper macros for fullstack apps
+
+#[macro_export]
+/// Launch a server with a router
+macro_rules! launch_router {
+    (@router_config) => {
+        dioxus_fullstack::router::FullstackRouterConfig::default()
+    };
+
+    (@router_config $router_cfg:expr) => {
+        $router_cfg
+    };
+
+    (@[$address:expr], $route:ty, $(cfg: $router_cfg:expr,)? {$($rule:ident $(: $cfg:expr)?,)*}) => {
+        dioxus_fullstack::launch!(
+            @[$address],
+            dioxus_fullstack::router::RouteWithCfg::<$route>,
+            (dioxus_fullstack::launch_router!(@router_config $($router_cfg)?)),
+            {
+                $($rule $(: $cfg)?,)*
+            }
+        )
+    };
+}
+
+#[macro_export]
+/// Launch a server
+macro_rules! launch {
+    (@web_cfg $server_cfg:ident $wcfg:expr) => {
+        #[cfg(feature = "web")]
+        let web_cfg = $wcfg;
+    };
+
+    (@web_cfg $server_cfg:ident) => {
+        #[cfg(feature = "web")]
+        let web_cfg = dioxus_web::Config::new();
+    };
+
+    (@server_cfg $server_cfg:ident $cfg:expr) => {
+        #[cfg(feature = "ssr")]
+        let $server_cfg = $cfg;
+    };
+
+    (@hot_reload $server_cfg:ident) => {
+        #[cfg(feature = "ssr")]
+        {
+            hot_reload_init!(dioxus_hot_reload::Config::new().with_rebuild_callback(|| {
+                std::process::Command::new("cargo")
+                    .args(&["run", "--features", "ssr"])
+                    .spawn()
+                    .unwrap()
+                    .wait()
+                    .unwrap();
+                std::process::Command::new("cargo")
+                    .args(&["run", "--features", "web"])
+                    .spawn()
+                    .unwrap()
+                    .wait()
+                    .unwrap();
+                true
+            }));
+        }
+    };
+
+    (@hot_reload $server_cfg:ident $hot_reload_cfg:expr) => {
+        #[cfg(feature = "ssr")]
+        {
+            hot_reload_init!($hot_reload_cfg);
+        }
+    };
+
+    (@incremental $server_cfg:ident) => {
+        #[cfg(feature = "ssr")]
+        let $server_cfg = $server_cfg.incremental(dioxus_fullstack::prelude::IncrementalRendererConfig::default());
+    };
+
+    (@incremental $server_cfg:ident $cfg:expr) => {
+        #[cfg(feature = "ssr")]
+        let $server_cfg = $server_cfg.incremental($cfg);
+    };
+
+    (@props_type) => {
+        Default::default()
+    };
+
+    (@props_type $props:expr) => {
+        $props
+    };
+
+    (@[$address:expr], $comp:path, $(( $props:expr ),)? {$($rule:ident $(: $cfg:expr)?,)*}) => {
+        #[cfg(feature = "web")]
+        {
+            #[allow(unused)]
+            let web_cfg = dioxus_web::Config::new();
+
+            $(
+                launch!(@$rule server_cfg $($cfg)?);
+            )*
+
+            dioxus_web::launch_with_props(
+                $comp,
+                dioxus_fullstack::prelude::get_root_props_from_document().expect("Failed to get root props from document"),
+                web_cfg.hydrate(true),
+            );
+        }
+        #[cfg(feature = "ssr")]
+        {
+            let server_cfg = ServeConfigBuilder::new($comp, launch!(@props_type $($props)?));
+
+            $(
+                launch!(@$rule server_cfg $($cfg)?);
+            )*
+
+            tokio::runtime::Runtime::new()
+                .unwrap()
+                .block_on(async move {
+                    let addr = std::net::SocketAddr::from($address);
+
+                    dioxus_fullstack::launch::launch_server(addr, server_cfg.build()).await;
+                });
+        }
+    };
+}
+
+/// Launch a server with the given configeration
+/// This will use the routing intigration of the currently enabled intigration feature
+#[cfg(feature = "ssr")]
+pub async fn launch_server<P: Clone + serde::Serialize + Send + Sync + 'static>(
+    addr: std::net::SocketAddr,
+    cfg: crate::prelude::ServeConfig<P>,
+) {
+    #[cfg(all(feature = "axum", not(feature = "warp"), not(feature = "salvo")))]
+    {
+        use crate::adapters::axum_adapter::DioxusRouterExt;
+        axum::Server::bind(&addr)
+            .serve(
+                axum::Router::new()
+                    .serve_dioxus_application("", cfg)
+                    .into_make_service(),
+            )
+            .await
+            .unwrap();
+    }
+    #[cfg(all(feature = "warp", not(feature = "axum"), not(feature = "salvo")))]
+    {
+        warp::serve(crate::prelude::serve_dioxus_application("", cfg))
+            .run(addr)
+            .await;
+    }
+    #[cfg(all(feature = "salvo", not(feature = "axum"), not(feature = "warp")))]
+    {
+        use crate::adapters::salvo_adapter::DioxusRouterExt;
+        let router = salvo::Router::new().serve_dioxus_application("", cfg);
+        salvo::Server::new(salvo::listener::TcpListener::bind(addr))
+            .serve(router)
+            .await;
+    }
+}

+ 5 - 2
packages/fullstack/src/lib.rs

@@ -7,9 +7,13 @@ pub use adapters::*;
 
 mod props_html;
 
+#[cfg(feature = "router")]
+pub mod router;
+
 mod adapters;
 #[cfg(all(debug_assertions, feature = "hot-reload", feature = "ssr"))]
 mod hot_reload;
+pub mod launch;
 #[cfg(feature = "ssr")]
 mod render;
 #[cfg(feature = "ssr")]
@@ -29,8 +33,6 @@ pub mod prelude {
     pub use crate::props_html::deserialize_props::get_root_props_from_document;
     #[cfg(feature = "ssr")]
     pub use crate::render::SSRState;
-    #[cfg(all(feature = "router", feature = "ssr"))]
-    pub use crate::serve_config::FullstackRouterConfig;
     #[cfg(feature = "ssr")]
     pub use crate::serve_config::{ServeConfig, ServeConfigBuilder};
     #[cfg(feature = "ssr")]
@@ -39,6 +41,7 @@ pub mod prelude {
     pub use crate::server_fn::DioxusServerFn;
     #[cfg(feature = "ssr")]
     pub use crate::server_fn::{ServerFnTraitObj, ServerFunction};
+    pub use crate::{launch, launch_router};
     pub use dioxus_server_macro::*;
     #[cfg(feature = "ssr")]
     pub use dioxus_ssr::incremental::IncrementalRendererConfig;

+ 1 - 0
packages/fullstack/src/render.rs

@@ -32,6 +32,7 @@ impl SsrRendererPool {
         match self {
             Self::Renderer(pool) => {
                 let mut vdom = VirtualDom::new_with_props(component, props);
+                modify_vdom(&mut vdom);
 
                 let _ = vdom.rebuild();
                 let mut renderer = pool.pull(pre_renderer);

+ 110 - 0
packages/fullstack/src/router.rs

@@ -0,0 +1,110 @@
+//! Fullstack router intigration
+#![allow(non_snake_case)]
+use dioxus::prelude::*;
+
+/// Used by the launch macro
+#[doc(hidden)]
+pub fn RouteWithCfg<R>(cx: Scope<FullstackRouterConfig<R>>) -> Element
+where
+    R: dioxus_router::prelude::Routable,
+    <R as std::str::FromStr>::Err: std::fmt::Display,
+{
+    use dioxus_router::prelude::RouterConfig;
+
+    #[cfg(feature = "ssr")]
+    let context: crate::prelude::DioxusServerContext = cx
+        .consume_context()
+        .expect("RouteWithCfg should be served by dioxus fullstack");
+
+    let cfg = *cx.props;
+    render! {
+        dioxus_router::prelude::GenericRouter::<R> {
+            config: move || {
+                RouterConfig::default()
+                    .failure_external_navigation(cfg.failure_external_navigation)
+                    .history({
+                        #[cfg(feature = "ssr")]
+                        let history = dioxus_router::prelude::MemoryHistory::with_initial_path(
+                            context
+                                .request_parts()
+                                .uri
+                                .to_string()
+                                .parse()
+                                .unwrap_or_else(|err| {
+                                    log::error!("Failed to parse uri: {}", err);
+                                    "/"
+                                        .parse()
+                                        .unwrap_or_else(|err| {
+                                            panic!("Failed to parse uri: {}", err);
+                                        })
+                                }),
+                        );
+                        #[cfg(not(feature = "ssr"))]
+                        let history = dioxus_router::prelude::WebHistory::new(
+                            None,
+                            cfg.scroll_restoration,
+                        );
+                        history
+                    })
+            },
+        }
+    }
+}
+
+fn default_external_navigation_handler<R>() -> fn(Scope) -> Element
+where
+    R: dioxus_router::prelude::Routable,
+    <R as std::str::FromStr>::Err: std::fmt::Display,
+{
+    dioxus_router::prelude::FailureExternalNavigation::<R>
+}
+
+/// The configeration for the router
+#[derive(Props, serde::Serialize, serde::Deserialize)]
+pub struct FullstackRouterConfig<R>
+where
+    R: dioxus_router::prelude::Routable,
+    <R as std::str::FromStr>::Err: std::fmt::Display,
+{
+    #[serde(skip)]
+    #[serde(default = "default_external_navigation_handler::<R>")]
+    failure_external_navigation: fn(Scope) -> Element,
+    scroll_restoration: bool,
+    #[serde(skip)]
+    phantom: std::marker::PhantomData<R>,
+}
+
+impl<R> Clone for FullstackRouterConfig<R>
+where
+    R: dioxus_router::prelude::Routable,
+    <R as std::str::FromStr>::Err: std::fmt::Display,
+{
+    fn clone(&self) -> Self {
+        Self {
+            failure_external_navigation: self.failure_external_navigation,
+            scroll_restoration: self.scroll_restoration,
+            phantom: std::marker::PhantomData,
+        }
+    }
+}
+
+impl<R> Copy for FullstackRouterConfig<R>
+where
+    R: dioxus_router::prelude::Routable,
+    <R as std::str::FromStr>::Err: std::fmt::Display,
+{
+}
+
+impl<R> Default for FullstackRouterConfig<R>
+where
+    R: dioxus_router::prelude::Routable,
+    <R as std::str::FromStr>::Err: std::fmt::Display,
+{
+    fn default() -> Self {
+        Self {
+            failure_external_navigation: dioxus_router::prelude::FailureExternalNavigation::<R>,
+            scroll_restoration: true,
+            phantom: std::marker::PhantomData,
+        }
+    }
+}

+ 3 - 126
packages/fullstack/src/serve_config.rs

@@ -1,6 +1,8 @@
 #![allow(non_snake_case)]
 //! Configeration for how to serve a Dioxus application
 
+#[cfg(feature = "router")]
+use crate::router::*;
 use std::fs::File;
 use std::io::Read;
 use std::path::PathBuf;
@@ -15,7 +17,6 @@ pub struct ServeConfigBuilder<P: Clone> {
     pub(crate) root_id: Option<&'static str>,
     pub(crate) index_path: Option<&'static str>,
     pub(crate) assets_path: Option<&'static str>,
-    #[cfg(feature = "router")]
     pub(crate) incremental: Option<
         std::sync::Arc<
             dioxus_ssr::incremental::IncrementalRendererConfig<EmptyIncrementalRenderTemplate>,
@@ -23,123 +24,10 @@ pub struct ServeConfigBuilder<P: Clone> {
     >,
 }
 
-#[cfg(feature = "router")]
-fn default_external_navigation_handler<R>() -> fn(Scope) -> Element
-where
-    R: dioxus_router::prelude::Routable,
-    <R as std::str::FromStr>::Err: std::fmt::Display,
-{
-    dioxus_router::prelude::FailureExternalNavigation::<R>
-}
-
-#[cfg(feature = "router")]
-/// The configeration for the router
-#[derive(Props, serde::Serialize, serde::Deserialize)]
-pub struct FullstackRouterConfig<R>
-where
-    R: dioxus_router::prelude::Routable,
-    <R as std::str::FromStr>::Err: std::fmt::Display,
-{
-    #[serde(skip)]
-    #[serde(default = "default_external_navigation_handler::<R>")]
-    failure_external_navigation: fn(Scope) -> Element,
-    scroll_restoration: bool,
-    #[serde(skip)]
-    phantom: std::marker::PhantomData<R>,
-}
-
-#[cfg(feature = "router")]
-impl<R> Clone for FullstackRouterConfig<R>
-where
-    R: dioxus_router::prelude::Routable,
-    <R as std::str::FromStr>::Err: std::fmt::Display,
-{
-    fn clone(&self) -> Self {
-        Self {
-            failure_external_navigation: self.failure_external_navigation,
-            scroll_restoration: self.scroll_restoration,
-            phantom: std::marker::PhantomData,
-        }
-    }
-}
-
-#[cfg(feature = "router")]
-impl<R> Copy for FullstackRouterConfig<R>
-where
-    R: dioxus_router::prelude::Routable,
-    <R as std::str::FromStr>::Err: std::fmt::Display,
-{
-}
-
-#[cfg(feature = "router")]
-impl<R> Default for FullstackRouterConfig<R>
-where
-    R: dioxus_router::prelude::Routable,
-    <R as std::str::FromStr>::Err: std::fmt::Display,
-{
-    fn default() -> Self {
-        Self {
-            failure_external_navigation: dioxus_router::prelude::FailureExternalNavigation::<R>,
-            scroll_restoration: true,
-            phantom: std::marker::PhantomData,
-        }
-    }
-}
-
-#[cfg(feature = "router")]
-fn RouteWithCfg<R>(cx: Scope<FullstackRouterConfig<R>>) -> Element
-where
-    R: dioxus_router::prelude::Routable,
-    <R as std::str::FromStr>::Err: std::fmt::Display,
-{
-    use dioxus_router::prelude::RouterConfig;
-
-    #[cfg(feature = "ssr")]
-    let context: crate::prelude::DioxusServerContext = cx
-        .consume_context()
-        .expect("RouteWithCfg should be served by dioxus fullstack");
-
-    let cfg = *cx.props;
-    render! {
-        dioxus_router::prelude::GenericRouter::<R> {
-            config: move || {
-                RouterConfig::default()
-                    .failure_external_navigation(cfg.failure_external_navigation)
-                    .history({
-                        #[cfg(feature = "ssr")]
-                        let history = dioxus_router::prelude::MemoryHistory::with_initial_path(
-                            context
-                                .request_parts()
-                                .uri
-                                .to_string()
-                                .parse()
-                                .unwrap_or_else(|err| {
-                                    log::error!("Failed to parse uri: {}", err);
-                                    "/"
-                                        .parse()
-                                        .unwrap_or_else(|err| {
-                                            panic!("Failed to parse uri: {}", err);
-                                        })
-                                }),
-                        );
-                        #[cfg(not(feature = "ssr"))]
-                        let history = dioxus_router::prelude::WebHistory::new(
-                            None,
-                            cfg.scroll_restoration,
-                        );
-                        history
-                    })
-            },
-        }
-    }
-}
-
-#[cfg(feature = "router")]
 /// A template for incremental rendering that does nothing.
 #[derive(Default, Clone)]
 pub struct EmptyIncrementalRenderTemplate;
 
-#[cfg(feature = "router")]
 impl dioxus_ssr::incremental::RenderHTML for EmptyIncrementalRenderTemplate {
     fn render_after_body<R: std::io::Write>(
         &self,
@@ -164,14 +52,7 @@ where
 {
     /// Create a new ServeConfigBuilder to serve a router on the server.
     pub fn new_with_router(cfg: FullstackRouterConfig<R>) -> Self {
-        Self {
-            app: RouteWithCfg::<R>,
-            props: cfg,
-            root_id: None,
-            index_path: None,
-            assets_path: None,
-            incremental: None,
-        }
+        Self::new(RouteWithCfg::<R>, cfg)
     }
 }
 
@@ -184,12 +65,10 @@ impl<P: Clone> ServeConfigBuilder<P> {
             root_id: None,
             index_path: None,
             assets_path: None,
-            #[cfg(feature = "router")]
             incremental: None,
         }
     }
 
-    #[cfg(feature = "router")]
     /// Enable incremental static generation
     pub fn incremental(
         mut self,
@@ -235,7 +114,6 @@ impl<P: Clone> ServeConfigBuilder<P> {
             props: self.props,
             index,
             assets_path,
-            #[cfg(feature = "router")]
             incremental: self.incremental,
         }
     }
@@ -279,7 +157,6 @@ pub struct ServeConfig<P: Clone> {
     pub(crate) props: P,
     pub(crate) index: IndexHtml,
     pub(crate) assets_path: &'static str,
-    #[cfg(feature = "router")]
     pub(crate) incremental: Option<
         std::sync::Arc<
             dioxus_ssr::incremental::IncrementalRendererConfig<EmptyIncrementalRenderTemplate>,

+ 2 - 2
packages/router/src/history/web.rs

@@ -93,7 +93,7 @@ impl<R: Routable> WebHistory<R> {
     ///
     /// If `do_scroll_restoration` is [`true`], [`WebHistory`] will take control of the history
     /// state. It'll also set the browsers scroll restoration to `manual`.
-    fn new(prefix: Option<String>, do_scroll_restoration: bool) -> Self
+    pub fn new(prefix: Option<String>, do_scroll_restoration: bool) -> Self
     where
         <R as std::str::FromStr>::Err: std::fmt::Display,
     {
@@ -132,7 +132,7 @@ impl<R: Routable> WebHistory<R> {
     ///
     /// If `do_scroll_restoration` is [`true`], [`WebHistory`] will take control of the history
     /// state. It'll also set the browsers scroll restoration to `manual`.
-    fn new(prefix: Option<String>, do_scroll_restoration: bool) -> Self
+    pub fn new(prefix: Option<String>, do_scroll_restoration: bool) -> Self
     where
         <R as std::str::FromStr>::Err: std::fmt::Display,
         R: serde::Serialize + serde::de::DeserializeOwned,