From 20d146d9bd8fd5fe1c9aa63fd2e8b5b58ce4deec Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 10 Oct 2024 18:00:58 -0500 Subject: [PATCH] Simplify the launch builder (#2967) * improve documentation for the fullstack server context * Add a section about axum integration to the crate root docs * make serve_dioxus_application accept the cfg builder directly * remove unused server_fn module * improve fullstack config docs * improve documentation for the server function macro * fix axum router extension link * Fix doc tests * Fix launch builder * Simplify the launch builder * don't re-export launch in the prelude * refactor fullstack launch * Fix fullstack launch builder * Update static generation with the new builder api * fix some formatting/overly broad launch replacements * fix custom menu example * fix fullstack/static generation examples * Fix static generation launch * A few small formatting fixes * Fix a few doc tests * implement LaunchConfig for serve configs * fix fullstack launch with separate web and server launch methods * fix check with all features * dont expose inner core module * clippy and check * fix readme --------- Co-authored-by: Jonathan Kelley --- .../ecommerce-site/src/components/cart.rs | 1 + example-projects/ecommerce-site/src/main.rs | 2 +- example-projects/file-explorer/src/main.rs | 2 +- .../fullstack-hackernews/src/main.rs | 2 +- example-projects/wifi-scanner/src/main.rs | 2 +- examples/all_events.rs | 2 +- examples/backgrounded_futures.rs | 2 +- examples/calculator.rs | 2 +- examples/calculator_mutable.rs | 2 +- examples/clock.rs | 2 +- examples/control_focus.rs | 2 +- examples/counters.rs | 2 +- examples/crm.rs | 2 +- examples/custom_assets.rs | 2 +- examples/custom_html.rs | 2 +- examples/custom_menu.rs | 2 +- examples/disabled.rs | 2 +- examples/dog_app.rs | 2 +- examples/dynamic_asset.rs | 2 +- examples/errors.rs | 2 +- examples/eval.rs | 2 +- examples/file_upload.rs | 2 +- examples/flat_router.rs | 2 +- examples/form.rs | 2 +- examples/fullstack-desktop/src/main.rs | 2 +- examples/fullstack-hello-world/src/main.rs | 2 +- examples/fullstack-router/src/main.rs | 2 +- examples/fullstack-streaming/src/main.rs | 2 +- examples/future.rs | 2 +- examples/generic_component.rs | 2 +- examples/global.rs | 2 +- examples/hash_fragment_state.rs | 2 +- examples/hello_world.rs | 2 +- examples/hydration.rs | 2 +- examples/image_generator_openai.rs | 2 +- examples/link.rs | 2 +- examples/login_form.rs | 2 +- examples/memo_chain.rs | 2 +- examples/meta.rs | 2 +- examples/mobile_demo/src/lib.rs | 2 +- examples/multiwindow.rs | 2 +- examples/nested_listeners.rs | 2 +- examples/optional_props.rs | 2 +- examples/overlay.rs | 4 +- examples/popup.rs | 2 +- examples/pwa/src/main.rs | 2 +- examples/query_segment_search.rs | 2 +- examples/read_size.rs | 2 +- examples/readme.rs | 2 +- examples/reducer.rs | 2 +- examples/resize.rs | 2 +- examples/router.rs | 2 +- examples/router_resource.rs | 2 +- examples/rsx_usage.rs | 2 +- examples/scroll_to_top.rs | 2 +- examples/shortcut.rs | 2 +- examples/shorthand.rs | 2 +- examples/signals.rs | 2 +- examples/simple_list.rs | 2 +- examples/simple_router.rs | 4 +- examples/ssg-github-pages/src/main.rs | 2 +- examples/ssg-router/src/main.rs | 2 +- examples/ssg-simple/src/main.rs | 2 +- examples/streams.rs | 2 +- examples/suspense.rs | 2 +- examples/svg.rs | 2 +- examples/tailwind/src/main.rs | 2 +- examples/title.rs | 2 +- examples/todomvc.rs | 2 +- examples/video_stream.rs | 4 +- examples/weather_app.rs | 2 +- examples/web_component.rs | 2 +- examples/window_event.rs | 2 +- examples/window_focus.rs | 2 +- examples/window_zoom.rs | 2 +- examples/xss_safety.rs | 2 +- packages/autofmt/tests/samples/many_exprs.rsx | 2 +- .../autofmt/tests/wrong/multiexpr-many.rsx | 2 +- .../tests/wrong/multiexpr-many.wrong.rsx | 2 +- packages/cli/src/builder/mod.rs | 94 +++-- packages/cli/src/cli/build.rs | 17 +- packages/cli/src/serve/mod.rs | 26 +- packages/cli/src/serve/server.rs | 35 +- packages/core/src/launch.rs | 8 + packages/core/src/lib.rs | 10 +- packages/desktop/headless_tests/utils.rs | 2 +- packages/desktop/src/config.rs | 3 + packages/desktop/src/launch.rs | 11 +- packages/desktop/src/readme.md | 4 +- packages/dioxus-lib/README.md | 4 +- packages/dioxus/README.md | 4 +- packages/dioxus/src/launch.rs | 378 ++++++------------ packages/dioxus/src/lib.rs | 6 +- packages/fullstack/README.md | 74 +++- packages/fullstack/src/config.rs | 289 ------------- packages/fullstack/src/document/mod.rs | 4 +- packages/fullstack/src/document/web.rs | 3 +- packages/fullstack/src/launch.rs | 171 -------- packages/fullstack/src/lib.rs | 8 +- packages/fullstack/src/serve_config.rs | 6 + packages/fullstack/src/server/launch.rs | 60 +++ .../src/{axum_adapter.rs => server/mod.rs} | 18 +- packages/fullstack/src/server_context.rs | 48 ++- packages/liveview/src/config.rs | 4 +- .../playwright-tests/fullstack/src/main.rs | 2 +- .../nested-suspense/src/main.rs | 2 +- .../static-generation/src/main.rs | 6 +- .../suspense-carousel/src/main.rs | 2 +- packages/playwright-tests/web/src/main.rs | 2 +- packages/router/examples/simple_routes.rs | 2 +- .../rsx-hotreload/tests/valid/combo.new.rsx | 2 +- .../rsx-hotreload/tests/valid/combo.old.rsx | 2 +- packages/signals/examples/context.rs | 2 +- packages/signals/examples/dependencies.rs | 2 +- packages/signals/examples/map_signal.rs | 2 +- .../signals/examples/read_only_degrade.rs | 2 +- packages/signals/examples/selector.rs | 2 +- packages/signals/examples/send.rs | 2 +- .../signals/examples/split_subscriptions.rs | 2 +- packages/static-generation/README.md | 2 +- packages/static-generation/src/config.rs | 20 +- packages/static-generation/src/launch.rs | 65 +-- packages/static-generation/src/lib.rs | 5 + packages/static-generation/src/ssg.rs | 18 +- packages/web/src/cfg.rs | 3 + packages/web/src/launch.rs | 10 +- 126 files changed, 618 insertions(+), 989 deletions(-) create mode 100644 packages/core/src/launch.rs delete mode 100644 packages/fullstack/src/config.rs delete mode 100644 packages/fullstack/src/launch.rs create mode 100644 packages/fullstack/src/server/launch.rs rename packages/fullstack/src/{axum_adapter.rs => server/mod.rs} (97%) diff --git a/example-projects/ecommerce-site/src/components/cart.rs b/example-projects/ecommerce-site/src/components/cart.rs index e69de29bb..8b1378917 100644 --- a/example-projects/ecommerce-site/src/components/cart.rs +++ b/example-projects/ecommerce-site/src/components/cart.rs @@ -0,0 +1 @@ + diff --git a/example-projects/ecommerce-site/src/main.rs b/example-projects/ecommerce-site/src/main.rs index 7323055d0..0ed9b7a46 100644 --- a/example-projects/ecommerce-site/src/main.rs +++ b/example-projects/ecommerce-site/src/main.rs @@ -15,7 +15,7 @@ mod components { mod api; fn main() { - launch(|| { + dioxus::launch(|| { rsx! { head::Link { rel: "stylesheet", diff --git a/example-projects/file-explorer/src/main.rs b/example-projects/file-explorer/src/main.rs index aa44850f5..eb769eace 100644 --- a/example-projects/file-explorer/src/main.rs +++ b/example-projects/file-explorer/src/main.rs @@ -12,7 +12,7 @@ use dioxus::desktop::{Config, WindowBuilder}; use dioxus::prelude::*; fn main() { - LaunchBuilder::desktop() + dioxus::LaunchBuilder::desktop() .with_cfg(Config::new().with_window(WindowBuilder::new().with_resizable(true))) .launch(app) } diff --git a/example-projects/fullstack-hackernews/src/main.rs b/example-projects/fullstack-hackernews/src/main.rs index 62bf9d8f7..d83696a8b 100644 --- a/example-projects/fullstack-hackernews/src/main.rs +++ b/example-projects/fullstack-hackernews/src/main.rs @@ -17,7 +17,7 @@ fn main() { #[cfg(feature = "server")] tracing_subscriber::fmt::init(); - launch(|| rsx! { Router:: {} }); + dioxus::launch(|| rsx! { Router:: {} }); } #[derive(Clone, Routable)] diff --git a/example-projects/wifi-scanner/src/main.rs b/example-projects/wifi-scanner/src/main.rs index 57cf8bb24..93d7061ad 100644 --- a/example-projects/wifi-scanner/src/main.rs +++ b/example-projects/wifi-scanner/src/main.rs @@ -2,7 +2,7 @@ use dioxus::prelude::*; use wifiscanner::Wifi; fn main() { - launch(app) + dioxus::launch(app) } fn perform_scan() -> Status { diff --git a/examples/all_events.rs b/examples/all_events.rs index 2a04ee2bb..232421ad1 100644 --- a/examples/all_events.rs +++ b/examples/all_events.rs @@ -9,7 +9,7 @@ use std::{collections::VecDeque, fmt::Debug, rc::Rc}; const STYLE: &str = asset!("./examples/assets/events.css"); fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/backgrounded_futures.rs b/examples/backgrounded_futures.rs index 4cafad439..4fabef09f 100644 --- a/examples/backgrounded_futures.rs +++ b/examples/backgrounded_futures.rs @@ -11,7 +11,7 @@ use async_std::task::sleep; use dioxus::prelude::*; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/calculator.rs b/examples/calculator.rs index 9bf2a0461..b1761de00 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -15,7 +15,7 @@ use dioxus::prelude::*; const STYLE: &str = asset!("./examples/assets/calculator.css"); fn main() { - LaunchBuilder::desktop() + dioxus::LaunchBuilder::desktop() .with_cfg(desktop!({ use dioxus::desktop::{Config, LogicalSize, WindowBuilder}; Config::new().with_window( diff --git a/examples/calculator_mutable.rs b/examples/calculator_mutable.rs index 2f933ebbe..2984d569d 100644 --- a/examples/calculator_mutable.rs +++ b/examples/calculator_mutable.rs @@ -13,7 +13,7 @@ use dioxus::html::MouseEvent; use dioxus::prelude::*; fn main() { - LaunchBuilder::desktop() + dioxus::LaunchBuilder::desktop() .with_cfg( Config::new().with_window( WindowBuilder::new() diff --git a/examples/clock.rs b/examples/clock.rs index dc168a5ec..4feade188 100644 --- a/examples/clock.rs +++ b/examples/clock.rs @@ -8,7 +8,7 @@ use web_time::Instant; const STYLE: &str = asset!("./examples/assets/clock.css"); fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/control_focus.rs b/examples/control_focus.rs index c69293c56..0ec425b9f 100644 --- a/examples/control_focus.rs +++ b/examples/control_focus.rs @@ -11,7 +11,7 @@ use dioxus::prelude::*; const STYLE: &str = asset!("./examples/assets/roulette.css"); fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/counters.rs b/examples/counters.rs index f2d8c4203..0b12cb0ab 100644 --- a/examples/counters.rs +++ b/examples/counters.rs @@ -5,7 +5,7 @@ use dioxus::prelude::*; const STYLE: &str = asset!("./examples/assets/counter.css"); fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/crm.rs b/examples/crm.rs index 21f07466c..641fa415f 100644 --- a/examples/crm.rs +++ b/examples/crm.rs @@ -12,7 +12,7 @@ use dioxus::prelude::*; fn main() { - LaunchBuilder::new() + dioxus::LaunchBuilder::new() .with_cfg(desktop!({ use dioxus::desktop::{LogicalSize, WindowBuilder}; dioxus::desktop::Config::default() diff --git a/examples/custom_assets.rs b/examples/custom_assets.rs index 6c927d21a..58d7677ee 100644 --- a/examples/custom_assets.rs +++ b/examples/custom_assets.rs @@ -14,7 +14,7 @@ static ASSET_PATH: &str = "examples/assets/logo.png"; static ASSET_PATH: &str = asset!("examples/assets/logo.png".format(ImageType::Avif)); fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/custom_html.rs b/examples/custom_html.rs index dbbe334f0..b0db59ceb 100644 --- a/examples/custom_html.rs +++ b/examples/custom_html.rs @@ -4,7 +4,7 @@ use dioxus::prelude::*; fn main() { - LaunchBuilder::new() + dioxus::LaunchBuilder::new() .with_cfg( dioxus::desktop::Config::new().with_custom_index( r#" diff --git a/examples/custom_menu.rs b/examples/custom_menu.rs index f78f33c88..5efcba49a 100644 --- a/examples/custom_menu.rs +++ b/examples/custom_menu.rs @@ -28,7 +28,7 @@ fn main() { let config = dioxus::desktop::Config::new().with_menu(menu); // Launch the app with the custom menu - LaunchBuilder::new().with_cfg(config).launch(app) + dioxus::LaunchBuilder::new().with_cfg(config).launch(app) } fn app() -> Element { diff --git a/examples/disabled.rs b/examples/disabled.rs index 6aaa70b9d..b48b1a912 100644 --- a/examples/disabled.rs +++ b/examples/disabled.rs @@ -5,7 +5,7 @@ use dioxus::prelude::*; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/dog_app.rs b/examples/dog_app.rs index 1c53c8731..09bf5f0a9 100644 --- a/examples/dog_app.rs +++ b/examples/dog_app.rs @@ -11,7 +11,7 @@ use dioxus::prelude::*; use std::collections::HashMap; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/dynamic_asset.rs b/examples/dynamic_asset.rs index 14a33c480..563ec69c6 100644 --- a/examples/dynamic_asset.rs +++ b/examples/dynamic_asset.rs @@ -10,7 +10,7 @@ use dioxus::prelude::*; const STYLE: &str = asset!("./examples/assets/custom_assets.css"); fn main() { - launch_desktop(app); + dioxus::LaunchBuilder::desktop().launch(app); } fn app() -> Element { diff --git a/examples/errors.rs b/examples/errors.rs index 4727cb5e0..a7319e017 100644 --- a/examples/errors.rs +++ b/examples/errors.rs @@ -11,7 +11,7 @@ use dioxus::prelude::*; fn main() { - launch(|| rsx! { Router:: {} }); + dioxus::launch(|| rsx! { Router:: {} }); } /// You can use an ErrorBoundary to catch errors in children and display a warning diff --git a/examples/eval.rs b/examples/eval.rs index 9cfd15e94..f232b04a9 100644 --- a/examples/eval.rs +++ b/examples/eval.rs @@ -7,7 +7,7 @@ use async_std::task::sleep; use dioxus::prelude::*; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/file_upload.rs b/examples/file_upload.rs index 9fb74072b..faaefc49f 100644 --- a/examples/file_upload.rs +++ b/examples/file_upload.rs @@ -11,7 +11,7 @@ use dioxus::{html::HasFileData, prelude::dioxus_elements::FileEngine}; const STYLE: &str = asset!("./examples/assets/file_upload.css"); fn main() { - launch(app); + dioxus::launch(app); } struct UploadedFile { diff --git a/examples/flat_router.rs b/examples/flat_router.rs index 79a9d2a3f..94ca7cf06 100644 --- a/examples/flat_router.rs +++ b/examples/flat_router.rs @@ -12,7 +12,7 @@ use dioxus::prelude::*; const STYLE: &str = asset!("./examples/assets/flat_router.css"); fn main() { - launch(|| { + dioxus::launch(|| { rsx! { head::Link { rel: "stylesheet", href: STYLE } Router:: {} diff --git a/examples/form.rs b/examples/form.rs index 91b09ecd3..ff6154b9d 100644 --- a/examples/form.rs +++ b/examples/form.rs @@ -7,7 +7,7 @@ use dioxus::prelude::*; use std::collections::HashMap; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/fullstack-desktop/src/main.rs b/examples/fullstack-desktop/src/main.rs index 0e39cc69d..f56451f19 100644 --- a/examples/fullstack-desktop/src/main.rs +++ b/examples/fullstack-desktop/src/main.rs @@ -5,7 +5,7 @@ fn main() { // Set the url of the server where server functions are hosted. #[cfg(not(feature = "server"))] dioxus::fullstack::prelude::server_fn::client::set_server_url("http://127.0.0.1:8080"); - launch(app); + dioxus::launch(app); } pub fn app() -> Element { diff --git a/examples/fullstack-hello-world/src/main.rs b/examples/fullstack-hello-world/src/main.rs index 513791fa4..b99664eb0 100644 --- a/examples/fullstack-hello-world/src/main.rs +++ b/examples/fullstack-hello-world/src/main.rs @@ -50,5 +50,5 @@ fn main() { #[cfg(feature = "server")] tracing_subscriber::fmt::init(); - launch(app); + dioxus::launch(app); } diff --git a/examples/fullstack-router/src/main.rs b/examples/fullstack-router/src/main.rs index 2240291b2..9a889e8f6 100644 --- a/examples/fullstack-router/src/main.rs +++ b/examples/fullstack-router/src/main.rs @@ -7,7 +7,7 @@ use dioxus::prelude::*; fn main() { - LaunchBuilder::fullstack() + dioxus::LaunchBuilder::new() .with_cfg(server_only!(ServeConfig::builder().incremental( IncrementalRendererConfig::default() .invalidate_after(std::time::Duration::from_secs(120)), diff --git a/examples/fullstack-streaming/src/main.rs b/examples/fullstack-streaming/src/main.rs index fc6e82c02..f090a847a 100644 --- a/examples/fullstack-streaming/src/main.rs +++ b/examples/fullstack-streaming/src/main.rs @@ -37,5 +37,5 @@ pub async fn test_stream() -> Result { } fn main() { - launch(app) + dioxus::launch(app) } diff --git a/examples/future.rs b/examples/future.rs index f4ddc1046..e32386fe8 100644 --- a/examples/future.rs +++ b/examples/future.rs @@ -7,7 +7,7 @@ use async_std::task::sleep; use dioxus::prelude::*; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/generic_component.rs b/examples/generic_component.rs index 6af34e542..520f25778 100644 --- a/examples/generic_component.rs +++ b/examples/generic_component.rs @@ -7,7 +7,7 @@ use dioxus::prelude::*; use std::fmt::Display; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/global.rs b/examples/global.rs index 0a08485b0..e6a4ade1a 100644 --- a/examples/global.rs +++ b/examples/global.rs @@ -13,7 +13,7 @@ static COUNT: GlobalSignal = Signal::global(|| 0); static DOUBLED_COUNT: GlobalMemo = Memo::global(|| COUNT() * 2); fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/hash_fragment_state.rs b/examples/hash_fragment_state.rs index ed77c8c46..7eb5aa67e 100644 --- a/examples/hash_fragment_state.rs +++ b/examples/hash_fragment_state.rs @@ -19,7 +19,7 @@ use dioxus::prelude::*; use serde::{Deserialize, Serialize}; fn main() { - launch(|| { + dioxus::launch(|| { rsx! { Router:: {} } diff --git a/examples/hello_world.rs b/examples/hello_world.rs index ee33c2230..20c9af934 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -12,7 +12,7 @@ use dioxus::prelude::*; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/hydration.rs b/examples/hydration.rs index 1e7de2706..06513a47d 100644 --- a/examples/hydration.rs +++ b/examples/hydration.rs @@ -13,7 +13,7 @@ use dioxus::desktop::Config; use dioxus::prelude::*; fn main() { - LaunchBuilder::desktop() + dioxus::LaunchBuilder::desktop() .with_cfg(Config::new().with_prerendered({ // We build the dom a first time, then pre-render it to HTML let pre_rendered_dom = VirtualDom::prebuilt(app); diff --git a/examples/image_generator_openai.rs b/examples/image_generator_openai.rs index 474c46de9..0a6f69d5b 100644 --- a/examples/image_generator_openai.rs +++ b/examples/image_generator_openai.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_json::{json, Error}; fn main() { - launch(app) + dioxus::launch(app) } fn app() -> Element { diff --git a/examples/link.rs b/examples/link.rs index ff29b6562..776e9f7a5 100644 --- a/examples/link.rs +++ b/examples/link.rs @@ -11,7 +11,7 @@ use dioxus::prelude::*; const STYLE: &str = asset!("./examples/assets/links.css"); fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/login_form.rs b/examples/login_form.rs index 255a31ec4..8549ba00e 100644 --- a/examples/login_form.rs +++ b/examples/login_form.rs @@ -9,7 +9,7 @@ use dioxus::prelude::*; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/memo_chain.rs b/examples/memo_chain.rs index 205fe8769..ab5708939 100644 --- a/examples/memo_chain.rs +++ b/examples/memo_chain.rs @@ -6,7 +6,7 @@ use dioxus::prelude::*; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/meta.rs b/examples/meta.rs index fae916258..d816ca183 100644 --- a/examples/meta.rs +++ b/examples/meta.rs @@ -4,7 +4,7 @@ use dioxus::prelude::*; fn main() { tracing_subscriber::fmt::init(); - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/mobile_demo/src/lib.rs b/examples/mobile_demo/src/lib.rs index 6fc142d9a..1eb2cea03 100644 --- a/examples/mobile_demo/src/lib.rs +++ b/examples/mobile_demo/src/lib.rs @@ -50,7 +50,7 @@ pub fn main() -> Result<()> { // Right now we're going through dioxus-desktop but we'd like to go through dioxus-mobile // That will seed the index.html with some fixes that prevent the page from scrolling/zooming etc - LaunchBuilder::mobile() + dioxus::LaunchBuilder::mobile() .with_cfg( // Note that we have to disable the viewport goofiness of the browser. // Dioxus_mobile should do this for us diff --git a/examples/multiwindow.rs b/examples/multiwindow.rs index 6858f6589..32e116c59 100644 --- a/examples/multiwindow.rs +++ b/examples/multiwindow.rs @@ -7,7 +7,7 @@ use dioxus::prelude::*; fn main() { - launch_desktop(app); + dioxus::LaunchBuilder::desktop().launch(app); } fn app() -> Element { diff --git a/examples/nested_listeners.rs b/examples/nested_listeners.rs index 33cf3d404..c2316aed3 100644 --- a/examples/nested_listeners.rs +++ b/examples/nested_listeners.rs @@ -7,7 +7,7 @@ use dioxus::prelude::*; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/optional_props.rs b/examples/optional_props.rs index ad54ed33c..334852bea 100644 --- a/examples/optional_props.rs +++ b/examples/optional_props.rs @@ -6,7 +6,7 @@ use dioxus::prelude::*; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/overlay.rs b/examples/overlay.rs index e4e2e8059..7139e96cb 100644 --- a/examples/overlay.rs +++ b/examples/overlay.rs @@ -11,7 +11,9 @@ use dioxus::desktop::{ use dioxus::prelude::*; fn main() { - LaunchBuilder::desktop().with_cfg(make_config()).launch(app); + dioxus::LaunchBuilder::desktop() + .with_cfg(make_config()) + .launch(app); } fn app() -> Element { diff --git a/examples/popup.rs b/examples/popup.rs index 2c79e0ba7..56d422aca 100644 --- a/examples/popup.rs +++ b/examples/popup.rs @@ -5,7 +5,7 @@ use dioxus::prelude::*; use std::rc::Rc; fn main() { - launch_desktop(app); + dioxus::LaunchBuilder::desktop().launch(app); } fn app() -> Element { diff --git a/examples/pwa/src/main.rs b/examples/pwa/src/main.rs index 3e4d5b236..581395e3d 100644 --- a/examples/pwa/src/main.rs +++ b/examples/pwa/src/main.rs @@ -5,7 +5,7 @@ fn main() { wasm_logger::init(wasm_logger::Config::default()); console_error_panic_hook::set_once(); - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/query_segment_search.rs b/examples/query_segment_search.rs index 018f475e4..7a4fc6009 100644 --- a/examples/query_segment_search.rs +++ b/examples/query_segment_search.rs @@ -14,7 +14,7 @@ use dioxus::prelude::*; fn main() { - launch(|| { + dioxus::launch(|| { rsx! { Router:: {} } diff --git a/examples/read_size.rs b/examples/read_size.rs index 1965597cf..8ef3ccfe9 100644 --- a/examples/read_size.rs +++ b/examples/read_size.rs @@ -9,7 +9,7 @@ use std::rc::Rc; use dioxus::{html::geometry::euclid::Rect, prelude::*}; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/readme.rs b/examples/readme.rs index 8f412a749..1c36c65c0 100644 --- a/examples/readme.rs +++ b/examples/readme.rs @@ -8,7 +8,7 @@ use dioxus::prelude::*; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/reducer.rs b/examples/reducer.rs index 7c434e585..e16ae41df 100644 --- a/examples/reducer.rs +++ b/examples/reducer.rs @@ -10,7 +10,7 @@ use dioxus::prelude::*; const STYLE: &str = asset!("./examples/assets/radio.css"); fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/resize.rs b/examples/resize.rs index eace774a0..48181c2c3 100644 --- a/examples/resize.rs +++ b/examples/resize.rs @@ -8,7 +8,7 @@ use dioxus::prelude::*; use dioxus_elements::geometry::euclid::Size2D; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/router.rs b/examples/router.rs index 13197f57e..951e82111 100644 --- a/examples/router.rs +++ b/examples/router.rs @@ -11,7 +11,7 @@ use dioxus::prelude::*; const STYLE: &str = asset!("./examples/assets/router.css"); fn main() { - launch(|| { + dioxus::launch(|| { rsx! { head::Link { rel: "stylesheet", href: STYLE } Router:: {} diff --git a/examples/router_resource.rs b/examples/router_resource.rs index de69f0e40..6898585f3 100644 --- a/examples/router_resource.rs +++ b/examples/router_resource.rs @@ -15,7 +15,7 @@ enum Route { } fn main() { - launch(App); + dioxus::launch(App); } #[component] diff --git a/examples/rsx_usage.rs b/examples/rsx_usage.rs index 3bffe8c70..0e223d6e8 100644 --- a/examples/rsx_usage.rs +++ b/examples/rsx_usage.rs @@ -39,7 +39,7 @@ //! - Allow top-level fragments fn main() { - launch(app) + dioxus::launch(app) } use core::{fmt, str::FromStr}; diff --git a/examples/scroll_to_top.rs b/examples/scroll_to_top.rs index 9f4f85955..1cd91e494 100644 --- a/examples/scroll_to_top.rs +++ b/examples/scroll_to_top.rs @@ -8,7 +8,7 @@ use dioxus::prelude::*; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/shortcut.rs b/examples/shortcut.rs index d0192b978..14e5beec3 100644 --- a/examples/shortcut.rs +++ b/examples/shortcut.rs @@ -9,7 +9,7 @@ use dioxus::desktop::use_global_shortcut; use dioxus::prelude::*; fn main() { - launch_desktop(app); + dioxus::LaunchBuilder::desktop().launch(app); } fn app() -> Element { diff --git a/examples/shorthand.rs b/examples/shorthand.rs index 33905d571..bd2047a8e 100644 --- a/examples/shorthand.rs +++ b/examples/shorthand.rs @@ -3,7 +3,7 @@ use dioxus::prelude::*; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/signals.rs b/examples/signals.rs index 26db9685c..6b881587b 100644 --- a/examples/signals.rs +++ b/examples/signals.rs @@ -10,7 +10,7 @@ use async_std::task::sleep; use dioxus::prelude::*; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/simple_list.rs b/examples/simple_list.rs index 3c1b9d71a..32e1d3eb8 100644 --- a/examples/simple_list.rs +++ b/examples/simple_list.rs @@ -5,7 +5,7 @@ use dioxus::prelude::*; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/simple_router.rs b/examples/simple_router.rs index e674f2f30..233ba9da9 100644 --- a/examples/simple_router.rs +++ b/examples/simple_router.rs @@ -3,9 +3,9 @@ use dioxus::prelude::*; fn main() { - // Launch the router, using our `Route` component as the generic type + // launch the router, using our `Route` component as the generic type // This will automatically boot the app to "/" unless otherwise specified - launch(|| rsx! { Router:: {} }); + dioxus::launch(|| rsx! { Router:: {} }); } /// By default, the Routable derive will use the name of the variant as the route diff --git a/examples/ssg-github-pages/src/main.rs b/examples/ssg-github-pages/src/main.rs index ba80f9983..c96c94659 100644 --- a/examples/ssg-github-pages/src/main.rs +++ b/examples/ssg-github-pages/src/main.rs @@ -6,7 +6,7 @@ use dioxus::prelude::*; // Generate all routes and output them to the static path fn main() { - LaunchBuilder::new() + dioxus::LaunchBuilder::new() .with_cfg(dioxus::static_site_generation::Config::new().github_pages()) .launch(|| { rsx! { diff --git a/examples/ssg-router/src/main.rs b/examples/ssg-router/src/main.rs index 2d43a264c..b96b6c98d 100644 --- a/examples/ssg-router/src/main.rs +++ b/examples/ssg-router/src/main.rs @@ -5,7 +5,7 @@ use dioxus::prelude::*; // Generate all routes and output them to the static path fn main() { - launch(|| { + dioxus::launch(|| { rsx! { Router:: {} } diff --git a/examples/ssg-simple/src/main.rs b/examples/ssg-simple/src/main.rs index b7816277c..b69315c8e 100644 --- a/examples/ssg-simple/src/main.rs +++ b/examples/ssg-simple/src/main.rs @@ -3,7 +3,7 @@ use dioxus::prelude::*; // Generate all routes and output them to the static path fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/streams.rs b/examples/streams.rs index 6155cb35f..a71b5b9c1 100644 --- a/examples/streams.rs +++ b/examples/streams.rs @@ -5,7 +5,7 @@ use dioxus::prelude::*; use futures_util::{future, stream, Stream, StreamExt}; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/suspense.rs b/examples/suspense.rs index 014bb3276..656e808b7 100644 --- a/examples/suspense.rs +++ b/examples/suspense.rs @@ -15,7 +15,7 @@ use dioxus::desktop::{Config, LogicalSize, WindowBuilder}; use dioxus::prelude::*; fn main() { - LaunchBuilder::new() + dioxus::LaunchBuilder::new() .with_cfg(desktop! { Config::new().with_window( WindowBuilder::new() diff --git a/examples/svg.rs b/examples/svg.rs index d4c23d1dd..b462f3028 100644 --- a/examples/svg.rs +++ b/examples/svg.rs @@ -10,7 +10,7 @@ use dioxus::prelude::*; use rand::{thread_rng, Rng}; fn main() { - launch(|| { + dioxus::launch(|| { rsx! { div { user_select: "none", webkit_user_select: "none", margin_left: "10%", margin_right: "10%", h1 { "Click die to generate a new value" } diff --git a/examples/tailwind/src/main.rs b/examples/tailwind/src/main.rs index 70ce484c3..7a952531b 100644 --- a/examples/tailwind/src/main.rs +++ b/examples/tailwind/src/main.rs @@ -5,7 +5,7 @@ use dioxus::prelude::*; const _STYLE: &str = asset!("public/tailwind.css"); fn main() { - launch(app); + dioxus::launch(app); } pub fn app() -> Element { diff --git a/examples/title.rs b/examples/title.rs index ebe258963..c92497492 100644 --- a/examples/title.rs +++ b/examples/title.rs @@ -4,7 +4,7 @@ use dioxus::prelude::*; fn main() { tracing_subscriber::fmt::init(); - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/todomvc.rs b/examples/todomvc.rs index ea2e528a1..95d27d29c 100644 --- a/examples/todomvc.rs +++ b/examples/todomvc.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; const STYLE: &str = asset!("./examples/assets/todomvc.css"); fn main() { - launch(app); + dioxus::launch(app); } #[derive(PartialEq, Eq, Clone, Copy)] diff --git a/examples/video_stream.rs b/examples/video_stream.rs index 9c3c4c919..e91c586bb 100644 --- a/examples/video_stream.rs +++ b/examples/video_stream.rs @@ -16,13 +16,13 @@ fn main() { // For the sake of this example, we will download the video file if it doesn't exist ensure_video_is_loaded(); - launch_desktop(app); + dioxus::LaunchBuilder::desktop().launch(app); } fn app() -> Element { // Any request to /videos will be handled by this handler use_asset_handler("videos", move |request, responder| { - // Using dioxus::spawn works, but is slower than a dedicated thread + // Using spawn works, but is slower than a dedicated thread tokio::task::spawn(async move { let video_file = PathBuf::from(VIDEO_PATH); let mut file = tokio::fs::File::open(&video_file).await.unwrap(); diff --git a/examples/weather_app.rs b/examples/weather_app.rs index 395fd81f1..924cf87eb 100644 --- a/examples/weather_app.rs +++ b/examples/weather_app.rs @@ -4,7 +4,7 @@ use dioxus::prelude::*; use serde::{Deserialize, Serialize}; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/web_component.rs b/examples/web_component.rs index df0515397..a5f831912 100644 --- a/examples/web_component.rs +++ b/examples/web_component.rs @@ -7,7 +7,7 @@ use dioxus::prelude::*; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/examples/window_event.rs b/examples/window_event.rs index cb91d2a5b..e2d88cc39 100644 --- a/examples/window_event.rs +++ b/examples/window_event.rs @@ -13,7 +13,7 @@ use dioxus::desktop::{window, Config, WindowBuilder}; use dioxus::prelude::*; fn main() { - LaunchBuilder::desktop() + dioxus::LaunchBuilder::desktop() .with_cfg( Config::new().with_window( WindowBuilder::new() diff --git a/examples/window_focus.rs b/examples/window_focus.rs index 447544077..1c44a8df1 100644 --- a/examples/window_focus.rs +++ b/examples/window_focus.rs @@ -12,7 +12,7 @@ use dioxus::desktop::{Config, WindowCloseBehaviour}; use dioxus::prelude::*; fn main() { - LaunchBuilder::desktop() + dioxus::LaunchBuilder::desktop() .with_cfg(Config::new().with_close_behaviour(WindowCloseBehaviour::CloseWindow)) .launch(app) } diff --git a/examples/window_zoom.rs b/examples/window_zoom.rs index 8b10d503b..fea162f12 100644 --- a/examples/window_zoom.rs +++ b/examples/window_zoom.rs @@ -5,7 +5,7 @@ use dioxus::prelude::*; fn main() { - launch_desktop(app); + dioxus::LaunchBuilder::desktop().launch(app); } fn app() -> Element { diff --git a/examples/xss_safety.rs b/examples/xss_safety.rs index cb8d414dd..904a42b07 100644 --- a/examples/xss_safety.rs +++ b/examples/xss_safety.rs @@ -5,7 +5,7 @@ use dioxus::prelude::*; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/packages/autofmt/tests/samples/many_exprs.rsx b/packages/autofmt/tests/samples/many_exprs.rsx index a0432822b..364b0c815 100644 --- a/packages/autofmt/tests/samples/many_exprs.rsx +++ b/packages/autofmt/tests/samples/many_exprs.rsx @@ -8,7 +8,7 @@ use std::{ use tokio::time::sleep; fn main() { - LaunchBuilder::desktop().launch(app); + dioxus::LaunchBuilder::desktop().launch(app); } struct WindowPreferences { diff --git a/packages/autofmt/tests/wrong/multiexpr-many.rsx b/packages/autofmt/tests/wrong/multiexpr-many.rsx index 383edfebf..826e9ad27 100644 --- a/packages/autofmt/tests/wrong/multiexpr-many.rsx +++ b/packages/autofmt/tests/wrong/multiexpr-many.rsx @@ -8,7 +8,7 @@ use std::{ use tokio::time::sleep; fn main() { - LaunchBuilder::desktop().launch(app); + dioxus::LaunchBuilder::desktop().launch(app); } struct WindowPreferences { diff --git a/packages/autofmt/tests/wrong/multiexpr-many.wrong.rsx b/packages/autofmt/tests/wrong/multiexpr-many.wrong.rsx index 66f87ee38..64921aa12 100644 --- a/packages/autofmt/tests/wrong/multiexpr-many.wrong.rsx +++ b/packages/autofmt/tests/wrong/multiexpr-many.wrong.rsx @@ -8,7 +8,7 @@ use std::{ use tokio::time::sleep; fn main() { - LaunchBuilder::desktop().launch(app); + dioxus::LaunchBuilder::desktop().launch(app); } struct WindowPreferences { diff --git a/packages/cli/src/builder/mod.rs b/packages/cli/src/builder/mod.rs index d245f05a8..24f145886 100644 --- a/packages/cli/src/builder/mod.rs +++ b/packages/cli/src/builder/mod.rs @@ -150,6 +150,49 @@ impl BuildRequest { } } +#[derive(Debug, Clone, Default)] +pub(crate) struct OpenArguments { + fullstack_address: Option, + devserver_addr: Option, + always_on_top: Option, + workspace: PathBuf, + asset_root: PathBuf, + app_title: String, + out_dir: PathBuf, + serve: bool, +} + +impl OpenArguments { + #[allow(clippy::too_many_arguments)] + pub(crate) fn new( + serve: &ServeArguments, + fullstack_address: Option, + dioxus_crate: &DioxusCrate, + ) -> Self { + Self { + devserver_addr: Some(serve.address.address()), + always_on_top: Some(serve.always_on_top.unwrap_or(true)), + serve: true, + fullstack_address, + workspace: dioxus_crate.workspace_dir().to_path_buf(), + asset_root: dioxus_crate.asset_dir().to_path_buf(), + app_title: dioxus_crate.dioxus_config.application.name.clone(), + out_dir: dioxus_crate.out_dir().to_path_buf(), + } + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn new_for_static_generation_build(dioxus_crate: &DioxusCrate) -> Self { + Self { + workspace: dioxus_crate.workspace_dir().to_path_buf(), + asset_root: dioxus_crate.asset_dir().to_path_buf(), + app_title: dioxus_crate.dioxus_config.application.name.clone(), + out_dir: dioxus_crate.out_dir().to_path_buf(), + ..Default::default() + } + } +} + #[derive(Debug, Clone)] pub(crate) struct BuildResult { pub executable: PathBuf, @@ -158,20 +201,12 @@ pub(crate) struct BuildResult { impl BuildResult { /// Open the executable if this is a native build - #[allow(clippy::too_many_arguments)] - pub fn open( - &self, - serve: &ServeArguments, - fullstack_address: Option, - workspace: &std::path::Path, - asset_root: &std::path::Path, - devserver_addr: SocketAddr, - app_title: String, - out_dir: PathBuf, - ) -> std::io::Result> { + pub fn open(&self, arguments: OpenArguments) -> std::io::Result> { match self.target_platform { TargetPlatform::Web => { - tracing::info!(dx_src = ?TraceSrc::Dev, "Serving web app on http://{} 🎉", serve.address.address()); + if let Some(address) = arguments.fullstack_address { + tracing::info!(dx_src = ?TraceSrc::Dev, "Serving web app on http://{} 🎉", address); + } return Ok(None); } TargetPlatform::Desktop => { @@ -181,7 +216,7 @@ impl BuildResult { // shut this up for now - the web app will take priority in logging } TargetPlatform::Liveview => { - if let Some(fullstack_address) = fullstack_address { + if let Some(fullstack_address) = arguments.fullstack_address { tracing::info!( dx_src = ?TraceSrc::Dev, "Launching liveview server on http://{:?} 🎉", @@ -191,7 +226,9 @@ impl BuildResult { } } - tracing::info!(dx_src = ?TraceSrc::Dev, "Press [o] to open the app manually."); + if arguments.serve { + tracing::info!(dx_src = ?TraceSrc::Dev, "Press [o] to open the app manually."); + } let executable = self.executable.canonicalize()?; let mut cmd = Command::new(executable); @@ -199,29 +236,36 @@ impl BuildResult { // Set the env vars that the clients will expect // These need to be stable within a release version (ie 0.6.0) cmd.env(dioxus_cli_config::CLI_ENABLED_ENV, "true"); - if let Some(addr) = fullstack_address { + if let Some(addr) = arguments.fullstack_address { cmd.env(dioxus_cli_config::SERVER_IP_ENV, addr.ip().to_string()); cmd.env(dioxus_cli_config::SERVER_PORT_ENV, addr.port().to_string()); } - cmd.env( - dioxus_cli_config::ALWAYS_ON_TOP_ENV, - serve.always_on_top.unwrap_or(true).to_string(), - ); + if let Some(always_on_top) = arguments.always_on_top { + cmd.env( + dioxus_cli_config::ALWAYS_ON_TOP_ENV, + always_on_top.to_string(), + ); + } cmd.env( dioxus_cli_config::ASSET_ROOT_ENV, - asset_root.display().to_string(), + arguments.asset_root.display().to_string(), ); + if let Some(devserver_addr) = arguments.devserver_addr { + cmd.env( + dioxus_cli_config::DEVSERVER_RAW_ADDR_ENV, + devserver_addr.to_string(), + ); + } + cmd.env(dioxus_cli_config::APP_TITLE_ENV, arguments.app_title); cmd.env( - dioxus_cli_config::DEVSERVER_RAW_ADDR_ENV, - devserver_addr.to_string(), + dioxus_cli_config::OUT_DIR, + arguments.out_dir.display().to_string(), ); - cmd.env(dioxus_cli_config::APP_TITLE_ENV, app_title); - cmd.env(dioxus_cli_config::OUT_DIR, out_dir.display().to_string()); cmd.stderr(Stdio::piped()) .stdout(Stdio::piped()) .kill_on_drop(true) - .current_dir(workspace); + .current_dir(arguments.workspace); Ok(Some(cmd.spawn()?)) } diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/cli/build.rs index 8e7f6099c..7af213df3 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/cli/build.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use crate::config::Platform; +use crate::{builder::OpenArguments, config::Platform}; use anyhow::Context; use crate::{ @@ -115,7 +115,20 @@ impl Build { pub async fn build(&mut self, dioxus_crate: &mut DioxusCrate) -> Result<()> { self.resolve(dioxus_crate)?; let build_requests = BuildRequest::create(false, dioxus_crate, self.clone())?; - BuildRequest::build_all_parallel(build_requests).await?; + let builds = BuildRequest::build_all_parallel(build_requests).await?; + + // If this is a static generation build, building involves running the server to generate static files + if self.platform.unwrap() == Platform::StaticGeneration { + println!("Building static site..."); + for build in builds { + if let Some(mut result) = + build.open(OpenArguments::new_for_static_generation_build(dioxus_crate))? + { + result.wait().await?; + } + } + println!("Static site built!"); + } Ok(()) } diff --git a/packages/cli/src/serve/mod.rs b/packages/cli/src/serve/mod.rs index b739d66d5..f12aced97 100644 --- a/packages/cli/src/serve/mod.rs +++ b/packages/cli/src/serve/mod.rs @@ -1,6 +1,7 @@ use std::future::{poll_fn, Future, IntoFuture}; use std::task::Poll; +use crate::builder::OpenArguments; use crate::cli::serve::Serve; use crate::dioxus_crate::DioxusCrate; use crate::tracer::CLILogControl; @@ -156,23 +157,14 @@ pub async fn serve_all( builder.children.clear(); } - let asset_dir = dioxus_crate - .dioxus_config - .application - .asset_dir - .canonicalize() - .unwrap_or(std::path::PathBuf::from("./assets")); - // If we have a build result, open it for build_result in results.iter() { let child = build_result.open( + OpenArguments::new( &serve.server_arguments, server.fullstack_address(), - &dioxus_crate.workspace_dir(), - &asset_dir, - server.ip, - dioxus_crate.dioxus_config.application.name.clone(), - dioxus_crate.out_dir() + &dioxus_crate + ) ); match child { Ok(Some(child_proc)) => builder.children.push((build_result.target_platform, child_proc)), @@ -192,12 +184,12 @@ pub async fn serve_all( server.send_reload_command().await; }, - // If the process exited *cleanly*, we can exit + // If the desktop process exited *cleanly*, we can exit Ok(BuilderUpdate::ProcessExited { status, target_platform }) => { // Then remove the child process builder.children.retain(|(platform, _)| *platform != target_platform); - match status { - Ok(status) => { + match (target_platform, status) { + (TargetPlatform::Desktop, Ok(status)) => { if status.success() { break; } @@ -205,7 +197,9 @@ pub async fn serve_all( tracing::error!(dx_src = ?TraceSrc::Dev, "Application exited with status: {status}"); } }, - Err(e) => { + // Ignore the static generation platform exiting + (_ , Ok(_)) => {}, + (_, Err(e)) => { tracing::error!(dx_src = ?TraceSrc::Dev, "Application exited with error: {e}"); } } diff --git a/packages/cli/src/serve/server.rs b/packages/cli/src/serve/server.rs index b253db1b7..ee08dd0a8 100644 --- a/packages/cli/src/serve/server.rs +++ b/packages/cli/src/serve/server.rs @@ -114,7 +114,7 @@ impl Server { // If we're serving a fullstack app, we need to find a port to proxy to let fullstack_port = if matches!( serve.build_arguments.platform(), - Platform::Liveview | Platform::Fullstack | Platform::StaticGeneration + Platform::Liveview | Platform::Fullstack ) { get_available_port(addr.ip()) } else { @@ -368,7 +368,7 @@ fn setup_router( // server the dir if it's web, otherwise let the fullstack server itself handle it match platform { - Platform::Web => { + Platform::Web | Platform::StaticGeneration => { // Route file service to output the .wasm and assets if this is a web build let base_path = format!( "/{}", @@ -382,9 +382,9 @@ fn setup_router( .trim_matches('/') ); - router = router.nest_service(&base_path, build_serve_dir(serve, config)); + router = router.nest_service(&base_path, build_serve_dir(serve, config, platform)); } - Platform::Liveview | Platform::Fullstack | Platform::StaticGeneration => { + Platform::Liveview | Platform::Fullstack => { // For fullstack and static generation, forward all requests to the server let address = fullstack_address.unwrap(); @@ -449,7 +449,11 @@ fn setup_router( Ok(router) } -fn build_serve_dir(serve: &Serve, cfg: &DioxusCrate) -> axum::routing::MethodRouter { +fn build_serve_dir( + serve: &Serve, + cfg: &DioxusCrate, + platform: Platform, +) -> axum::routing::MethodRouter { static CORS_UNSAFE: (HeaderValue, HeaderValue) = ( HeaderValue::from_static("unsafe-none"), HeaderValue::from_static("unsafe-none"), @@ -465,7 +469,11 @@ fn build_serve_dir(serve: &Serve, cfg: &DioxusCrate) -> axum::routing::MethodRou false => CORS_UNSAFE.clone(), }; - let out_dir = cfg.out_dir(); + let out_dir = match platform { + // Static generation only serves files from the public directory + Platform::StaticGeneration => cfg.out_dir().join("public"), + _ => cfg.out_dir(), + }; let index_on_404 = cfg.dioxus_config.web.watcher.index_on_404; get_service( @@ -479,7 +487,7 @@ fn build_serve_dir(serve: &Serve, cfg: &DioxusCrate) -> axum::routing::MethodRou let out_dir = out_dir.clone(); move |response| async move { Ok(no_cache(index_on_404, &out_dir, response)) } }) - .service(ServeDir::new(out_dir)), + .service(ServeDir::new(&out_dir)), ) .handle_error(|error: Infallible| async move { ( @@ -500,7 +508,18 @@ fn no_cache( // If there's a 404 and we're supposed to index on 404, upgrade that failed request to the index.html // We might want to isnert a header here saying we *did* that but oh well if response.status() == StatusCode::NOT_FOUND && index_on_404 { - let body = Body::from(std::fs::read_to_string(out_dir.join("index.html")).unwrap()); + // First try to find a 404.html or 404/index.html file + let out_dir_404_html = out_dir.join("404.html"); + let out_dir_404_index_html = out_dir.join("404").join("index.html"); + let path = if out_dir_404_html.exists() { + out_dir_404_html + } else if out_dir_404_index_html.exists() { + out_dir_404_index_html + } else { + // If we can't find a 404.html or 404/index.html, just use the index.html + out_dir.join("index.html") + }; + let body = Body::from(std::fs::read_to_string(path).unwrap()); response = Response::builder() .status(StatusCode::OK) diff --git a/packages/core/src/launch.rs b/packages/core/src/launch.rs new file mode 100644 index 000000000..f3fa70506 --- /dev/null +++ b/packages/core/src/launch.rs @@ -0,0 +1,8 @@ +//! This module contains utilities renderers use to integrate with the launch function. + +/// A marker trait for platform configs. We use this marker to +/// make sure that the user doesn't accidentally pass in a config +/// builder instead of the config +pub trait LaunchConfig: 'static {} + +impl LaunchConfig for () {} diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 4f2c747c1..10e7e909f 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -12,6 +12,7 @@ mod events; mod fragment; mod generational_box; mod global_context; +mod launch; mod mutations; mod nodes; mod properties; @@ -51,6 +52,7 @@ pub(crate) mod innerlude { pub use crate::fragment::*; pub use crate::generational_box::*; pub use crate::global_context::*; + pub use crate::launch::*; pub use crate::mutations::*; pub use crate::nodes::*; pub use crate::properties::*; @@ -75,10 +77,10 @@ pub(crate) mod innerlude { pub use crate::innerlude::{ fc_to_builder, generation, schedule_update, schedule_update_any, use_hook, vdom_is_rendering, AnyValue, Attribute, AttributeValue, CapturedError, Component, ComponentFunction, DynamicNode, - Element, ElementId, Event, Fragment, HasAttributes, IntoDynNode, MarkerWrapper, Mutation, - Mutations, NoOpMutations, Ok, Properties, Result, Runtime, ScopeId, ScopeState, SpawnIfAsync, - Task, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VNodeInner, VPlaceholder, - VText, VirtualDom, WriteMutations, + Element, ElementId, Event, Fragment, HasAttributes, IntoDynNode, LaunchConfig, MarkerWrapper, + Mutation, Mutations, NoOpMutations, Ok, Properties, Result, Runtime, ScopeId, ScopeState, + SpawnIfAsync, Task, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VNodeInner, + VPlaceholder, VText, VirtualDom, WriteMutations, }; /// The purpose of this module is to alleviate imports of many common types diff --git a/packages/desktop/headless_tests/utils.rs b/packages/desktop/headless_tests/utils.rs index 1f15cdd53..50fac3fb6 100644 --- a/packages/desktop/headless_tests/utils.rs +++ b/packages/desktop/headless_tests/utils.rs @@ -17,7 +17,7 @@ pub fn check_app_exits(app: fn() -> Element) { } }); - LaunchBuilder::desktop() + dioxus::LaunchBuilder::desktop() .with_cfg(Config::new().with_window(WindowBuilder::new().with_visible(false))) .launch(app); diff --git a/packages/desktop/src/config.rs b/packages/desktop/src/config.rs index 1c2ffb234..68f891d33 100644 --- a/packages/desktop/src/config.rs +++ b/packages/desktop/src/config.rs @@ -1,3 +1,4 @@ +use dioxus_core::LaunchConfig; use std::borrow::Cow; use std::path::PathBuf; use tao::window::{Icon, WindowBuilder}; @@ -51,6 +52,8 @@ pub struct Config { pub(crate) last_window_close_behavior: WindowCloseBehaviour, } +impl LaunchConfig for Config {} + pub(crate) type WryProtocol = ( String, Box>) -> HttpResponse> + 'static>, diff --git a/packages/desktop/src/launch.rs b/packages/desktop/src/launch.rs index 1a66d169b..f3da9ad2c 100644 --- a/packages/desktop/src/launch.rs +++ b/packages/desktop/src/launch.rs @@ -1,4 +1,4 @@ -pub use crate::Config; +use crate::Config; use crate::{ app::App, ipc::{IpcMethod, UserWindowEvent}, @@ -82,8 +82,8 @@ pub fn launch_virtual_dom(virtual_dom: VirtualDom, desktop_config: Config) -> ! /// Launches the WebView and runs the event loop, with configuration and root props. pub fn launch( root: fn() -> Element, - contexts: Vec Box>>, - platform_config: Config, + contexts: Vec Box + Send + Sync>>, + platform_config: Vec>, ) -> ! { let mut virtual_dom = VirtualDom::new(root); @@ -91,5 +91,10 @@ pub fn launch( virtual_dom.insert_any_root_context(context()); } + let platform_config = *platform_config + .into_iter() + .find_map(|cfg| cfg.downcast::().ok()) + .unwrap_or_default(); + launch_virtual_dom(virtual_dom, platform_config) } diff --git a/packages/desktop/src/readme.md b/packages/desktop/src/readme.md index 1764d279a..2242ef1de 100644 --- a/packages/desktop/src/readme.md +++ b/packages/desktop/src/readme.md @@ -35,11 +35,11 @@ fn main() { } fn app() -> Element { - rsx!{ + rsx! { div { "hello world!" } - }) + } } ``` diff --git a/packages/dioxus-lib/README.md b/packages/dioxus-lib/README.md index eaba6221b..80d6a1bce 100644 --- a/packages/dioxus-lib/README.md +++ b/packages/dioxus-lib/README.md @@ -44,7 +44,7 @@ To launch an app, we use the `launch` method and use features in `Cargo.toml` to use dioxus::prelude::*; fn main() { - launch(App); + dioxus::launch(App); } // The #[component] attribute streamlines component creation. @@ -239,7 +239,7 @@ Using components, templates, and hooks, we can build a simple app. use dioxus::prelude::*; fn main() { - launch(App); + dioxus::launch(App); } #[component] diff --git a/packages/dioxus/README.md b/packages/dioxus/README.md index 81c0600e9..a5acff195 100644 --- a/packages/dioxus/README.md +++ b/packages/dioxus/README.md @@ -42,7 +42,7 @@ To launch an app, we use the `launch` method and use features in `Cargo.toml` to use dioxus::prelude::*; fn main() { - launch(App); + dioxus::launch(App); } // The #[component] attribute streamlines component creation. @@ -184,7 +184,7 @@ Using components, rsx, and hooks, we can build a simple app. use dioxus::prelude::*; fn main() { - launch(App); + dioxus::launch(App); } #[component] diff --git a/packages/dioxus/src/launch.rs b/packages/dioxus/src/launch.rs index 56143b499..7141f5691 100644 --- a/packages/dioxus/src/launch.rs +++ b/packages/dioxus/src/launch.rs @@ -2,20 +2,22 @@ #![allow(clippy::new_without_default)] #![allow(unused)] use dioxus_config_macro::*; +use dioxus_core::LaunchConfig; use std::any::Any; use crate::prelude::*; /// A builder for a fullstack app. #[must_use] -pub struct LaunchBuilder { - launch_fn: LaunchFn, - contexts: Vec>, - - platform_config: Option, +pub struct LaunchBuilder { + launch_fn: LaunchFn, + contexts: Vec, + configs: Vec>, } -pub type LaunchFn = fn(fn() -> Element, Vec>, Cfg); +pub type LaunchFn = fn(fn() -> Element, Vec, Vec>); +/// A context function is a Send and Sync closure that returns a boxed trait object +pub type ContextFn = Box Box + Send + Sync + 'static>; #[cfg(any( feature = "fullstack", @@ -54,69 +56,74 @@ impl LaunchBuilder { note = "No renderer is enabled. You must enable a renderer feature on the dioxus crate before calling the launch function.\nAdd `web`, `desktop`, `mobile`, `fullstack`, or `static-generation` to the `features` of dioxus field in your Cargo.toml.\n# Example\n```toml\n# ...\n[dependencies]\ndioxus = { version = \"0.5.0\", features = [\"web\"] }\n# ...\n```" ) )] - pub fn new() -> LaunchBuilder { + pub fn new() -> LaunchBuilder { LaunchBuilder { + // We can't use the `current_platform::launch` function directly because it may return ! or () launch_fn: |root, contexts, cfg| current_platform::launch(root, contexts, cfg), contexts: Vec::new(), - platform_config: None, + configs: Vec::new(), } } /// Launch your web application. #[cfg(feature = "web")] #[cfg_attr(docsrs, doc(cfg(feature = "web")))] - pub fn web() -> LaunchBuilder { + pub fn web() -> LaunchBuilder { LaunchBuilder { - launch_fn: dioxus_web::launch::launch, + launch_fn: web_launch, contexts: Vec::new(), - platform_config: None, + configs: Vec::new(), } } /// Launch your desktop application. #[cfg(feature = "desktop")] #[cfg_attr(docsrs, doc(cfg(feature = "desktop")))] - pub fn desktop() -> LaunchBuilder { + pub fn desktop() -> LaunchBuilder { LaunchBuilder { launch_fn: |root, contexts, cfg| dioxus_desktop::launch::launch(root, contexts, cfg), contexts: Vec::new(), - platform_config: None, + configs: Vec::new(), } } - /// Launch your fullstack application. - #[cfg(feature = "fullstack")] - #[cfg_attr(docsrs, doc(cfg(feature = "fullstack")))] - pub fn fullstack() -> LaunchBuilder { + /// Launch your fullstack axum server. + #[cfg(all(feature = "fullstack", feature = "server"))] + #[cfg_attr(docsrs, doc(cfg(all(feature = "fullstack", feature = "server"))))] + pub fn server() -> LaunchBuilder { LaunchBuilder { - launch_fn: |root, contexts, cfg| dioxus_fullstack::launch::launch(root, contexts, cfg), + launch_fn: |root, contexts, cfg| { + dioxus_fullstack::server::launch::launch(root, contexts, cfg) + }, contexts: Vec::new(), - platform_config: None, + configs: Vec::new(), } } /// Launch your static site generation application. - #[cfg(feature = "static-generation")] - #[cfg_attr(docsrs, doc(cfg(feature = "static-generation")))] - pub fn static_generation() -> LaunchBuilder - { + #[cfg(all(feature = "static-generation", feature = "server"))] + #[cfg_attr( + docsrs, + doc(cfg(all(feature = "static-generation", feature = "server"))) + )] + pub fn static_generation() -> LaunchBuilder { LaunchBuilder { launch_fn: |root, contexts, cfg| { dioxus_static_site_generation::launch::launch(root, contexts, cfg) }, contexts: Vec::new(), - platform_config: None, + configs: Vec::new(), } } /// Launch your fullstack application. #[cfg(feature = "mobile")] #[cfg_attr(docsrs, doc(cfg(feature = "mobile")))] - pub fn mobile() -> LaunchBuilder { + pub fn mobile() -> LaunchBuilder { LaunchBuilder { launch_fn: |root, contexts, cfg| dioxus_mobile::launch::launch(root, contexts, cfg), contexts: Vec::new(), - platform_config: None, + configs: Vec::new(), } } @@ -132,7 +139,7 @@ impl LaunchBuilder { /// #[derive(Default)] /// struct Config; /// - /// fn my_custom_launcher(root: fn() -> Element, contexts: Vec>, cfg: Config) { + /// fn my_custom_launcher(root: fn() -> Element, contexts: Vec Box + Send + Sync>>, cfg: Vec>) { /// println!("launching with root: {:?}", root()); /// loop { /// println!("running..."); @@ -145,42 +152,24 @@ impl LaunchBuilder { /// } /// } /// - /// LaunchBuilder::custom(my_custom_launcher).launch(app); + /// dioxus::LaunchBuilder::custom(my_custom_launcher).launch(app); /// ``` - pub fn custom( - launch_fn: LaunchFn, - ) -> LaunchBuilder { + pub fn custom(launch_fn: LaunchFn) -> LaunchBuilder { LaunchBuilder { launch_fn, contexts: vec![], - platform_config: None, + configs: Vec::new(), } } } -// Fullstack platform builder -impl LaunchBuilder { - /// Inject state into the root component's context that is created on the thread that the app is launched on. - pub fn with_context_provider(mut self, state: impl Fn() -> Box + 'static) -> Self { - self.contexts.push(Box::new(state) as Box); - self - } - - /// Inject state into the root component's context. - pub fn with_context(mut self, state: impl Any + Clone + 'static) -> Self { - self.contexts - .push(Box::new(move || Box::new(state.clone()))); - self - } -} - -impl LaunchBuilder { +impl LaunchBuilder { /// Inject state into the root component's context that is created on the thread that the app is launched on. pub fn with_context_provider( mut self, - state: impl Fn() -> Box + Send + Sync + 'static, + state: impl Fn() -> Box + Send + Sync + 'static, ) -> Self { - self.contexts.push(Box::new(state) as Box); + self.contexts.push(Box::new(state)); self } @@ -192,38 +181,10 @@ impl LaunchBuilder { } } -/// A trait for converting a type into a platform-specific config: -/// - A unit value will be converted into `None` -/// - Any config will be converted into `Some(config)` -/// - If the config is for another platform, it will be converted into `None` -pub trait TryIntoConfig { - fn into_config(self, config: &mut Option); -} - -// A config can always be converted into itself -impl TryIntoConfig for Cfg { - fn into_config(self, config: &mut Option) { - *config = Some(self); - } -} - -// The unit type can be converted into the current platform config. -// This makes it possible to use the `desktop!`, `web!`, etc macros with the launch API. -#[cfg(any( - feature = "liveview", - feature = "desktop", - feature = "mobile", - feature = "web", - feature = "fullstack" -))] -impl TryIntoConfig for () { - fn into_config(self, config: &mut Option) {} -} - -impl LaunchBuilder { +impl LaunchBuilder { /// Provide a platform-specific config to the builder. - pub fn with_cfg(mut self, config: impl TryIntoConfig) -> Self { - config.into_config(&mut self.platform_config); + pub fn with_cfg(mut self, config: impl LaunchConfig) -> Self { + self.configs.push(Box::new(config)); self } @@ -231,7 +192,7 @@ impl LaunchBuilder { #[cfg(any(feature = "static-generation", feature = "web"))] /// Launch your application. pub fn launch(self, app: fn() -> Element) { - let cfg = self.platform_config.unwrap_or_default(); + let cfg = self.configs; (self.launch_fn)(app, self.contexts, cfg) } @@ -239,7 +200,7 @@ impl LaunchBuilder { #[cfg(not(any(feature = "static-generation", feature = "web")))] /// Launch your application. pub fn launch(self, app: fn() -> Element) -> ! { - let cfg = self.platform_config.unwrap_or_default(); + let cfg = self.configs; (self.launch_fn)(app, self.contexts, cfg); unreachable!("Launching an application will never exit") @@ -256,147 +217,67 @@ impl LaunchBuilder { /// - `web` /// - `liveview` mod current_platform { - macro_rules! if_else_cfg { - (if $attr:meta { $($then:item)* } else { $($else:item)* }) => { - $( - #[cfg($attr)] - $then - )* - $( - #[cfg(not($attr))] - $else - )* - }; - } - use crate::prelude::TryIntoConfig; + #[cfg(all(feature = "fullstack", feature = "server"))] + pub use dioxus_fullstack::server::launch::*; - #[cfg(feature = "fullstack")] - pub use dioxus_fullstack::launch::*; + #[cfg(all( + feature = "desktop", + not(all(feature = "fullstack", feature = "server")) + ))] + pub use dioxus_desktop::launch::*; - #[cfg(all(feature = "fullstack", feature = "axum"))] - impl TryIntoConfig - for ::dioxus_fullstack::prelude::ServeConfigBuilder - { - fn into_config(self, config: &mut Option) { - match config { - Some(config) => config.set_server_config(self), - None => { - *config = Some( - crate::launch::current_platform::Config::new().with_server_config(self), - ) - } - } - } + #[cfg(all( + feature = "mobile", + not(feature = "desktop"), + not(all(feature = "fullstack", feature = "server")) + ))] + pub use dioxus_mobile::launch::*; + + #[cfg(all( + all(feature = "static-generation", feature = "server"), + not(all(feature = "fullstack", feature = "server")), + not(feature = "desktop"), + not(feature = "mobile") + ))] + pub use dioxus_static_site_generation::launch::*; + + #[cfg(all( + feature = "web", + not(all(feature = "fullstack", feature = "server")), + not(all(feature = "static-generation", feature = "server")), + not(feature = "desktop"), + not(feature = "mobile"), + ))] + pub fn launch( + root: fn() -> dioxus_core::Element, + contexts: Vec, + platform_config: Vec>, + ) { + super::web_launch(root, contexts, platform_config); } - #[cfg(any(feature = "desktop", feature = "mobile"))] - if_else_cfg! { - if not(feature = "fullstack") { - #[cfg(feature = "desktop")] - pub use dioxus_desktop::launch::*; - #[cfg(not(feature = "desktop"))] - pub use dioxus_mobile::launch::*; - } else { - if_else_cfg! { - if feature = "desktop" { - impl TryIntoConfig for ::dioxus_desktop::Config { - fn into_config(self, config: &mut Option) { - match config { - Some(config) => config.set_desktop_config(self), - None => *config = Some(crate::launch::current_platform::Config::new().with_desktop_config(self)), - } - } - } - } else { - impl TryIntoConfig for ::dioxus_mobile::Config { - fn into_config(self, config: &mut Option) { - match config { - Some(config) => config.set_mobile_cfg(self), - None => *config = Some(crate::launch::current_platform::Config::new().with_mobile_cfg(self)), - } - } - } - } - } - } - } - - #[cfg(feature = "static-generation")] - if_else_cfg! { - if all(not(feature = "fullstack"), not(feature = "desktop"), not(feature = "mobile")) { - pub use dioxus_static_site_generation::launch::*; - } else { - impl TryIntoConfig for ::dioxus_static_site_generation::Config { - fn into_config(self, config: &mut Option) {} - } - } - } - - #[cfg(feature = "web")] - if_else_cfg! { - if not(any(feature = "desktop", feature = "mobile", feature = "fullstack", feature = "static-generation")) { - pub use dioxus_web::launch::*; - } else { - if_else_cfg! { - if feature = "fullstack" { - impl TryIntoConfig for ::dioxus_web::Config { - fn into_config(self, config: &mut Option) { - match config { - Some(config) => config.set_web_config(self), - None => *config = Some(crate::launch::current_platform::Config::new().with_web_config(self)), - } - } - } - } else { - impl TryIntoConfig for ::dioxus_web::Config { - fn into_config(self, config: &mut Option) {} - } - } - } - } - } - - #[cfg(feature = "liveview")] - if_else_cfg! { - if - not(any( - feature = "web", - feature = "desktop", - feature = "mobile", - feature = "fullstack", - feature = "static-generation" - )) - { - pub use dioxus_liveview::launch::*; - } else { - impl TryIntoConfig for ::dioxus_liveview::Config { - fn into_config(self, config: &mut Option) {} - } - } - } + #[cfg(all( + feature = "liveview", + not(all(feature = "fullstack", feature = "server")), + not(all(feature = "static-generation", feature = "server")), + not(feature = "desktop"), + not(feature = "mobile"), + not(feature = "web"), + ))] + pub use dioxus_liveview::launch::*; #[cfg(not(any( feature = "liveview", + all(feature = "fullstack", feature = "server"), + all(feature = "static-generation", feature = "server"), feature = "desktop", feature = "mobile", feature = "web", - feature = "fullstack", - feature = "static-generation" - )))] - pub type Config = (); - - #[cfg(not(any( - feature = "liveview", - feature = "desktop", - feature = "mobile", - feature = "web", - feature = "fullstack", - feature = "static-generation" )))] pub fn launch( root: fn() -> dioxus_core::Element, - contexts: Vec>, - platform_config: (), + contexts: Vec, + platform_config: Vec>, ) -> ! { #[cfg(feature = "third-party-renderer")] panic!("No first party renderer feature enabled. It looks like you are trying to use a third party renderer. You will need to use the launch function from the third party renderer crate."); @@ -409,22 +290,6 @@ mod current_platform { macro_rules! impl_launch { ($($return_type:tt),*) => { /// Launch your application without any additional configuration. See [`LaunchBuilder`] for more options. - // If you aren't using a third party renderer and this is not a docs.rs build, generate a warning about no renderer being enabled - #[cfg_attr( - all(not(any( - docsrs, - feature = "third-party-renderer", - feature = "liveview", - feature = "desktop", - feature = "mobile", - feature = "web", - feature = "fullstack", - feature = "static-generation" - ))), - deprecated( - note = "No renderer is enabled. You must enable a renderer feature on the dioxus crate before calling the launch function.\nAdd `web`, `desktop`, `mobile`, `fullstack`, or `static-generation` to the `features` of dioxus field in your Cargo.toml.\n# Example\n```toml\n# ...\n[dependencies]\ndioxus = { version = \"0.5.0\", features = [\"web\"] }\n# ...\n```" - ) - )] pub fn launch(app: fn() -> Element) -> $($return_type)* { #[allow(deprecated)] LaunchBuilder::new().launch(app) @@ -439,29 +304,40 @@ impl_launch!(()); impl_launch!(!); #[cfg(feature = "web")] -#[cfg_attr(docsrs, doc(cfg(feature = "web")))] -/// Launch your web application without any additional configuration. See [`LaunchBuilder`] for more options. -pub fn launch_web(app: fn() -> Element) { - LaunchBuilder::web().launch(app) -} +fn web_launch( + root: fn() -> dioxus_core::Element, + contexts: Vec, + platform_config: Vec>, +) { + // If the server feature is enabled, launch the client with hydration enabled + #[cfg(any(feature = "static-generation", feature = "fullstack"))] + { + let platform_config = platform_config + .into_iter() + .find_map(|cfg| cfg.downcast::().ok()) + .unwrap_or_default() + .hydrate(true); -#[cfg(feature = "desktop")] -#[cfg_attr(docsrs, doc(cfg(feature = "desktop")))] -/// Launch your desktop application without any additional configuration. See [`LaunchBuilder`] for more options. -pub fn launch_desktop(app: fn() -> Element) { - LaunchBuilder::desktop().launch(app) -} + let factory = move || { + let mut vdom = dioxus_core::VirtualDom::new(root); + for context in contexts { + vdom.insert_any_root_context(context()); + } + #[cfg(feature = "document")] + { + #[cfg(feature = "fullstack")] + use dioxus_fullstack::document; + #[cfg(all(feature = "static-generation", not(feature = "fullstack")))] + use dioxus_static_site_generation::document; + let document = std::rc::Rc::new(document::web::FullstackWebDocument) + as std::rc::Rc; + vdom.provide_root_context(document); + } + vdom + }; -#[cfg(feature = "fullstack")] -#[cfg_attr(docsrs, doc(cfg(feature = "fullstack")))] -/// Launch your fullstack application without any additional configuration. See [`LaunchBuilder`] for more options. -pub fn launch_fullstack(app: fn() -> Element) { - LaunchBuilder::fullstack().launch(app) -} - -#[cfg(feature = "mobile")] -#[cfg_attr(docsrs, doc(cfg(feature = "mobile")))] -/// Launch your mobile application without any additional configuration. See [`LaunchBuilder`] for more options. -pub fn launch_mobile(app: fn() -> Element) { - LaunchBuilder::mobile().launch(app) + dioxus_web::launch::launch_virtual_dom(factory(), platform_config) + } + #[cfg(not(any(feature = "static-generation", feature = "fullstack")))] + dioxus_web::launch::launch(root, contexts, platform_config); } diff --git a/packages/dioxus/src/lib.rs b/packages/dioxus/src/lib.rs index 24f231aec..beb7b81c5 100644 --- a/packages/dioxus/src/lib.rs +++ b/packages/dioxus/src/lib.rs @@ -34,8 +34,7 @@ mod launch; #[cfg(feature = "launch")] #[cfg_attr(docsrs, doc(cfg(feature = "launch")))] -#[allow(deprecated)] -pub use launch::launch; +pub use crate::launch::*; #[cfg(feature = "hooks")] #[cfg_attr(docsrs, doc(cfg(feature = "hooks")))] @@ -60,9 +59,6 @@ pub use dioxus_html as html; pub use dioxus_core_macro as core_macro; pub mod prelude { - #[cfg(feature = "launch")] - #[cfg_attr(docsrs, doc(cfg(feature = "launch")))] - pub use crate::launch::*; #[cfg(feature = "hooks")] #[cfg_attr(docsrs, doc(cfg(feature = "hooks")))] diff --git a/packages/fullstack/README.md b/packages/fullstack/README.md index fa0f5c0d2..9272ab335 100644 --- a/packages/fullstack/README.md +++ b/packages/fullstack/README.md @@ -37,7 +37,77 @@ Full stack Dioxus in under 30 lines of code use dioxus::prelude::*; fn main() { - launch(App); + dioxus::launch(App); +} + +#[component] +fn App() -> Element { + let mut meaning = use_signal(|| None); + + rsx! { + h1 { "Meaning of life: {meaning:?}" } + button { + onclick: move |_| async move { + if let Ok(data) = get_meaning("life the universe and everything".into()).await { + meaning.set(data); + } + }, + "Run a server function" + } + } +} + +#[server] +async fn get_meaning(of: String) -> Result, ServerFnError> { + Ok(of.contains("life").then(|| 42)) +} +``` + +## Axum Integration + +If you have an existing Axum router or you need more control over the server, you can use the [`DioxusRouterExt`](https://docs.rs/dioxus-fullstack/0.6.0-alpha.2/dioxus_fullstack/prelude/trait.DioxusRouterExt.html) trait to integrate with your existing Axum router. + +First, make sure your `axum` dependency is optional and enabled by the server feature flag. Axum cannot be compiled to wasm, so if it is enabled by default, it will cause a compile error: + +```toml +[dependencies] +dioxus = { version = "*", features = ["fullstack"] } +axum = { version = "0.7.0", optional = true } + +[features] +server = ["dep:axum", "dioxus/server"] +web = ["dioxus/web"] +``` + +Then we can set up dioxus with the axum server: + +```rust, no_run +#![allow(non_snake_case)] +use dioxus::prelude::*; + +// The entry point for the server +#[cfg(feature = "server")] +fn main() { + // Get the address the server should run on. If the CLI is running, the CLI proxies fullstack into the main address + // and we use the generated address the CLI gives us + let address = dioxus_cli_config::RuntimeCLIArguments::fullstack_address_or_localhost(); + + // Set up the axum router + let router = axum::Router::new() + // You can add a dioxus application to the router with the `serve_dioxus_application` method + // This will add a fallback route to the router that will serve your component and server functions + .serve_dioxus_application(ServeConfigBuilder::default(), App); + + // Finally, we can launch the server + let router = router.into_make_service(); + let listener = tokio::net::TcpListener::bind(address).await.unwrap(); + axum::serve(listener, router).await.unwrap(); +} + +// For any other platform, we just launch the app +#[cfg(not(feature = "server"))] +fn main() { + dioxus::launch(App); } #[component] @@ -111,7 +181,7 @@ fn main() { // For any other platform, we just launch the app #[cfg(not(feature = "server"))] fn main() { - launch(App); + dioxus::launch(App); } #[component] diff --git a/packages/fullstack/src/config.rs b/packages/fullstack/src/config.rs deleted file mode 100644 index 6b82ddaeb..000000000 --- a/packages/fullstack/src/config.rs +++ /dev/null @@ -1,289 +0,0 @@ -//! Launch helper macros for fullstack apps -#![allow(unused)] -use crate::prelude::*; -use dioxus_lib::prelude::*; -use std::sync::Arc; - -/// Settings for a fullstack app. -/// -/// Depending on what features are enabled, you can pass in configurations for each client platform as well as the server: -/// ```rust, no_run -/// # fn app() -> Element { todo!() } -/// use dioxus::prelude::*; -/// -/// let mut cfg = dioxus::fullstack::Config::new(); -/// -/// // Only set the server config if the server feature is enabled -/// server_only! { -/// cfg = cfg.with_server_config(ServeConfigBuilder::default()); -/// } -/// -/// // Only set the web config if the web feature is enabled -/// web! { -/// cfg = cfg.with_web_config(dioxus::web::Config::default()); -/// } -/// -/// // Only set the desktop config if the desktop feature is enabled -/// desktop! { -/// cfg = cfg.with_desktop_config(dioxus::desktop::Config::default()); -/// } -/// -/// // Finally, launch the app with the config -/// LaunchBuilder::new() -/// .with_cfg(cfg) -/// .launch(app); -/// ``` -pub struct Config { - #[cfg(feature = "server")] - pub(crate) server_cfg: ServeConfigBuilder, - - #[cfg(feature = "web")] - pub(crate) web_cfg: dioxus_web::Config, - - #[cfg(feature = "desktop")] - pub(crate) desktop_cfg: dioxus_desktop::Config, - - #[cfg(feature = "mobile")] - pub(crate) mobile_cfg: dioxus_mobile::Config, -} - -#[allow(clippy::derivable_impls)] -impl Default for Config { - fn default() -> Self { - Self { - #[cfg(feature = "server")] - server_cfg: ServeConfigBuilder::new(), - #[cfg(feature = "web")] - web_cfg: dioxus_web::Config::default(), - #[cfg(feature = "desktop")] - desktop_cfg: dioxus_desktop::Config::default(), - #[cfg(feature = "mobile")] - mobile_cfg: dioxus_mobile::Config::default(), - } - } -} - -impl Config { - /// Create a new config for a fullstack app. - pub fn new() -> Self { - Self::default() - } - - /// Set the incremental renderer config. The incremental config can be used to improve - /// performance of heavy routes by caching the rendered html in memory and/or the file system. - /// - /// ```rust, no_run - /// # fn app() -> Element { todo!() } - /// use dioxus::prelude::*; - /// - /// let mut cfg = dioxus::fullstack::Config::new(); - /// - /// // Only set the server config if the server feature is enabled - /// server_only! { - /// cfg = cfg.incremental(IncrementalRendererConfig::default().with_memory_cache_limit(10000)); - /// } - /// - /// // Finally, launch the app with the config - /// LaunchBuilder::new() - /// .with_cfg(cfg) - /// .launch(app); - /// ``` - #[cfg(feature = "server")] - #[cfg_attr(docsrs, doc(cfg(feature = "server")))] - pub fn incremental(self, cfg: IncrementalRendererConfig) -> Self { - Self { - server_cfg: self.server_cfg.incremental(cfg), - ..self - } - } - - /// Set the server config - /// ```rust, no_run - /// # fn app() -> Element { todo!() } - /// use dioxus::prelude::*; - /// - /// let mut cfg = dioxus::fullstack::Config::new(); - /// - /// // Only set the server config if the server feature is enabled - /// server_only! { - /// cfg = cfg.with_server_config(ServeConfigBuilder::default()); - /// } - /// - /// // Finally, launch the app with the config - /// LaunchBuilder::new() - /// .with_cfg(cfg) - /// .launch(app); - /// ``` - #[cfg(feature = "server")] - #[cfg_attr(docsrs, doc(cfg(feature = "server")))] - pub fn with_server_config(self, server_cfg: ServeConfigBuilder) -> Self { - Self { server_cfg, ..self } - } - - /// Set the server config by modifying the config in place - /// - /// ```rust, no_run - /// # fn app() -> Element { todo!() } - /// use dioxus::prelude::*; - /// - /// let mut cfg = dioxus::fullstack::Config::new(); - /// - /// // Only set the server config if the server feature is enabled - /// server_only! { - /// cfg.set_server_config(ServeConfigBuilder::default()); - /// } - /// - /// // Finally, launch the app with the config - /// LaunchBuilder::new() - /// .with_cfg(cfg) - /// .launch(app); - /// ``` - #[cfg(feature = "server")] - #[cfg_attr(docsrs, doc(cfg(feature = "server")))] - pub fn set_server_config(&mut self, server_cfg: ServeConfigBuilder) { - self.server_cfg = server_cfg; - } - - /// Set the web config - /// ```rust, no_run - /// # fn app() -> Element { todo!() } - /// use dioxus::prelude::*; - /// - /// let mut cfg = dioxus::fullstack::Config::new(); - /// - /// // Only set the web config if the server feature is enabled - /// web! { - /// cfg = cfg.with_web_config(dioxus::web::Config::default()); - /// } - /// - /// // Finally, launch the app with the config - /// LaunchBuilder::new() - /// .with_cfg(cfg) - /// .launch(app); - /// ``` - #[cfg(feature = "web")] - #[cfg_attr(docsrs, doc(cfg(feature = "web")))] - pub fn with_web_config(self, web_cfg: dioxus_web::Config) -> Self { - Self { web_cfg, ..self } - } - - /// Set the server config by modifying the config in place - /// - /// ```rust, no_run - /// # fn app() -> Element { todo!() } - /// use dioxus::prelude::*; - /// - /// let mut cfg = dioxus::fullstack::Config::new(); - /// - /// // Only set the web config if the server feature is enabled - /// web! { - /// cfg.set_web_config(dioxus::web::Config::default()); - /// } - /// - /// // Finally, launch the app with the config - /// LaunchBuilder::new() - /// .with_cfg(cfg) - /// .launch(app); - /// ``` - #[cfg(feature = "web")] - #[cfg_attr(docsrs, doc(cfg(feature = "web")))] - pub fn set_web_config(&mut self, web_cfg: dioxus_web::Config) { - self.web_cfg = web_cfg; - } - - /// Set the desktop config - /// ```rust, no_run - /// # fn app() -> Element { todo!() } - /// use dioxus::prelude::*; - /// - /// let mut cfg = dioxus::fullstack::Config::new(); - /// - /// // Only set the desktop config if the server feature is enabled - /// desktop! { - /// cfg = cfg.with_desktop_config(dioxus::desktop::Config::default()); - /// } - /// - /// // Finally, launch the app with the config - /// LaunchBuilder::new() - /// .with_cfg(cfg) - /// .launch(app); - /// ``` - #[cfg(feature = "desktop")] - #[cfg_attr(docsrs, doc(cfg(feature = "desktop")))] - pub fn with_desktop_config(self, desktop_cfg: dioxus_desktop::Config) -> Self { - Self { - desktop_cfg, - ..self - } - } - - /// Set the desktop config by modifying the config in place - /// - /// ```rust, no_run - /// # fn app() -> Element { todo!() } - /// use dioxus::prelude::*; - /// - /// let mut cfg = dioxus::fullstack::Config::new(); - /// - /// // Only set the desktop config if the server feature is enabled - /// desktop! { - /// cfg.set_desktop_config(dioxus::desktop::Config::default()); - /// } - /// - /// // Finally, launch the app with the config - /// LaunchBuilder::new() - /// .with_cfg(cfg) - /// .launch(app); - /// ``` - #[cfg(feature = "desktop")] - #[cfg_attr(docsrs, doc(cfg(feature = "desktop")))] - pub fn set_desktop_config(&mut self, desktop_cfg: dioxus_desktop::Config) { - self.desktop_cfg = desktop_cfg; - } - - /// Set the mobile config - /// ```rust, no_run - /// # fn app() -> Element { todo!() } - /// use dioxus::prelude::*; - /// - /// let mut cfg = dioxus::fullstack::Config::new(); - /// - /// // Only set the mobile config if the server feature is enabled - /// mobile! { - /// cfg = cfg.with_mobile_cfg(dioxus::mobile::Config::default()); - /// } - /// - /// // Finally, launch the app with the config - /// LaunchBuilder::new() - /// .with_cfg(cfg) - /// .launch(app); - /// Set the mobile config. - #[cfg(feature = "mobile")] - #[cfg_attr(docsrs, doc(cfg(feature = "mobile")))] - pub fn with_mobile_cfg(self, mobile_cfg: dioxus_mobile::Config) -> Self { - Self { mobile_cfg, ..self } - } - - /// Set the mobile config by modifying the config in place - /// ```rust, no_run - /// # fn app() -> Element { todo!() } - /// use dioxus::prelude::*; - /// - /// let mut cfg = dioxus::fullstack::Config::new(); - /// - /// // Only set the mobile config if the server feature is enabled - /// mobile! { - /// cfg.set_mobile_cfg(dioxus::mobile::Config::default()); - /// } - /// - /// // Finally, launch the app with the config - /// LaunchBuilder::new() - /// .with_cfg(cfg) - /// .launch(app); - /// Set the mobile config. - #[cfg(feature = "mobile")] - #[cfg_attr(docsrs, doc(cfg(feature = "mobile")))] - pub fn set_mobile_cfg(&mut self, mobile_cfg: dioxus_mobile::Config) { - self.mobile_cfg = mobile_cfg; - } -} diff --git a/packages/fullstack/src/document/mod.rs b/packages/fullstack/src/document/mod.rs index 0c380994f..173b58b97 100644 --- a/packages/fullstack/src/document/mod.rs +++ b/packages/fullstack/src/document/mod.rs @@ -1,8 +1,8 @@ //! This module contains the document providers for the fullstack platform. #[cfg(feature = "server")] -pub(crate) mod server; +pub mod server; #[cfg(feature = "server")] pub use server::ServerDocument; #[cfg(all(feature = "web", feature = "document"))] -pub(crate) mod web; +pub mod web; diff --git a/packages/fullstack/src/document/web.rs b/packages/fullstack/src/document/web.rs index 3169fa05a..66d096b01 100644 --- a/packages/fullstack/src/document/web.rs +++ b/packages/fullstack/src/document/web.rs @@ -11,7 +11,8 @@ fn head_element_written_on_server() -> bool { .unwrap_or_default() } -pub(crate) struct FullstackWebDocument; +/// A document provider for fullstack web clients +pub struct FullstackWebDocument; impl Document for FullstackWebDocument { fn new_evaluator( diff --git a/packages/fullstack/src/launch.rs b/packages/fullstack/src/launch.rs deleted file mode 100644 index e590efc41..000000000 --- a/packages/fullstack/src/launch.rs +++ /dev/null @@ -1,171 +0,0 @@ -//! This module contains the `launch` function, which is the main entry point for dioxus fullstack - -use std::{any::Any, sync::Arc}; - -use dioxus_lib::prelude::{Element, VirtualDom}; - -pub use crate::Config; - -#[allow(unused)] -pub(crate) type ContextProviders = Arc< - Vec Box + Send + Sync + 'static>>, ->; - -#[allow(unused)] -fn virtual_dom_factory( - root: fn() -> Element, - contexts: ContextProviders, -) -> impl Fn() -> VirtualDom + 'static { - move || { - let mut vdom = VirtualDom::new(root); - for context in &*contexts { - vdom.insert_any_root_context(context()); - } - vdom - } -} - -#[cfg(feature = "server")] -/// Launch a fullstack app with the given root component, contexts, and config. -#[allow(unused)] -pub fn launch( - root: fn() -> Element, - contexts: Vec Box + Send + Sync>>, - platform_config: Config, -) -> ! { - let contexts = Arc::new(contexts); - let factory = virtual_dom_factory(root, contexts.clone()); - #[cfg(all(feature = "server", not(target_arch = "wasm32")))] - tokio::runtime::Runtime::new() - .unwrap() - .block_on(async move { - launch_server(platform_config, factory, contexts).await; - }); - - unreachable!("Launching a fullstack app should never return") -} - -#[cfg(all(not(feature = "server"), feature = "web"))] -/// Launch a fullstack app with the given root component, contexts, and config. -#[allow(unused)] -pub fn launch( - root: fn() -> Element, - #[allow(unused_mut)] mut contexts: Vec< - Box Box + Send + Sync>, - >, - platform_config: Config, -) { - let contexts = Arc::new(contexts); - let mut factory = virtual_dom_factory(root, contexts); - let cfg = platform_config.web_cfg.hydrate(true); - - #[cfg(feature = "document")] - let factory = move || { - let mut vdom = factory(); - let document = std::rc::Rc::new(crate::document::web::FullstackWebDocument) - as std::rc::Rc; - vdom.provide_root_context(document); - vdom - }; - - dioxus_web::launch::launch_virtual_dom(factory(), cfg) -} - -#[cfg(all(not(any(feature = "server", feature = "web")), feature = "desktop"))] -/// Launch a fullstack app with the given root component, contexts, and config. -#[allow(unused)] -pub fn launch( - root: fn() -> Element, - contexts: Vec Box + Send + Sync>>, - platform_config: Config, -) -> ! { - let contexts = Arc::new(contexts); - let factory = virtual_dom_factory(root, contexts); - let cfg = platform_config.desktop_cfg; - dioxus_desktop::launch::launch_virtual_dom(factory(), cfg) -} - -#[cfg(all( - not(any(feature = "server", feature = "web", feature = "desktop")), - feature = "mobile" -))] -/// Launch a fullstack app with the given root component, contexts, and config. -#[allow(unused)] -pub fn launch( - root: fn() -> Element, - contexts: Vec Box + Send + Sync>>, - platform_config: Config, -) -> ! { - let contexts = Arc::new(contexts); - let factory = virtual_dom_factory(root, contexts.clone()); - let cfg = platform_config.mobile_cfg; - dioxus_mobile::launch::launch_virtual_dom(factory(), cfg) -} - -#[cfg(not(any( - feature = "server", - feature = "web", - feature = "desktop", - feature = "mobile" -)))] -/// Launch a fullstack app with the given root component, contexts, and config. -#[allow(unused)] -pub fn launch( - root: fn() -> Element, - contexts: Vec Box + Send + Sync>>, - platform_config: Config, -) -> ! { - panic!("No platform feature enabled. Please enable one of the following features: axum, desktop, or web to use the launch API.") -} - -#[cfg(feature = "server")] -#[allow(unused)] -/// Launch a server application -async fn launch_server( - platform_config: Config, - build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static, - context_providers: ContextProviders, -) { - // Get the address the server should run on. If the CLI is running, the CLI proxies fullstack into the main address - // and we use the generated address the CLI gives us - let address = dioxus_cli_config::fullstack_address_or_localhost(); - - #[cfg(feature = "axum")] - { - use crate::axum_adapter::DioxusRouterExt; - - #[allow(unused_mut)] - let mut router = - axum::Router::new().register_server_functions_with_context(context_providers); - - #[cfg(not(any(feature = "desktop", feature = "mobile")))] - { - use crate::prelude::RenderHandleState; - use crate::prelude::SSRState; - - match platform_config.server_cfg.build() { - Ok(cfg) => { - router = router.serve_static_assets(); - - router = router.fallback( - axum::routing::get(crate::axum_adapter::render_handler).with_state( - RenderHandleState::new_with_virtual_dom_factory(cfg, build_virtual_dom), - ), - ); - } - Err(err) => { - tracing::trace!("Failed to create render handler. This is expected if you are only using fullstack for desktop/mobile server functions: {}", err); - } - } - } - - let router = router.into_make_service(); - let listener = tokio::net::TcpListener::bind(address).await.unwrap(); - - axum::serve(listener, router).await.unwrap(); - } - #[cfg(not(feature = "axum"))] - { - panic!("Launching with dioxus fullstack requires the axum feature. If you are using a community fullstack adapter, please check the documentation for that adapter to see how to launch the application."); - } -} diff --git a/packages/fullstack/src/lib.rs b/packages/fullstack/src/lib.rs index 7e32eb114..b7cf7dc43 100644 --- a/packages/fullstack/src/lib.rs +++ b/packages/fullstack/src/lib.rs @@ -10,13 +10,9 @@ mod html_storage; #[cfg(feature = "axum")] #[cfg_attr(docsrs, doc(cfg(feature = "axum")))] -mod axum_adapter; +pub mod server; -mod config; mod hooks; -pub mod launch; - -pub use config::*; pub mod document; #[cfg(feature = "server")] @@ -39,7 +35,7 @@ pub mod prelude { #[cfg(feature = "axum")] #[cfg_attr(docsrs, doc(cfg(feature = "axum")))] - pub use crate::axum_adapter::*; + pub use crate::server::*; #[cfg(feature = "server")] #[cfg_attr(docsrs, doc(cfg(feature = "server")))] diff --git a/packages/fullstack/src/serve_config.rs b/packages/fullstack/src/serve_config.rs index 272a50fc0..ce3ff430c 100644 --- a/packages/fullstack/src/serve_config.rs +++ b/packages/fullstack/src/serve_config.rs @@ -5,6 +5,8 @@ use std::fs::File; use std::io::Read; use std::path::PathBuf; +use dioxus_lib::prelude::dioxus_core::LaunchConfig; + /// A ServeConfig is used to configure how to serve a Dioxus application. It contains information about how to serve static assets, and what content to render with [`dioxus-ssr`]. #[derive(Clone, Default)] pub struct ServeConfigBuilder { @@ -14,6 +16,8 @@ pub struct ServeConfigBuilder { pub(crate) incremental: Option, } +impl LaunchConfig for ServeConfigBuilder {} + impl ServeConfigBuilder { /// Create a new ServeConfigBuilder with incremental static generation disabled and the default index.html settings pub fn new() -> Self { @@ -239,6 +243,8 @@ pub struct ServeConfig { pub(crate) incremental: Option, } +impl LaunchConfig for ServeConfig {} + impl ServeConfig { /// Create a new ServeConfig pub fn new() -> Result { diff --git a/packages/fullstack/src/server/launch.rs b/packages/fullstack/src/server/launch.rs new file mode 100644 index 000000000..15ed239d3 --- /dev/null +++ b/packages/fullstack/src/server/launch.rs @@ -0,0 +1,60 @@ +//! A launch function that creates an axum router for the LaunchBuilder + +use std::any::Any; + +use dioxus_lib::prelude::*; + +/// Launch a fullstack app with the given root component, contexts, and config. +#[allow(unused)] +pub fn launch( + root: fn() -> Element, + contexts: Vec Box + Send + Sync>>, + platform_config: Vec>, +) -> ! { + use crate::{ServeConfig, ServeConfigBuilder}; + + #[cfg(not(target_arch = "wasm32"))] + tokio::runtime::Runtime::new() + .unwrap() + .block_on(async move { + let platform_config = platform_config + .into_iter() + .find_map(|cfg| { + cfg.downcast::() + .map(|cfg| Result::Ok(*cfg)) + .or_else(|cfg| { + cfg.downcast::() + .map(|builder| builder.build()) + }) + .ok() + }) + .unwrap_or_else(ServeConfig::new); + + // Get the address the server should run on. If the CLI is running, the CLI proxies fullstack into the main address + // and we use the generated address the CLI gives us + let address = dioxus_cli_config::fullstack_address_or_localhost(); + + use crate::server::DioxusRouterExt; + + struct TryIntoResult(Result); + + impl TryInto for TryIntoResult { + type Error = crate::UnableToLoadIndex; + + fn try_into(self) -> Result { + self.0 + } + } + + #[allow(unused_mut)] + let mut router = + axum::Router::new().serve_dioxus_application(TryIntoResult(platform_config), root); + + let router = router.into_make_service(); + let listener = tokio::net::TcpListener::bind(address).await.unwrap(); + + axum::serve(listener, router).await.unwrap(); + }); + + unreachable!("Launching a fullstack app should never return") +} diff --git a/packages/fullstack/src/axum_adapter.rs b/packages/fullstack/src/server/mod.rs similarity index 97% rename from packages/fullstack/src/axum_adapter.rs rename to packages/fullstack/src/server/mod.rs index e0aaa85d3..8dac921b2 100644 --- a/packages/fullstack/src/axum_adapter.rs +++ b/packages/fullstack/src/server/mod.rs @@ -8,7 +8,7 @@ //! fn main() { //! #[cfg(feature = "web")] //! // Hydrate the application on the client -//! launch(app); +//! dioxus::launch(app); //! #[cfg(feature = "server")] //! { //! tokio::runtime::Runtime::new() @@ -52,6 +52,12 @@ //! } //! ``` +pub mod launch; + +#[allow(unused)] +pub(crate) type ContextProviders = + Arc Box + Send + Sync + 'static>>>; + use axum::routing::*; use axum::{ body::{self, Body}, @@ -64,7 +70,6 @@ use http::header::*; use std::sync::Arc; -use crate::launch::ContextProviders; use crate::prelude::*; /// A extension trait with utilities for integrating Dioxus with your Axum router. @@ -177,8 +182,6 @@ where ) -> Self { use http::method::Method; - let context_providers = Arc::new(context_providers); - for (path, method) in server_fn::axum::server_fn_paths() { tracing::trace!("Registering server function: {} {}", method, path); let context_providers = context_providers.clone(); @@ -186,9 +189,10 @@ where handle_server_fns_inner( path, move |server_context| { - for context_provider in context_providers.iter() { - let context = context_provider(); - server_context.insert_any(context); + for index in 0..context_providers.len() { + let context_providers = context_providers.clone(); + server_context + .insert_boxed_factory(Box::new(move || context_providers[index]())); } }, req, diff --git a/packages/fullstack/src/server_context.rs b/packages/fullstack/src/server_context.rs index 02efa363d..7c321e494 100644 --- a/packages/fullstack/src/server_context.rs +++ b/packages/fullstack/src/server_context.rs @@ -3,8 +3,7 @@ use std::any::Any; use std::collections::HashMap; use std::sync::Arc; -type SendSyncAnyMap = - std::collections::HashMap>; +type SendSyncAnyMap = std::collections::HashMap; /// A shared context for server functions that contains information about the request and middleware state. /// @@ -29,6 +28,20 @@ pub struct DioxusServerContext { pub(crate) parts: Arc>, } +enum ContextType { + Factory(Box Box + Send + Sync>), + Value(Box), +} + +impl ContextType { + fn downcast(&self) -> Option { + match self { + ContextType::Value(value) => value.downcast_ref::().cloned(), + ContextType::Factory(factory) => factory().downcast::().ok().map(|v| *v), + } + } +} + #[allow(clippy::derivable_impls)] impl Default for DioxusServerContext { fn default() -> Self { @@ -103,21 +116,38 @@ mod server_fn_impl { self.shared_context .read() .get(&TypeId::of::()) - .map(|v| v.downcast_ref::().unwrap().clone()) + .map(|v| v.downcast::().unwrap()) } /// Insert a value into the shared server context - /// - /// pub fn insert(&self, value: T) { self.insert_any(Box::new(value)); } - /// Insert a Boxed `Any` value into the shared server context - pub fn insert_any(&self, value: Box) { + /// Insert a boxed `Any` value into the shared server context + pub fn insert_any(&self, value: Box) { self.shared_context .write() - .insert((*value).type_id(), value); + .insert((*value).type_id(), ContextType::Value(value)); + } + + /// Insert a factory that creates a non-sync value for the shared server context + pub fn insert_factory(&self, value: F) + where + F: Fn() -> T + Send + Sync + 'static, + T: 'static, + { + self.shared_context.write().insert( + TypeId::of::(), + ContextType::Factory(Box::new(move || Box::new(value()))), + ); + } + + /// Insert a boxed factory that creates a non-sync value for the shared server context + pub fn insert_boxed_factory(&self, value: Box Box + Send + Sync>) { + self.shared_context + .write() + .insert((*value()).type_id(), ContextType::Factory(value)); } /// Get the response parts from the server context @@ -324,7 +354,7 @@ impl std::error::Error for NotFoundInServerContext {} /// ```rust, no_run /// use dioxus::prelude::*; /// -/// LaunchBuilder::new() +/// dioxus::LaunchBuilder::new() /// // You can provide context to your whole app (including server functions) with the `with_context` method on the launch builder /// .with_context(server_only! { /// 1234567890u32 diff --git a/packages/liveview/src/config.rs b/packages/liveview/src/config.rs index 0f22eba8b..a33b7bff8 100644 --- a/packages/liveview/src/config.rs +++ b/packages/liveview/src/config.rs @@ -1,4 +1,4 @@ -use dioxus_core::VirtualDom; +use dioxus_core::{LaunchConfig, VirtualDom}; use crate::LiveviewRouter; @@ -13,6 +13,8 @@ pub struct Config { route: String, } +impl LaunchConfig for Config {} + impl Default for Config { fn default() -> Self { Self { diff --git a/packages/playwright-tests/fullstack/src/main.rs b/packages/playwright-tests/fullstack/src/main.rs index 59edd973e..ab6a89b7b 100644 --- a/packages/playwright-tests/fullstack/src/main.rs +++ b/packages/playwright-tests/fullstack/src/main.rs @@ -8,7 +8,7 @@ use dioxus::{prelude::*, CapturedError}; fn main() { - LaunchBuilder::fullstack().launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/packages/playwright-tests/nested-suspense/src/main.rs b/packages/playwright-tests/nested-suspense/src/main.rs index c8e275ff9..4d5f3e9e1 100644 --- a/packages/playwright-tests/nested-suspense/src/main.rs +++ b/packages/playwright-tests/nested-suspense/src/main.rs @@ -12,7 +12,7 @@ use dioxus::prelude::*; use serde::{Deserialize, Serialize}; fn main() { - LaunchBuilder::fullstack().launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/packages/playwright-tests/static-generation/src/main.rs b/packages/playwright-tests/static-generation/src/main.rs index 06a98c221..7540b226b 100644 --- a/packages/playwright-tests/static-generation/src/main.rs +++ b/packages/playwright-tests/static-generation/src/main.rs @@ -8,9 +8,13 @@ use dioxus::prelude::*; fn main() { - LaunchBuilder::static_generation().launch(app); + #[cfg(feature = "web")] + dioxus::LaunchBuilder::web().launch(app); + #[cfg(feature = "server")] + dioxus::LaunchBuilder::static_generation().launch(app); } +#[allow(unused)] fn app() -> Element { let mut count = use_signal(|| 12345); let server_data = use_server_future(get_server_data)?; diff --git a/packages/playwright-tests/suspense-carousel/src/main.rs b/packages/playwright-tests/suspense-carousel/src/main.rs index 6d304ba88..fd72d73a3 100644 --- a/packages/playwright-tests/suspense-carousel/src/main.rs +++ b/packages/playwright-tests/suspense-carousel/src/main.rs @@ -107,5 +107,5 @@ fn NestedSuspendedComponent(id: i32) -> Element { } fn main() { - launch(app); + dioxus::launch(app); } diff --git a/packages/playwright-tests/web/src/main.rs b/packages/playwright-tests/web/src/main.rs index 126f729e4..e04668c13 100644 --- a/packages/playwright-tests/web/src/main.rs +++ b/packages/playwright-tests/web/src/main.rs @@ -77,5 +77,5 @@ fn main() { .set_max_level(tracing::Level::TRACE) .build(), ); - launch(app); + dioxus::launch(app); } diff --git a/packages/router/examples/simple_routes.rs b/packages/router/examples/simple_routes.rs index 3ee695dee..2083d4635 100644 --- a/packages/router/examples/simple_routes.rs +++ b/packages/router/examples/simple_routes.rs @@ -44,7 +44,7 @@ async fn main() { #[cfg(not(feature = "liveview"))] fn main() { - launch(app) + dioxus::launch(app) } fn app() -> Element { diff --git a/packages/rsx-hotreload/tests/valid/combo.new.rsx b/packages/rsx-hotreload/tests/valid/combo.new.rsx index 08a0c49b1..dd0a52e78 100644 --- a/packages/rsx-hotreload/tests/valid/combo.new.rsx +++ b/packages/rsx-hotreload/tests/valid/combo.new.rsx @@ -64,5 +64,5 @@ fn main() { // .set_max_level(tracing::Level::TRACE) // .build(), // ); - launch(app); + dioxus::launch(app); } diff --git a/packages/rsx-hotreload/tests/valid/combo.old.rsx b/packages/rsx-hotreload/tests/valid/combo.old.rsx index d08ca4e87..545490ede 100644 --- a/packages/rsx-hotreload/tests/valid/combo.old.rsx +++ b/packages/rsx-hotreload/tests/valid/combo.old.rsx @@ -53,5 +53,5 @@ fn main() { // .set_max_level(tracing::Level::TRACE) // .build(), // ); - launch(app); + dioxus::launch(app); } diff --git a/packages/signals/examples/context.rs b/packages/signals/examples/context.rs index 1e22423d0..e769a3ea8 100644 --- a/packages/signals/examples/context.rs +++ b/packages/signals/examples/context.rs @@ -3,7 +3,7 @@ use dioxus::prelude::*; fn main() { - launch(app) + dioxus::launch(app) } // Because signal is never read in this component, this component will not rerun when the signal changes diff --git a/packages/signals/examples/dependencies.rs b/packages/signals/examples/dependencies.rs index 70319da40..81fce00a1 100644 --- a/packages/signals/examples/dependencies.rs +++ b/packages/signals/examples/dependencies.rs @@ -1,7 +1,7 @@ use dioxus::prelude::*; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/packages/signals/examples/map_signal.rs b/packages/signals/examples/map_signal.rs index 71d882638..f8a5cd681 100644 --- a/packages/signals/examples/map_signal.rs +++ b/packages/signals/examples/map_signal.rs @@ -3,7 +3,7 @@ use dioxus::prelude::*; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/packages/signals/examples/read_only_degrade.rs b/packages/signals/examples/read_only_degrade.rs index a69101bec..52e6efe88 100644 --- a/packages/signals/examples/read_only_degrade.rs +++ b/packages/signals/examples/read_only_degrade.rs @@ -4,7 +4,7 @@ use dioxus::prelude::*; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/packages/signals/examples/selector.rs b/packages/signals/examples/selector.rs index c9f578ddb..6e282cbc5 100644 --- a/packages/signals/examples/selector.rs +++ b/packages/signals/examples/selector.rs @@ -1,7 +1,7 @@ use dioxus::prelude::*; fn main() { - launch(app) + dioxus::launch(app) } fn app() -> Element { diff --git a/packages/signals/examples/send.rs b/packages/signals/examples/send.rs index 5d9c5e51f..3591d9f05 100644 --- a/packages/signals/examples/send.rs +++ b/packages/signals/examples/send.rs @@ -1,7 +1,7 @@ use dioxus::prelude::*; fn main() { - launch(app); + dioxus::launch(app); } fn app() -> Element { diff --git a/packages/signals/examples/split_subscriptions.rs b/packages/signals/examples/split_subscriptions.rs index da02ba34f..b041c533d 100644 --- a/packages/signals/examples/split_subscriptions.rs +++ b/packages/signals/examples/split_subscriptions.rs @@ -4,7 +4,7 @@ use dioxus::prelude::*; use dioxus_signals::Signal; fn main() { - launch(app); + dioxus::launch(app); } #[derive(Clone, Copy, Default)] diff --git a/packages/static-generation/README.md b/packages/static-generation/README.md index a3b5c2e17..35c22661d 100644 --- a/packages/static-generation/README.md +++ b/packages/static-generation/README.md @@ -37,7 +37,7 @@ Full stack Dioxus in under 30 lines of code use dioxus::prelude::*; fn main() { - launch(App); + dioxus::launch(App); } #[component] diff --git a/packages/static-generation/src/config.rs b/packages/static-generation/src/config.rs index 12f123a7a..f3552a7d3 100644 --- a/packages/static-generation/src/config.rs +++ b/packages/static-generation/src/config.rs @@ -2,6 +2,8 @@ use std::path::PathBuf; +use dioxus_lib::prelude::dioxus_core::LaunchConfig; + /// Settings for a statically generated site that may be hydrated in the browser pub struct Config { #[cfg(feature = "server")] @@ -25,12 +27,10 @@ pub struct Config { #[cfg(feature = "server")] pub(crate) github_pages: bool, - - #[cfg(feature = "web")] - #[allow(unused)] - pub(crate) web_cfg: dioxus_web::Config, } +impl LaunchConfig for Config {} + #[allow(clippy::derivable_impls)] impl Default for Config { fn default() -> Self { @@ -49,8 +49,6 @@ impl Default for Config { additional_routes: vec!["/".to_string()], #[cfg(feature = "server")] github_pages: false, - #[cfg(feature = "web")] - web_cfg: dioxus_web::Config::default(), } } } @@ -144,16 +142,6 @@ impl Config { } myself } - - /// Set the web config. - /// - /// This method will only effect the hydrated web renderer. - #[cfg(feature = "web")] - #[cfg_attr(docsrs, doc(cfg(feature = "web")))] - pub fn web_cfg(self, web_cfg: dioxus_web::Config) -> Self { - #[allow(clippy::needless_update)] - Self { web_cfg, ..self } - } } #[cfg(feature = "server")] diff --git a/packages/static-generation/src/launch.rs b/packages/static-generation/src/launch.rs index a5a7297ef..db949ab77 100644 --- a/packages/static-generation/src/launch.rs +++ b/packages/static-generation/src/launch.rs @@ -2,25 +2,15 @@ use std::any::Any; -use dioxus_lib::prelude::{Element, VirtualDom}; - -pub use crate::Config; +use dioxus_lib::prelude::Element; /// Launch a fullstack app with the given root component, contexts, and config. #[allow(unused)] pub fn launch( root: fn() -> Element, - contexts: Vec Box + Send + Sync>>, - platform_config: Config, + contexts: Vec Box + Send + Sync>>, + platform_config: Vec>, ) { - let virtual_dom_factory = move || { - let mut vdom = VirtualDom::new(root); - for context in &contexts { - vdom.insert_any_root_context(context()); - } - vdom - }; - #[cfg(feature = "server")] tokio::runtime::Runtime::new() .unwrap() @@ -33,56 +23,15 @@ pub fn launch( use http::StatusCode; use tower_http::services::ServeDir; use tower_http::services::ServeFile; + let platform_config = platform_config + .into_iter() + .find_map(|cfg| cfg.downcast::().map(|cfg| *cfg).ok()) + .unwrap_or_else(crate::Config::new); let github_pages = platform_config.github_pages; let path = platform_config.output_dir.clone(); crate::ssg::generate_static_site(root, platform_config) .await .unwrap(); - - // Serve the program if we are running with cargo - if std::env::var_os("CARGO").is_some() || dioxus_cli_config::is_cli_enabled() { - // Get the address the server should run on. If the CLI is running, the CLI proxies static generation into the main address - // and we use the generated address the CLI gives us - let serve_address = dioxus_cli_config::fullstack_address_or_localhost(); - - // Point the user to the CLI address if the CLI is running or the fullstack address if not - println!( - "Serving static files from {} at http://{serve_address}", - path.display() - ); - - let mut serve_dir = - ServeDir::new(path.clone()).call_fallback_on_method_not_allowed(true); - - let mut router = axum::Router::new(); - - // If we are acting like github pages, we need to serve the 404 page if the user requests a directory that doesn't exist - router = if github_pages { - router.fallback_service( - serve_dir.fallback(ServeFile::new(path.join("404/index.html"))), - ) - } else { - router.fallback_service(serve_dir.fallback(get(|| async move { - "The requested path does not exist" - .to_string() - .into_response() - }))) - }; - - let listener = tokio::net::TcpListener::bind(serve_address).await.unwrap(); - axum::serve(listener, router.into_make_service()) - .await - .unwrap(); - } }); - - #[cfg(not(feature = "server"))] - { - #[cfg(feature = "web")] - { - let cfg = platform_config.web_cfg.hydrate(true); - dioxus_web::launch::launch_virtual_dom(virtual_dom_factory(), cfg); - } - } } diff --git a/packages/static-generation/src/lib.rs b/packages/static-generation/src/lib.rs index 950ecc09c..21266da77 100644 --- a/packages/static-generation/src/lib.rs +++ b/packages/static-generation/src/lib.rs @@ -16,3 +16,8 @@ pub(crate) mod ssg; pub mod prelude { pub use dioxus_fullstack::prelude::*; } + +/// A document provider for static generation apps +pub mod document { + pub use dioxus_fullstack::document::*; +} diff --git a/packages/static-generation/src/ssg.rs b/packages/static-generation/src/ssg.rs index a4cae763c..f43405464 100644 --- a/packages/static-generation/src/ssg.rs +++ b/packages/static-generation/src/ssg.rs @@ -4,7 +4,7 @@ use dioxus_router::prelude::*; use dioxus_ssr::renderer; use std::collections::HashSet; use std::fs; -use std::path::Path; +use std::path::{Path, PathBuf}; use crate::Config; @@ -65,20 +65,24 @@ pub async fn generate_static_site( } // Copy over the web output dir into the static output dir - let assets_path = dioxus_cli_config::out_dir().unwrap_or("./dist".into()); + let out_path = dioxus_cli_config::out_dir().unwrap_or("./dist".into()); - let assets_path = assets_path.join("public"); + let assets_path = out_path.join("public"); - copy_static_files(&assets_path, &config.output_dir)?; + let index_path = assets_path.join("index.html"); + let skip = vec![index_path.clone()]; + copy_static_files(&assets_path, &config.output_dir, &skip)?; + + // Copy the output of the SSG build into the public directory so the CLI serves it + copy_static_files(&config.output_dir, &assets_path, &[])?; Ok(()) } -fn copy_static_files(src: &Path, dst: &Path) -> Result<(), std::io::Error> { - let index_path = src.join("index.html"); +fn copy_static_files(src: &Path, dst: &Path, skip: &[PathBuf]) -> Result<(), std::io::Error> { let mut queue = vec![src.to_path_buf()]; while let Some(path) = queue.pop() { - if path == index_path { + if skip.contains(&path) { continue; } if path.is_dir() { diff --git a/packages/web/src/cfg.rs b/packages/web/src/cfg.rs index ddc589414..a7ed6f223 100644 --- a/packages/web/src/cfg.rs +++ b/packages/web/src/cfg.rs @@ -1,3 +1,4 @@ +use dioxus_core::LaunchConfig; use wasm_bindgen::JsCast as _; /// Configuration for the WebSys renderer for the Dioxus VirtualDOM. @@ -15,6 +16,8 @@ pub struct Config { pub(crate) default_panic_hook: bool, } +impl LaunchConfig for Config {} + pub(crate) enum ConfigRoot { RootName(String), RootNode(web_sys::Node), diff --git a/packages/web/src/launch.rs b/packages/web/src/launch.rs index 4cc421e9d..71c972ded 100644 --- a/packages/web/src/launch.rs +++ b/packages/web/src/launch.rs @@ -9,13 +9,17 @@ use std::any::Any; /// For a builder API, see `LaunchBuilder` defined in the `dioxus` crate. pub fn launch( root: fn() -> Element, - contexts: Vec Box>>, - platform_config: Config, + contexts: Vec Box + Send + Sync>>, + platform_config: Vec>, ) { let mut vdom = VirtualDom::new(root); for context in contexts { vdom.insert_any_root_context(context()); } + let platform_config = *platform_config + .into_iter() + .find_map(|cfg| cfg.downcast::().ok()) + .unwrap_or_default(); launch_virtual_dom(vdom, platform_config) } @@ -30,5 +34,5 @@ pub fn launch_virtual_dom(vdom: VirtualDom, platform_config: Config) { /// Launch the web application with the given root component and config pub fn launch_cfg(root: fn() -> Element, platform_config: Config) { - launch(root, Vec::new(), platform_config); + launch(root, Vec::new(), vec![Box::new(platform_config)]) }