Skip to content

Commit

Permalink
Fix base path on web and fullstack (#3247)
Browse files Browse the repository at this point in the history
* Create a crate for constant serialization of config structs for manganis

* use SerializeConst for the image asset builder

* switch to a declarative macro for assets

* clean up asset macro a bit

* add serializable options for each asset type

* serialize const vec

* Add unique path formatting

* implement the new manganis macro

* optimize assets in the CLI

* Fix clippy

* Fix assets with dioxus formattable

* reduce fuzzing test limits

* use the new syntax in examples

* fix formatting

* Final doc and test pass on const-serialize

* fix avif support

* Fix manganis doc tests

* cache asset optimization

* Split out asset and bundled asset

* make hash pointer width independent

* remove const serialize cargo lock

* Fix manganis macro docs

* all tests passing

* add constvec::is_empty method to fix clippy lint

* remove nasm feature

* simplify test_rsplit_once test so typos passes

* fix range syntax for stable

* revert example change from clippy fix

* remove dioxus-static-site-generation workspace dependency

* always accept unix paths

* fix windows path seperator

* fix folder asset hash

* Optimize assets in a blocking task

* Fix asset options docs

* Document Asset and BundledAsset

* move manganis back into it's own folder

* simplify the linker macro a bit

* add more docs to AssetParser expansion

* fix manganis core doc test

* add image format helpers

* Fill in new cargo.tomls

* fix folders with explicit options

* Split by  both unix and windows path separators and take the smallest one

* fix string length

* Fix base path on web and fullstack

* Fix assets with base path

* fix fullstack base route with trailing slash

* fix clippy
  • Loading branch information
ealmloff authored Nov 27, 2024
1 parent 1e8693a commit 3d62fbf
Show file tree
Hide file tree
Showing 20 changed files with 254 additions and 41 deletions.
5 changes: 5 additions & 0 deletions Cargo.lock

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

4 changes: 4 additions & 0 deletions packages/cli-config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ description = "CLI Configuration for dioxus-cli"
keywords = ["dom", "ui", "gui", "react", ]

[dependencies]
wasm-bindgen = { workspace = true, optional = true }

[features]
web = ["dep:wasm-bindgen"]
76 changes: 73 additions & 3 deletions packages/cli-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,29 @@ pub const ASSET_ROOT_ENV: &str = "DIOXUS_ASSET_ROOT";
pub const APP_TITLE_ENV: &str = "DIOXUS_APP_TITLE";
pub const OUT_DIR: &str = "DIOXUS_OUT_DIR";

/// Reads an environment variable at runtime in debug mode or at compile time in
/// release mode. When bundling in release mode, we will not be running under the
/// environment variables that the CLI sets, so we need to read them at compile time.
macro_rules! read_env_config {
($name:expr) => {{
#[cfg(debug_assertions)]
{
// In debug mode, read the environment variable set by the CLI at runtime
std::env::var($name).ok()
}

#[cfg(not(debug_assertions))]
{
// In release mode, read the environment variable set by the CLI at compile time
// This means the value will still be available when running the application
// standalone.
// We don't always read the environment variable at compile time to avoid rebuilding
// this crate when the environment variable changes.
option_env!($name).map(ToString::to_string)
}
}};
}

/// Get the address of the devserver for use over a raw socket
///
/// This is not a websocket! There's no protocol!
Expand Down Expand Up @@ -51,7 +74,7 @@ pub fn fullstack_address_or_localhost() -> SocketAddr {
}

pub fn app_title() -> Option<String> {
std::env::var(APP_TITLE_ENV).ok()
read_env_config!("DIOXUS_APP_TITLE")
}

pub fn always_on_top() -> Option<bool> {
Expand All @@ -64,8 +87,55 @@ pub fn is_cli_enabled() -> bool {
std::env::var(CLI_ENABLED_ENV).is_ok()
}

pub fn base_path() -> Option<PathBuf> {
std::env::var("DIOXUS_ASSET_ROOT").ok().map(PathBuf::from)
#[cfg(feature = "web")]
#[wasm_bindgen::prelude::wasm_bindgen(inline_js = r#"
export function getMetaContents(meta_name) {
const selector = document.querySelector(`meta[name="${meta_name}"]`);
if (!selector) {
return null;
}
return selector.content;
}
"#)]
extern "C" {
#[wasm_bindgen(js_name = getMetaContents)]
pub fn get_meta_contents(selector: &str) -> Option<String>;
}

/// Get the path where the application will be served from. This is used by the router to format the URLs.
pub fn base_path() -> Option<String> {
// This may trigger when compiling to the server if you depend on another crate that pulls in
// the web feature. It might be better for the renderers to provide the current platform
// as a global context
#[cfg(all(feature = "web", target_arch = "wasm32"))]
{
return web_base_path();
}

read_env_config!("DIOXUS_ASSET_ROOT")
}

/// Get the path where the application is served from in the browser.
#[cfg(feature = "web")]
pub fn web_base_path() -> Option<String> {
// In debug mode, we get the base path from the meta element which can be hot reloaded and changed without recompiling
#[cfg(debug_assertions)]
{
thread_local! {
static BASE_PATH: std::cell::OnceCell<Option<String>> = const { std::cell::OnceCell::new() };
}
BASE_PATH.with(|f| f.get_or_init(|| get_meta_contents(ASSET_ROOT_ENV)).clone())
}

// In release mode, we get the base path from the environment variable
#[cfg(not(debug_assertions))]
{
option_env!("DIOXUS_ASSET_ROOT").map(ToString::to_string)
}
}

pub fn format_base_path_meta_element(base_path: &str) -> String {
format!(r#"<meta name="{ASSET_ROOT_ENV}" content="{base_path}">"#,)
}

pub fn out_dir() -> Option<PathBuf> {
Expand Down
10 changes: 10 additions & 0 deletions packages/cli/src/build/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{assets::AssetManifest, TraceSrc};
use crate::{link::LinkAction, BuildArgs};
use crate::{AppBundle, Platform};
use anyhow::Context;
use dioxus_cli_config::{APP_TITLE_ENV, ASSET_ROOT_ENV};
use serde::Deserialize;
use std::{
path::{Path, PathBuf},
Expand Down Expand Up @@ -540,6 +541,15 @@ impl BuildRequest {
// env_vars.push(("PATH", extended_path));
};

// If this is a release build, bake the base path and title
// into the binary with env vars
if self.build.release {
if let Some(base_path) = &self.krate.config.web.app.base_path {
env_vars.push((ASSET_ROOT_ENV, base_path.clone()));
}
env_vars.push((APP_TITLE_ENV, self.krate.config.web.app.title.clone()));
}

Ok(env_vars)
}

Expand Down
9 changes: 9 additions & 0 deletions packages/cli/src/build/web.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use dioxus_cli_config::format_base_path_meta_element;

use crate::error::Result;
use crate::BuildRequest;
use std::fmt::Write;
Expand Down Expand Up @@ -66,6 +68,13 @@ impl BuildRequest {
)?;
}

// Add the base path to the head if this is a debug build
if self.is_dev_build() {
if let Some(base_path) = &self.krate.config.web.app.base_path {
head_resources.push_str(&format_base_path_meta_element(base_path));
}
}

if !style_list.is_empty() {
self.send_resource_deprecation_warning(style_list, ResourceType::Style);
}
Expand Down
12 changes: 9 additions & 3 deletions packages/cli/src/config/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,15 @@ pub(crate) struct WebAppConfig {
impl WebAppConfig {
/// Get the normalized base path for the application with `/` trimmed from both ends. If the base path is not set, this will return `.`.
pub(crate) fn base_path(&self) -> &str {
match &self.base_path {
Some(path) => path.trim_matches('/'),
None => ".",
let trimmed_path = self
.base_path
.as_deref()
.unwrap_or_default()
.trim_matches('/');
if trimmed_path.is_empty() {
"."
} else {
trimmed_path
}
}
}
Expand Down
14 changes: 11 additions & 3 deletions packages/cli/src/serve/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,11 @@ impl AppHandle {
// Set the env vars that the clients will expect
// These need to be stable within a release version (ie 0.6.0)
let mut envs = vec![
("DIOXUS_CLI_ENABLED", "true".to_string()),
(dioxus_cli_config::CLI_ENABLED_ENV, "true".to_string()),
(
dioxus_cli_config::APP_TITLE_ENV,
self.app.build.krate.config.web.app.title.clone(),
),
("RUST_BACKTRACE", "1".to_string()),
(
dioxus_cli_config::DEVSERVER_RAW_ADDR_ENV,
Expand All @@ -80,6 +84,10 @@ impl AppHandle {
("CARGO_MANIFEST_DIR", "".to_string()),
];

if let Some(base_path) = &self.app.build.krate.config.web.app.base_path {
envs.push((dioxus_cli_config::ASSET_ROOT_ENV, base_path.clone()));
}

if let Some(addr) = fullstack_address {
envs.push((dioxus_cli_config::SERVER_IP_ENV, addr.ip().to_string()));
envs.push((dioxus_cli_config::SERVER_PORT_ENV, addr.port().to_string()));
Expand Down Expand Up @@ -109,7 +117,7 @@ impl AppHandle {
Platform::Web => {
// Only the first build we open the web app, after that the user knows it's running
if open_browser {
self.open_web(envs, devserver_ip);
self.open_web(devserver_ip);
}

None
Expand Down Expand Up @@ -212,7 +220,7 @@ impl AppHandle {
/// Open the web app by opening the browser to the given address.
/// Check if we need to use https or not, and if so, add the protocol.
/// Go to the basepath if that's set too.
fn open_web(&self, _envs: Vec<(&str, String)>, address: SocketAddr) {
fn open_web(&self, address: SocketAddr) {
let base_path = self.app.build.krate.config.web.app.base_path.clone();
let https = self
.app
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/serve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,9 @@ pub(crate) async fn serve_all(mut args: ServeArgs) -> Result<()> {
}

ServeUpdate::OpenApp => {
runner.open_existing(&devserver).await;
if let Err(err) = runner.open_existing(&devserver).await {
tracing::error!("Failed to open app: {err}")
}
}

ServeUpdate::Redraw => {
Expand Down
13 changes: 8 additions & 5 deletions packages/cli/src/serve/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,15 @@ impl AppRunner {
}

/// Open an existing app bundle, if it exists
pub(crate) async fn open_existing(&self, devserver: &WebServer) {
if let Some(address) = devserver.server_address() {
let url = format!("http://{address}");
tracing::debug!("opening url: {url}");
_ = open::that(url);
pub(crate) async fn open_existing(&mut self, devserver: &WebServer) -> Result<()> {
if let Some((_, app)) = self
.running
.iter_mut()
.find(|(platform, _)| **platform != Platform::Server)
{
app.open(devserver.devserver_address(), None, true).await?;
}
Ok(())
}

pub(crate) fn attempt_hot_reload(
Expand Down
3 changes: 2 additions & 1 deletion packages/dioxus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dioxus-ssr = { workspace = true, optional = true }
manganis = { workspace = true, features = ["dioxus"], optional = true }

serde = { version = "1.0.136", optional = true }
dioxus-cli-config = { workspace = true, optional = true }

[target.'cfg(not(any(target_arch = "wasm32", target_os = "ios", target_os = "android")))'.dependencies]
dioxus-devtools = { workspace = true, optional = true }
Expand All @@ -53,7 +54,7 @@ router = ["dep:dioxus-router"]
fullstack = ["dep:dioxus-fullstack", "dioxus-config-macro/fullstack", "dep:serde"]
desktop = ["dep:dioxus-desktop", "dioxus-fullstack?/desktop", "dioxus-config-macro/desktop"]
mobile = ["dep:dioxus-mobile", "dioxus-fullstack?/mobile", "dioxus-config-macro/mobile"]
web = ["dep:dioxus-web", "dioxus-fullstack?/web", "dioxus-config-macro/web"]
web = ["dep:dioxus-web", "dioxus-fullstack?/web", "dioxus-config-macro/web", "dioxus-cli-config", "dioxus-cli-config/web"]
ssr = ["dep:dioxus-ssr", "dioxus-config-macro/ssr"]
liveview = ["dep:dioxus-liveview", "dioxus-config-macro/liveview"]
server = ["dioxus-fullstack?/axum", "dioxus-fullstack?/server", "ssr", "dioxus-liveview?/axum"]
Expand Down
13 changes: 13 additions & 0 deletions packages/dioxus/src/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,19 @@ fn web_launch(
.unwrap_or_default()
.hydrate(true);

// If there is a base path set, call server functions from that base path
if let Some(base_path) = dioxus_cli_config::web_base_path() {
let base_path = base_path.trim_matches('/');
crate::prelude::server_fn::client::set_server_url(
format!(
"{}/{}",
crate::prelude::server_fn::client::get_server_url(),
base_path
)
.leak(),
);
}

let factory = move || {
let mut vdom = dioxus_core::VirtualDom::new(root);
for context in contexts {
Expand Down
17 changes: 14 additions & 3 deletions packages/fullstack/src/render.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! A shared pool of renderers for efficient server side rendering.
use crate::document::ServerDocument;
use crate::streaming::{Mount, StreamingRenderer};
use dioxus_cli_config::base_path;
use dioxus_interpreter_js::INITIALIZE_STREAMING_JS;
use dioxus_isrg::{CachedRender, RenderFreshness};
use dioxus_lib::document::Document;
Expand Down Expand Up @@ -169,9 +170,19 @@ impl SsrRendererPool {
let mut virtual_dom = virtual_dom_factory();
let document = std::rc::Rc::new(crate::document::server::ServerDocument::default());
virtual_dom.provide_root_context(document.clone());
virtual_dom.provide_root_context(Rc::new(
dioxus_history::MemoryHistory::with_initial_path(&route),
) as Rc<dyn dioxus_history::History>);
// If there is a base path, trim the base path from the route and add the base path formatting to the
// history provider
let history;
if let Some(base_path) = base_path() {
let base_path = base_path.trim_matches('/');
let base_path = format!("/{base_path}");
let route = route.strip_prefix(&base_path).unwrap_or(&route);
history =
dioxus_history::MemoryHistory::with_initial_path(route).with_prefix(base_path);
} else {
history = dioxus_history::MemoryHistory::with_initial_path(&route);
}
virtual_dom.provide_root_context(Rc::new(history) as Rc<dyn dioxus_history::History>);
virtual_dom.provide_root_context(document.clone() as std::rc::Rc<dyn Document>);

// poll the future, which may call server_context()
Expand Down
40 changes: 38 additions & 2 deletions packages/fullstack/src/server/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@

use std::any::Any;

use axum::{
body::Body,
extract::{Request, State},
response::IntoResponse,
};
use dioxus_cli_config::base_path;
use dioxus_lib::prelude::*;

use crate::server::{render_handler, RenderHandleState, SSRState};

/// Launch a fullstack app with the given root component, contexts, and config.
#[allow(unused)]
pub fn launch(
Expand Down Expand Up @@ -60,9 +68,37 @@ pub fn launch(
}
}

#[allow(unused_mut)]
let mut router =
let mut base_path = base_path();
let config = platform_config.as_ref().ok().cloned();
let dioxus_router =
axum::Router::new().serve_dioxus_application(TryIntoResult(platform_config), root);
let mut router;
match base_path.as_deref() {
Some(base_path) => {
let base_path = base_path.trim_matches('/');
// If there is a base path, nest the router under it and serve the root route manually
// Nesting a route in axum only serves /base_path or /base_path/ not both
router = axum::Router::new().nest(&format!("/{base_path}/"), dioxus_router);
async fn root_render_handler(
state: State<RenderHandleState>,
mut request: Request<Body>,
) -> impl IntoResponse {
// The root of the base path always looks like the root from dioxus fullstack
*request.uri_mut() = "/".parse().unwrap();
render_handler(state, request).await
}
if let Some(cfg) = config {
let ssr_state = SSRState::new(&cfg);
router = router.route(
&format!("/{base_path}"),
axum::routing::method_routing::get(root_render_handler).with_state(
RenderHandleState::new(cfg, root).with_ssr_state(ssr_state),
),
)
}
}
None => router = dioxus_router,
}

let router = router.into_make_service();
let listener = tokio::net::TcpListener::bind(address).await.unwrap();
Expand Down
Loading

0 comments on commit 3d62fbf

Please sign in to comment.