Fix base path on web and fullstack (#3247)
* 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
This commit is contained in:
1 parent
1e8693a0e8
commit
3d62fbf021
20 files changed
+254
-41
No files matched your search
Generated
+5
@@ -3368,6 +3368,7 @@ version = "0.6.0-alpha.5"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"criterion",
|
"criterion",
|
||||||
"dioxus",
|
"dioxus",
|
||||||
|
"dioxus-cli-config",
|
||||||
"dioxus-config-macro",
|
"dioxus-config-macro",
|
||||||
"dioxus-core",
|
"dioxus-core",
|
||||||
"dioxus-core-macro",
|
"dioxus-core-macro",
|
||||||
@@ -3570,6 +3571,9 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-cli-config"
|
name = "dioxus-cli-config"
|
||||||
version = "0.6.0-alpha.5"
|
version = "0.6.0-alpha.5"
|
||||||
|
dependencies = [
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dioxus-config-macro"
|
name = "dioxus-config-macro"
|
||||||
@@ -7715,6 +7719,7 @@ version = "0.6.0-alpha.5"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"const-serialize",
|
"const-serialize",
|
||||||
"dioxus",
|
"dioxus",
|
||||||
|
"dioxus-cli-config",
|
||||||
"dioxus-core-types",
|
"dioxus-core-types",
|
||||||
"manganis",
|
"manganis",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
@@ -10,3 +10,7 @@ description = "CLI Configuration for dioxus-cli"
|
|||||||
keywords = ["dom", "ui", "gui", "react", ]
|
keywords = ["dom", "ui", "gui", "react", ]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
wasm-bindgen = { workspace = true, optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
web = ["dep:wasm-bindgen"]
|
||||||
@@ -12,6 +12,29 @@ pub const ASSET_ROOT_ENV: &str = "DIOXUS_ASSET_ROOT";
|
|||||||
pub const APP_TITLE_ENV: &str = "DIOXUS_APP_TITLE";
|
pub const APP_TITLE_ENV: &str = "DIOXUS_APP_TITLE";
|
||||||
pub const OUT_DIR: &str = "DIOXUS_OUT_DIR";
|
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
|
/// Get the address of the devserver for use over a raw socket
|
||||||
///
|
///
|
||||||
/// This is not a websocket! There's no protocol!
|
/// This is not a websocket! There's no protocol!
|
||||||
@@ -51,7 +74,7 @@ pub fn fullstack_address_or_localhost() -> SocketAddr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn app_title() -> Option<String> {
|
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> {
|
pub fn always_on_top() -> Option<bool> {
|
||||||
@@ -64,8 +87,55 @@ pub fn is_cli_enabled() -> bool {
|
|||||||
std::env::var(CLI_ENABLED_ENV).is_ok()
|
std::env::var(CLI_ENABLED_ENV).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn base_path() -> Option<PathBuf> {
|
#[cfg(feature = "web")]
|
||||||
std::env::var("DIOXUS_ASSET_ROOT").ok().map(PathBuf::from)
|
#[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> {
|
pub fn out_dir() -> Option<PathBuf> {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use crate::{assets::AssetManifest, TraceSrc};
|
|||||||
use crate::{link::LinkAction, BuildArgs};
|
use crate::{link::LinkAction, BuildArgs};
|
||||||
use crate::{AppBundle, Platform};
|
use crate::{AppBundle, Platform};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use dioxus_cli_config::{APP_TITLE_ENV, ASSET_ROOT_ENV};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{
|
use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
@@ -540,6 +541,15 @@ impl BuildRequest {
|
|||||||
// env_vars.push(("PATH", extended_path));
|
// 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)
|
Ok(env_vars)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use dioxus_cli_config::format_base_path_meta_element;
|
||||||
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::BuildRequest;
|
use crate::BuildRequest;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
@@ -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() {
|
if !style_list.is_empty() {
|
||||||
self.send_resource_deprecation_warning(style_list, ResourceType::Style);
|
self.send_resource_deprecation_warning(style_list, ResourceType::Style);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,9 +101,15 @@ pub(crate) struct WebAppConfig {
|
|||||||
impl 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 `.`.
|
/// 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 {
|
pub(crate) fn base_path(&self) -> &str {
|
||||||
match &self.base_path {
|
let trimmed_path = self
|
||||||
Some(path) => path.trim_matches('/'),
|
.base_path
|
||||||
None => ".",
|
.as_deref()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.trim_matches('/');
|
||||||
|
if trimmed_path.is_empty() {
|
||||||
|
"."
|
||||||
|
} else {
|
||||||
|
trimmed_path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,11 @@ impl AppHandle {
|
|||||||
// Set the env vars that the clients will expect
|
// Set the env vars that the clients will expect
|
||||||
// These need to be stable within a release version (ie 0.6.0)
|
// These need to be stable within a release version (ie 0.6.0)
|
||||||
let mut envs = vec![
|
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()),
|
("RUST_BACKTRACE", "1".to_string()),
|
||||||
(
|
(
|
||||||
dioxus_cli_config::DEVSERVER_RAW_ADDR_ENV,
|
dioxus_cli_config::DEVSERVER_RAW_ADDR_ENV,
|
||||||
@@ -80,6 +84,10 @@ impl AppHandle {
|
|||||||
("CARGO_MANIFEST_DIR", "".to_string()),
|
("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 {
|
if let Some(addr) = fullstack_address {
|
||||||
envs.push((dioxus_cli_config::SERVER_IP_ENV, addr.ip().to_string()));
|
envs.push((dioxus_cli_config::SERVER_IP_ENV, addr.ip().to_string()));
|
||||||
envs.push((dioxus_cli_config::SERVER_PORT_ENV, addr.port().to_string()));
|
envs.push((dioxus_cli_config::SERVER_PORT_ENV, addr.port().to_string()));
|
||||||
@@ -109,7 +117,7 @@ impl AppHandle {
|
|||||||
Platform::Web => {
|
Platform::Web => {
|
||||||
// Only the first build we open the web app, after that the user knows it's running
|
// Only the first build we open the web app, after that the user knows it's running
|
||||||
if open_browser {
|
if open_browser {
|
||||||
self.open_web(envs, devserver_ip);
|
self.open_web(devserver_ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
@@ -212,7 +220,7 @@ impl AppHandle {
|
|||||||
/// Open the web app by opening the browser to the given address.
|
/// 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.
|
/// Check if we need to use https or not, and if so, add the protocol.
|
||||||
/// Go to the basepath if that's set too.
|
/// 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 base_path = self.app.build.krate.config.web.app.base_path.clone();
|
||||||
let https = self
|
let https = self
|
||||||
.app
|
.app
|
||||||
|
|||||||
@@ -231,7 +231,9 @@ pub(crate) async fn serve_all(mut args: ServeArgs) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ServeUpdate::OpenApp => {
|
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 => {
|
ServeUpdate::Redraw => {
|
||||||
|
|||||||
@@ -146,12 +146,15 @@ impl AppRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Open an existing app bundle, if it exists
|
/// Open an existing app bundle, if it exists
|
||||||
pub(crate) async fn open_existing(&self, devserver: &WebServer) {
|
pub(crate) async fn open_existing(&mut self, devserver: &WebServer) -> Result<()> {
|
||||||
if let Some(address) = devserver.server_address() {
|
if let Some((_, app)) = self
|
||||||
let url = format!("http://{address}");
|
.running
|
||||||
tracing::debug!("opening url: {url}");
|
.iter_mut()
|
||||||
_ = open::that(url);
|
.find(|(platform, _)| **platform != Platform::Server)
|
||||||
|
{
|
||||||
|
app.open(devserver.devserver_address(), None, true).await?;
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn attempt_hot_reload(
|
pub(crate) fn attempt_hot_reload(
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ dioxus-ssr = { workspace = true, optional = true }
|
|||||||
manganis = { workspace = true, features = ["dioxus"], optional = true }
|
manganis = { workspace = true, features = ["dioxus"], optional = true }
|
||||||
|
|
||||||
serde = { version = "1.0.136", 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]
|
[target.'cfg(not(any(target_arch = "wasm32", target_os = "ios", target_os = "android")))'.dependencies]
|
||||||
dioxus-devtools = { workspace = true, optional = true }
|
dioxus-devtools = { workspace = true, optional = true }
|
||||||
@@ -53,7 +54,7 @@ router = ["dep:dioxus-router"]
|
|||||||
fullstack = ["dep:dioxus-fullstack", "dioxus-config-macro/fullstack", "dep:serde"]
|
fullstack = ["dep:dioxus-fullstack", "dioxus-config-macro/fullstack", "dep:serde"]
|
||||||
desktop = ["dep:dioxus-desktop", "dioxus-fullstack?/desktop", "dioxus-config-macro/desktop"]
|
desktop = ["dep:dioxus-desktop", "dioxus-fullstack?/desktop", "dioxus-config-macro/desktop"]
|
||||||
mobile = ["dep:dioxus-mobile", "dioxus-fullstack?/mobile", "dioxus-config-macro/mobile"]
|
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"]
|
ssr = ["dep:dioxus-ssr", "dioxus-config-macro/ssr"]
|
||||||
liveview = ["dep:dioxus-liveview", "dioxus-config-macro/liveview"]
|
liveview = ["dep:dioxus-liveview", "dioxus-config-macro/liveview"]
|
||||||
server = ["dioxus-fullstack?/axum", "dioxus-fullstack?/server", "ssr", "dioxus-liveview?/axum"]
|
server = ["dioxus-fullstack?/axum", "dioxus-fullstack?/server", "ssr", "dioxus-liveview?/axum"]
|
||||||
|
|||||||
@@ -274,6 +274,19 @@ fn web_launch(
|
|||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.hydrate(true);
|
.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 factory = move || {
|
||||||
let mut vdom = dioxus_core::VirtualDom::new(root);
|
let mut vdom = dioxus_core::VirtualDom::new(root);
|
||||||
for context in contexts {
|
for context in contexts {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
//! A shared pool of renderers for efficient server side rendering.
|
//! A shared pool of renderers for efficient server side rendering.
|
||||||
use crate::document::ServerDocument;
|
use crate::document::ServerDocument;
|
||||||
use crate::streaming::{Mount, StreamingRenderer};
|
use crate::streaming::{Mount, StreamingRenderer};
|
||||||
|
use dioxus_cli_config::base_path;
|
||||||
use dioxus_interpreter_js::INITIALIZE_STREAMING_JS;
|
use dioxus_interpreter_js::INITIALIZE_STREAMING_JS;
|
||||||
use dioxus_isrg::{CachedRender, RenderFreshness};
|
use dioxus_isrg::{CachedRender, RenderFreshness};
|
||||||
use dioxus_lib::document::Document;
|
use dioxus_lib::document::Document;
|
||||||
@@ -169,9 +170,19 @@ impl SsrRendererPool {
|
|||||||
let mut virtual_dom = virtual_dom_factory();
|
let mut virtual_dom = virtual_dom_factory();
|
||||||
let document = std::rc::Rc::new(crate::document::server::ServerDocument::default());
|
let document = std::rc::Rc::new(crate::document::server::ServerDocument::default());
|
||||||
virtual_dom.provide_root_context(document.clone());
|
virtual_dom.provide_root_context(document.clone());
|
||||||
virtual_dom.provide_root_context(Rc::new(
|
// If there is a base path, trim the base path from the route and add the base path formatting to the
|
||||||
dioxus_history::MemoryHistory::with_initial_path(&route),
|
// history provider
|
||||||
) as Rc<dyn dioxus_history::History>);
|
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>);
|
virtual_dom.provide_root_context(document.clone() as std::rc::Rc<dyn Document>);
|
||||||
|
|
||||||
// poll the future, which may call server_context()
|
// poll the future, which may call server_context()
|
||||||
|
|||||||
@@ -2,8 +2,16 @@
|
|||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
body::Body,
|
||||||
|
extract::{Request, State},
|
||||||
|
response::IntoResponse,
|
||||||
|
};
|
||||||
|
use dioxus_cli_config::base_path;
|
||||||
use dioxus_lib::prelude::*;
|
use dioxus_lib::prelude::*;
|
||||||
|
|
||||||
|
use crate::server::{render_handler, RenderHandleState, SSRState};
|
||||||
|
|
||||||
/// Launch a fullstack app with the given root component, contexts, and config.
|
/// Launch a fullstack app with the given root component, contexts, and config.
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub fn launch(
|
pub fn launch(
|
||||||
@@ -60,9 +68,37 @@ pub fn launch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_mut)]
|
let mut base_path = base_path();
|
||||||
let mut router =
|
let config = platform_config.as_ref().ok().cloned();
|
||||||
|
let dioxus_router =
|
||||||
axum::Router::new().serve_dioxus_application(TryIntoResult(platform_config), root);
|
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 router = router.into_make_service();
|
||||||
let listener = tokio::net::TcpListener::bind(address).await.unwrap();
|
let listener = tokio::net::TcpListener::bind(address).await.unwrap();
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ struct MemoryHistoryState {
|
|||||||
/// A [`History`] provider that stores all navigation information in memory.
|
/// A [`History`] provider that stores all navigation information in memory.
|
||||||
pub struct MemoryHistory {
|
pub struct MemoryHistory {
|
||||||
state: RefCell<MemoryHistoryState>,
|
state: RefCell<MemoryHistoryState>,
|
||||||
|
base_path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MemoryHistory {
|
impl Default for MemoryHistory {
|
||||||
@@ -44,16 +45,36 @@ impl MemoryHistory {
|
|||||||
pub fn with_initial_path(path: impl ToString) -> Self {
|
pub fn with_initial_path(path: impl ToString) -> Self {
|
||||||
Self {
|
Self {
|
||||||
state: MemoryHistoryState{
|
state: MemoryHistoryState{
|
||||||
current: path.to_string().parse().unwrap_or_else(|err| {
|
current: path.to_string().parse().unwrap_or_else(|err| {
|
||||||
panic!("index route does not exist:\n{err}\n use MemoryHistory::with_initial_path to set a custom path")
|
panic!("index route does not exist:\n{err}\n use MemoryHistory::with_initial_path to set a custom path")
|
||||||
}),
|
}),
|
||||||
history: Vec::new(),
|
history: Vec::new(),
|
||||||
future: Vec::new(),}.into()
|
future: Vec::new(),
|
||||||
|
}.into(),
|
||||||
|
base_path: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the base path for the history. All routes will be prefixed with this path when rendered.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use dioxus_history::*;
|
||||||
|
/// let mut history = MemoryHistory::default().with_prefix("/my-app");
|
||||||
|
///
|
||||||
|
/// // The base path is set to "/my-app"
|
||||||
|
/// assert_eq!(history.current_prefix(), Some("/my-app".to_string()));
|
||||||
|
/// ```
|
||||||
|
pub fn with_prefix(mut self, prefix: impl ToString) -> Self {
|
||||||
|
self.base_path = Some(prefix.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl History for MemoryHistory {
|
impl History for MemoryHistory {
|
||||||
|
fn current_prefix(&self) -> Option<String> {
|
||||||
|
self.base_path.clone()
|
||||||
|
}
|
||||||
|
|
||||||
fn current_route(&self) -> String {
|
fn current_route(&self) -> String {
|
||||||
self.state.borrow().current.clone()
|
self.state.borrow().current.clone()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ keywords = ["dom", "ui", "gui", "assets"]
|
|||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
const-serialize = { workspace = true, features = ["serde"] }
|
const-serialize = { workspace = true, features = ["serde"] }
|
||||||
dioxus-core-types = { workspace = true, optional = true }
|
dioxus-core-types = { workspace = true, optional = true }
|
||||||
|
dioxus-cli-config = { workspace = true, optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
manganis = { workspace = true }
|
manganis = { workspace = true }
|
||||||
dioxus = { workspace = true }
|
dioxus = { workspace = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
dioxus = ["dep:dioxus-core-types"]
|
dioxus = ["dep:dioxus-core-types", "dep:dioxus-cli-config"]
|
||||||
@@ -124,8 +124,23 @@ impl Asset {
|
|||||||
return PathBuf::from(self.bundled.absolute_source_path.as_str());
|
return PathBuf::from(self.bundled.absolute_source_path.as_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "dioxus")]
|
||||||
|
let bundle_root = {
|
||||||
|
let base_path = dioxus_cli_config::base_path();
|
||||||
|
let base_path = base_path
|
||||||
|
.as_deref()
|
||||||
|
.map(|base_path| {
|
||||||
|
let trimmed = base_path.trim_matches('/');
|
||||||
|
format!("/{trimmed}")
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
PathBuf::from(format!("{base_path}/assets/"))
|
||||||
|
};
|
||||||
|
#[cfg(not(feature = "dioxus"))]
|
||||||
|
let bundle_root = PathBuf::from("/assets/");
|
||||||
|
|
||||||
// Otherwise presumably we're bundled and we can use the bundled path
|
// Otherwise presumably we're bundled and we can use the bundled path
|
||||||
PathBuf::from("/assets/").join(PathBuf::from(
|
bundle_root.join(PathBuf::from(
|
||||||
self.bundled.bundled_path.as_str().trim_start_matches('/'),
|
self.bundled.bundled_path.as_str().trim_start_matches('/'),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -163,6 +163,8 @@ pub fn Link(props: LinkProps) -> Element {
|
|||||||
NavigationTarget::Internal(url) => url.clone(),
|
NavigationTarget::Internal(url) => url.clone(),
|
||||||
NavigationTarget::External(route) => route.clone(),
|
NavigationTarget::External(route) => route.clone(),
|
||||||
};
|
};
|
||||||
|
// Add the history's prefix to the href for use in the rsx
|
||||||
|
let full_href = router.prefix().unwrap_or_default() + &href;
|
||||||
|
|
||||||
let mut class_ = String::new();
|
let mut class_ = String::new();
|
||||||
if let Some(c) = class {
|
if let Some(c) = class {
|
||||||
@@ -240,7 +242,7 @@ pub fn Link(props: LinkProps) -> Element {
|
|||||||
a {
|
a {
|
||||||
onclick: action,
|
onclick: action,
|
||||||
"onclick": liveview_prevent_default,
|
"onclick": liveview_prevent_default,
|
||||||
href,
|
href: full_href,
|
||||||
onmounted: onmounted,
|
onmounted: onmounted,
|
||||||
class,
|
class,
|
||||||
rel,
|
rel,
|
||||||
|
|||||||
@@ -47,9 +47,6 @@ pub(crate) type RoutingCallback<R> =
|
|||||||
pub(crate) type AnyRoutingCallback = Arc<dyn Fn(RouterContext) -> Option<NavigationTarget>>;
|
pub(crate) type AnyRoutingCallback = Arc<dyn Fn(RouterContext) -> Option<NavigationTarget>>;
|
||||||
|
|
||||||
struct RouterContextInner {
|
struct RouterContextInner {
|
||||||
/// The current prefix.
|
|
||||||
prefix: Option<String>,
|
|
||||||
|
|
||||||
unresolved_error: Option<ExternalNavigationFailure>,
|
unresolved_error: Option<ExternalNavigationFailure>,
|
||||||
|
|
||||||
subscribers: Arc<Mutex<HashSet<ReactiveContext>>>,
|
subscribers: Arc<Mutex<HashSet<ReactiveContext>>>,
|
||||||
@@ -105,7 +102,6 @@ impl RouterContext {
|
|||||||
let mapping = consume_child_route_mapping();
|
let mapping = consume_child_route_mapping();
|
||||||
|
|
||||||
let myself = RouterContextInner {
|
let myself = RouterContextInner {
|
||||||
prefix: Default::default(),
|
|
||||||
unresolved_error: None,
|
unresolved_error: None,
|
||||||
subscribers: subscribers.clone(),
|
subscribers: subscribers.clone(),
|
||||||
routing_callback: cfg.on_update.map(|update| {
|
routing_callback: cfg.on_update.map(|update| {
|
||||||
@@ -259,7 +255,8 @@ impl RouterContext {
|
|||||||
|
|
||||||
/// The prefix that is currently active.
|
/// The prefix that is currently active.
|
||||||
pub fn prefix(&self) -> Option<String> {
|
pub fn prefix(&self) -> Option<String> {
|
||||||
self.inner.read().prefix.clone()
|
let history = history();
|
||||||
|
history.current_prefix()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear any unresolved errors
|
/// Clear any unresolved errors
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
dioxus-core = { workspace = true }
|
dioxus-core = { workspace = true }
|
||||||
dioxus-core-types = { workspace = true }
|
dioxus-core-types = { workspace = true }
|
||||||
dioxus-cli-config = { workspace = true }
|
dioxus-cli-config = { workspace = true, features = ["web"] }
|
||||||
dioxus-html = { workspace = true }
|
dioxus-html = { workspace = true }
|
||||||
dioxus-history = { workspace = true }
|
dioxus-history = { workspace = true }
|
||||||
dioxus-document = { workspace = true }
|
dioxus-document = { workspace = true }
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use scroll::ScrollPosition;
|
use scroll::ScrollPosition;
|
||||||
use std::path::PathBuf;
|
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use wasm_bindgen::{prelude::Closure, JsValue};
|
use wasm_bindgen::{prelude::Closure, JsValue};
|
||||||
use web_sys::{window, Window};
|
use web_sys::{window, Window};
|
||||||
@@ -7,9 +6,8 @@ use web_sys::{Event, History, ScrollRestoration};
|
|||||||
|
|
||||||
mod scroll;
|
mod scroll;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
fn base_path() -> Option<String> {
|
||||||
fn base_path() -> Option<PathBuf> {
|
let base_path = dioxus_cli_config::web_base_path();
|
||||||
let base_path = dioxus_cli_config::base_path();
|
|
||||||
tracing::trace!("Using base_path from the CLI: {:?}", base_path);
|
tracing::trace!("Using base_path from the CLI: {:?}", base_path);
|
||||||
base_path
|
base_path
|
||||||
}
|
}
|
||||||
@@ -77,9 +75,10 @@ impl WebHistory {
|
|||||||
|
|
||||||
let prefix = prefix
|
let prefix = prefix
|
||||||
// If there isn't a base path, try to grab one from the CLI
|
// If there isn't a base path, try to grab one from the CLI
|
||||||
.or_else(|| base_path().map(|s| s.display().to_string()))
|
.or_else(base_path)
|
||||||
// Normalize the prefix to start and end with no slashes
|
// Normalize the prefix to start and end with no slashes
|
||||||
.map(|prefix| prefix.trim_matches('/').to_string())
|
.as_ref()
|
||||||
|
.map(|prefix| prefix.trim_matches('/'))
|
||||||
// If the prefix is empty, don't add it
|
// If the prefix is empty, don't add it
|
||||||
.filter(|prefix| !prefix.is_empty())
|
.filter(|prefix| !prefix.is_empty())
|
||||||
// Otherwise, start with a slash
|
// Otherwise, start with a slash
|
||||||
|
|||||||
Reference in new issue
Block a user