Forward cli serve settings and make it easier to provide platform config to fullstack (#2521)
* assert that launch never returns for better compiler errors * fix static generation launch function * fix web renderer * pass context providers into server functions * add an example for FromContext * clean up DioxusRouterExt * fix server function context * fix fullstack desktop example * forward CLI serve settings to fullstack * re-export serve config at the root of fullstack * forward env directly instead of using a guard * just set the port in the CLI for fullstack playwright tests * fix fullstack dioxus-cli-config feature * fix launch server merge conflicts * fix fullstack launch context * run web playwright tests on 9999 to avoid port conflicts with other local servers * fix playwright tests
This commit is contained in:
1 parent
9dd4427cb5
commit
ffa36a67c3
18 files changed
+403
-244
No files matched your search
Generated
+11
-10
@@ -1138,7 +1138,7 @@ checksum = "aa7015584550945f11fdfb7af113d30e2727468ec281c1d7f28cc1019196c25d"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"auth-git2",
|
"auth-git2",
|
||||||
"clap 4.5.4",
|
"clap 4.5.7",
|
||||||
"console",
|
"console",
|
||||||
"dialoguer",
|
"dialoguer",
|
||||||
"env_logger 0.11.3",
|
"env_logger 0.11.3",
|
||||||
@@ -1361,9 +1361,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.4"
|
version = "4.5.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
|
checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@@ -1371,9 +1371,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.2"
|
version = "4.5.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
|
checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@@ -1383,9 +1383,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.4"
|
version = "4.5.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
|
checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -1730,7 +1730,7 @@ dependencies = [
|
|||||||
"anes",
|
"anes",
|
||||||
"cast",
|
"cast",
|
||||||
"ciborium",
|
"ciborium",
|
||||||
"clap 4.5.4",
|
"clap 4.5.7",
|
||||||
"criterion-plot 0.5.0",
|
"criterion-plot 0.5.0",
|
||||||
"futures",
|
"futures",
|
||||||
"is-terminal",
|
"is-terminal",
|
||||||
@@ -2190,7 +2190,7 @@ dependencies = [
|
|||||||
"cargo_metadata 0.18.1",
|
"cargo_metadata 0.18.1",
|
||||||
"cargo_toml 0.18.0",
|
"cargo_toml 0.18.0",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap 4.5.4",
|
"clap 4.5.7",
|
||||||
"colored 2.1.0",
|
"colored 2.1.0",
|
||||||
"ctrlc",
|
"ctrlc",
|
||||||
"dioxus-autofmt",
|
"dioxus-autofmt",
|
||||||
@@ -2252,7 +2252,7 @@ name = "dioxus-cli-config"
|
|||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cargo_toml 0.18.0",
|
"cargo_toml 0.18.0",
|
||||||
"clap 4.5.4",
|
"clap 4.5.7",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -2417,6 +2417,7 @@ dependencies = [
|
|||||||
"base64 0.21.7",
|
"base64 0.21.7",
|
||||||
"bytes",
|
"bytes",
|
||||||
"ciborium",
|
"ciborium",
|
||||||
|
"clap 4.5.7",
|
||||||
"dioxus",
|
"dioxus",
|
||||||
"dioxus-cli-config",
|
"dioxus-cli-config",
|
||||||
"dioxus-desktop",
|
"dioxus-desktop",
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ mod bundle;
|
|||||||
pub use bundle::*;
|
pub use bundle::*;
|
||||||
mod cargo;
|
mod cargo;
|
||||||
pub use cargo::*;
|
pub use cargo::*;
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
|
mod serve;
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
|
pub use serve::*;
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod __private {
|
pub mod __private {
|
||||||
@@ -28,6 +32,11 @@ pub mod __private {
|
|||||||
std::env::remove_var(CONFIG_ENV);
|
std::env::remove_var(CONFIG_ENV);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
|
/// The environment variable that stores the CLIs serve configuration.
|
||||||
|
/// We use this to communicate between the CLI and the server for fullstack applications.
|
||||||
|
pub const SERVE_ENV: &str = "DIOXUS_SERVE_CONFIG";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error that occurs when the dioxus CLI was not used to build the application.
|
/// An error that occurs when the dioxus CLI was not used to build the application.
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
/// Arguments for the serve command
|
||||||
|
#[derive(Clone, Debug, Parser, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct ServeArguments {
|
||||||
|
/// The port the server will run on
|
||||||
|
#[clap(long)]
|
||||||
|
#[clap(default_value_t = default_port())]
|
||||||
|
pub port: u16,
|
||||||
|
/// The address the server will run on
|
||||||
|
#[clap(long)]
|
||||||
|
pub addr: Option<std::net::IpAddr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ServeArguments {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
port: default_port(),
|
||||||
|
addr: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServeArguments {
|
||||||
|
/// Attempt to read the current serve settings from the CLI. This will only be set for the fullstack platform on recent versions of the CLI.
|
||||||
|
pub fn from_cli() -> Option<Self> {
|
||||||
|
std::env::var(crate::__private::SERVE_ENV)
|
||||||
|
.ok()
|
||||||
|
.and_then(|json| serde_json::from_str(&json).ok())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_port() -> u16 {
|
||||||
|
8080
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
use dioxus_cli_config::Platform;
|
use dioxus_cli_config::Platform;
|
||||||
|
use dioxus_cli_config::ServeArguments;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@@ -80,10 +81,9 @@ impl From<ConfigOptsServe> for ConfigOptsBuild {
|
|||||||
#[derive(Clone, Debug, Default, Deserialize, Parser)]
|
#[derive(Clone, Debug, Default, Deserialize, Parser)]
|
||||||
#[command(group = clap::ArgGroup::new("release-incompatible").multiple(true).conflicts_with("release"))]
|
#[command(group = clap::ArgGroup::new("release-incompatible").multiple(true).conflicts_with("release"))]
|
||||||
pub struct ConfigOptsServe {
|
pub struct ConfigOptsServe {
|
||||||
/// Port of dev server
|
/// Arguments for the serve command
|
||||||
#[clap(long)]
|
#[clap(flatten)]
|
||||||
#[clap(default_value_t = 8080)]
|
pub(crate) server_arguments: ServeArguments,
|
||||||
pub port: u16,
|
|
||||||
|
|
||||||
/// Open the app in the default browser [default: true]
|
/// Open the app in the default browser [default: true]
|
||||||
#[clap(long, default_value_t = false)]
|
#[clap(long, default_value_t = false)]
|
||||||
@@ -156,6 +156,10 @@ pub struct ConfigOptsServe {
|
|||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub target: Option<String>,
|
pub target: Option<String>,
|
||||||
|
|
||||||
|
/// Additional arguments to pass to the executable
|
||||||
|
#[clap(long)]
|
||||||
|
pub args: Vec<String>,
|
||||||
|
|
||||||
/// Extra arguments passed to cargo build
|
/// Extra arguments passed to cargo build
|
||||||
#[clap(last = true)]
|
#[clap(last = true)]
|
||||||
pub cargo_args: Vec<String>,
|
pub cargo_args: Vec<String>,
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ async fn serve<P: Platform + Send + 'static>(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let platform = RwLock::new(P::start(&config, serve)?);
|
let platform = RwLock::new(P::start(&config, serve, Vec::new())?);
|
||||||
|
|
||||||
tracing::info!("🚀 Starting development server...");
|
tracing::info!("🚀 Starting development server...");
|
||||||
|
|
||||||
@@ -100,7 +100,13 @@ async fn serve<P: Platform + Send + 'static>(
|
|||||||
let _watcher = setup_file_watcher(
|
let _watcher = setup_file_watcher(
|
||||||
{
|
{
|
||||||
let config = config.clone();
|
let config = config.clone();
|
||||||
move || platform.write().unwrap().rebuild(&config)
|
let serve = serve.clone();
|
||||||
|
move || {
|
||||||
|
platform
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.rebuild(&config, &serve, Vec::new())
|
||||||
|
}
|
||||||
},
|
},
|
||||||
&config,
|
&config,
|
||||||
None,
|
None,
|
||||||
@@ -238,6 +244,8 @@ fn start_desktop(
|
|||||||
config: &CrateConfig,
|
config: &CrateConfig,
|
||||||
skip_assets: bool,
|
skip_assets: bool,
|
||||||
rust_flags: Option<String>,
|
rust_flags: Option<String>,
|
||||||
|
args: &Vec<String>,
|
||||||
|
env: Vec<(String, String)>,
|
||||||
) -> Result<(RAIIChild, BuildResult)> {
|
) -> Result<(RAIIChild, BuildResult)> {
|
||||||
// Run the desktop application
|
// Run the desktop application
|
||||||
// Only used for the fullstack platform,
|
// Only used for the fullstack platform,
|
||||||
@@ -251,7 +259,9 @@ fn start_desktop(
|
|||||||
.clone()
|
.clone()
|
||||||
.ok_or(anyhow::anyhow!("No executable found after desktop build"))?,
|
.ok_or(anyhow::anyhow!("No executable found after desktop build"))?,
|
||||||
)
|
)
|
||||||
|
.args(args)
|
||||||
.env(active, "true")
|
.env(active, "true")
|
||||||
|
.envs(env)
|
||||||
.spawn()?,
|
.spawn()?,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -259,6 +269,7 @@ fn start_desktop(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct DesktopPlatform {
|
pub(crate) struct DesktopPlatform {
|
||||||
|
args: Vec<String>,
|
||||||
currently_running_child: RAIIChild,
|
currently_running_child: RAIIChild,
|
||||||
skip_assets: bool,
|
skip_assets: bool,
|
||||||
}
|
}
|
||||||
@@ -270,8 +281,10 @@ impl DesktopPlatform {
|
|||||||
config: &CrateConfig,
|
config: &CrateConfig,
|
||||||
serve: &ConfigOptsServe,
|
serve: &ConfigOptsServe,
|
||||||
rust_flags: Option<String>,
|
rust_flags: Option<String>,
|
||||||
|
env: Vec<(String, String)>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let (child, first_build_result) = start_desktop(config, serve.skip_assets, rust_flags)?;
|
let (child, first_build_result) =
|
||||||
|
start_desktop(config, serve.skip_assets, rust_flags, &serve.args, env)?;
|
||||||
|
|
||||||
tracing::info!("🚀 Starting development server...");
|
tracing::info!("🚀 Starting development server...");
|
||||||
|
|
||||||
@@ -287,6 +300,7 @@ impl DesktopPlatform {
|
|||||||
);
|
);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
args: serve.args.clone(),
|
||||||
currently_running_child: child,
|
currently_running_child: child,
|
||||||
skip_assets: serve.skip_assets,
|
skip_assets: serve.skip_assets,
|
||||||
})
|
})
|
||||||
@@ -298,6 +312,7 @@ impl DesktopPlatform {
|
|||||||
&mut self,
|
&mut self,
|
||||||
config: &CrateConfig,
|
config: &CrateConfig,
|
||||||
rust_flags: Option<String>,
|
rust_flags: Option<String>,
|
||||||
|
env: Vec<(String, String)>,
|
||||||
) -> Result<BuildResult> {
|
) -> Result<BuildResult> {
|
||||||
// Gracefully shtudown the desktop app
|
// Gracefully shtudown the desktop app
|
||||||
// It might have a receiver to do some cleanup stuff
|
// It might have a receiver to do some cleanup stuff
|
||||||
@@ -322,27 +337,36 @@ impl DesktopPlatform {
|
|||||||
// Todo: add a timeout here to kill the process if it doesn't shut down within a reasonable time
|
// Todo: add a timeout here to kill the process if it doesn't shut down within a reasonable time
|
||||||
self.currently_running_child.0.wait()?;
|
self.currently_running_child.0.wait()?;
|
||||||
|
|
||||||
let (child, result) = start_desktop(config, self.skip_assets, rust_flags)?;
|
let (child, result) = start_desktop(config, self.skip_assets, rust_flags, &self.args, env)?;
|
||||||
self.currently_running_child = child;
|
self.currently_running_child = child;
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Platform for DesktopPlatform {
|
impl Platform for DesktopPlatform {
|
||||||
fn start(config: &CrateConfig, serve: &ConfigOptsServe) -> Result<Self> {
|
fn start(
|
||||||
|
config: &CrateConfig,
|
||||||
|
serve: &ConfigOptsServe,
|
||||||
|
env: Vec<(String, String)>,
|
||||||
|
) -> Result<Self> {
|
||||||
// See `start_with_options()`'s docs for the explanation why the code
|
// See `start_with_options()`'s docs for the explanation why the code
|
||||||
// was moved there.
|
// was moved there.
|
||||||
// Since desktop platform doesn't use `rust_flags`, this argument is
|
// Since desktop platform doesn't use `rust_flags`, this argument is
|
||||||
// explicitly set to `None`.
|
// explicitly set to `None`.
|
||||||
DesktopPlatform::start_with_options(config, serve, None)
|
DesktopPlatform::start_with_options(config, serve, None, env)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rebuild(&mut self, config: &CrateConfig) -> Result<BuildResult> {
|
fn rebuild(
|
||||||
|
&mut self,
|
||||||
|
config: &CrateConfig,
|
||||||
|
_: &ConfigOptsServe,
|
||||||
|
env: Vec<(String, String)>,
|
||||||
|
) -> Result<BuildResult> {
|
||||||
// See `rebuild_with_options()`'s docs for the explanation why the code
|
// See `rebuild_with_options()`'s docs for the explanation why the code
|
||||||
// was moved there.
|
// was moved there.
|
||||||
// Since desktop platform doesn't use `rust_flags`, this argument is
|
// Since desktop platform doesn't use `rust_flags`, this argument is
|
||||||
// explicitly set to `None`.
|
// explicitly set to `None`.
|
||||||
DesktopPlatform::rebuild_with_options(self, config, None)
|
DesktopPlatform::rebuild_with_options(self, config, None, env)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use dioxus_cli_config::CrateConfig;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cfg::{ConfigOptsBuild, ConfigOptsServe},
|
cfg::{ConfigOptsBuild, ConfigOptsServe},
|
||||||
Result,
|
BuildResult, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{desktop, Platform};
|
use super::{desktop, Platform};
|
||||||
@@ -61,6 +61,13 @@ fn make_desktop_config(config: &CrateConfig, serve: &ConfigOptsServe) -> CrateCo
|
|||||||
desktop_config
|
desktop_config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_serve_options_to_env(serve: &ConfigOptsServe, env: &mut Vec<(String, String)>) {
|
||||||
|
env.push((
|
||||||
|
dioxus_cli_config::__private::SERVE_ENV.to_string(),
|
||||||
|
serde_json::to_string(&serve.server_arguments).unwrap(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
struct FullstackPlatform {
|
struct FullstackPlatform {
|
||||||
serve: ConfigOptsServe,
|
serve: ConfigOptsServe,
|
||||||
desktop: desktop::DesktopPlatform,
|
desktop: desktop::DesktopPlatform,
|
||||||
@@ -68,7 +75,11 @@ struct FullstackPlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Platform for FullstackPlatform {
|
impl Platform for FullstackPlatform {
|
||||||
fn start(config: &CrateConfig, serve: &ConfigOptsServe) -> Result<Self>
|
fn start(
|
||||||
|
config: &CrateConfig,
|
||||||
|
serve: &ConfigOptsServe,
|
||||||
|
env: Vec<(String, String)>,
|
||||||
|
) -> Result<Self>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
@@ -76,15 +87,30 @@ impl Platform for FullstackPlatform {
|
|||||||
|
|
||||||
let desktop_config = make_desktop_config(config, serve);
|
let desktop_config = make_desktop_config(config, serve);
|
||||||
let server_rust_flags = server_rust_flags(&serve.clone().into());
|
let server_rust_flags = server_rust_flags(&serve.clone().into());
|
||||||
|
let mut desktop_env = env.clone();
|
||||||
|
add_serve_options_to_env(serve, &mut desktop_env);
|
||||||
let desktop = desktop::DesktopPlatform::start_with_options(
|
let desktop = desktop::DesktopPlatform::start_with_options(
|
||||||
&desktop_config,
|
&desktop_config,
|
||||||
serve,
|
serve,
|
||||||
Some(server_rust_flags.clone()),
|
Some(server_rust_flags.clone()),
|
||||||
|
desktop_env,
|
||||||
)?;
|
)?;
|
||||||
thread_handle
|
thread_handle
|
||||||
.join()
|
.join()
|
||||||
.map_err(|_| anyhow::anyhow!("Failed to join thread"))??;
|
.map_err(|_| anyhow::anyhow!("Failed to join thread"))??;
|
||||||
|
|
||||||
|
if serve.open {
|
||||||
|
crate::server::web::open_browser(
|
||||||
|
config,
|
||||||
|
serve
|
||||||
|
.server_arguments
|
||||||
|
.addr
|
||||||
|
.unwrap_or(std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0))),
|
||||||
|
serve.server_arguments.port,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
desktop,
|
desktop,
|
||||||
serve: serve.clone(),
|
serve: serve.clone(),
|
||||||
@@ -92,12 +118,21 @@ impl Platform for FullstackPlatform {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rebuild(&mut self, crate_config: &CrateConfig) -> Result<crate::BuildResult> {
|
fn rebuild(
|
||||||
|
&mut self,
|
||||||
|
crate_config: &CrateConfig,
|
||||||
|
serve: &ConfigOptsServe,
|
||||||
|
env: Vec<(String, String)>,
|
||||||
|
) -> Result<BuildResult> {
|
||||||
let thread_handle = start_web_build_thread(crate_config, &self.serve);
|
let thread_handle = start_web_build_thread(crate_config, &self.serve);
|
||||||
let desktop_config = make_desktop_config(crate_config, &self.serve);
|
let desktop_config = make_desktop_config(crate_config, &self.serve);
|
||||||
let result = self
|
let mut desktop_env = env.clone();
|
||||||
.desktop
|
add_serve_options_to_env(serve, &mut desktop_env);
|
||||||
.rebuild_with_options(&desktop_config, Some(self.server_rust_flags.clone()));
|
let result = self.desktop.rebuild_with_options(
|
||||||
|
&desktop_config,
|
||||||
|
Some(self.server_rust_flags.clone()),
|
||||||
|
desktop_env,
|
||||||
|
);
|
||||||
thread_handle
|
thread_handle
|
||||||
.join()
|
.join()
|
||||||
.map_err(|_| anyhow::anyhow!("Failed to join thread"))??;
|
.map_err(|_| anyhow::anyhow!("Failed to join thread"))??;
|
||||||
|
|||||||
@@ -309,10 +309,19 @@ fn local_path_of_asset(path: &Path) -> Option<PathBuf> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait Platform {
|
pub(crate) trait Platform {
|
||||||
fn start(config: &CrateConfig, serve: &ConfigOptsServe) -> Result<Self>
|
fn start(
|
||||||
|
config: &CrateConfig,
|
||||||
|
serve: &ConfigOptsServe,
|
||||||
|
env: Vec<(String, String)>,
|
||||||
|
) -> Result<Self>
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
fn rebuild(&mut self, config: &CrateConfig) -> Result<BuildResult>;
|
fn rebuild(
|
||||||
|
&mut self,
|
||||||
|
config: &CrateConfig,
|
||||||
|
serve: &ConfigOptsServe,
|
||||||
|
env: Vec<(String, String)>,
|
||||||
|
) -> Result<BuildResult>;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_backup_file(path: &Path) -> bool {
|
fn is_backup_file(path: &Path) -> bool {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::server::Diagnostic;
|
use crate::server::Diagnostic;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use dioxus_cli_config::{crate_root, CrateConfig};
|
use dioxus_cli_config::{crate_root, CrateConfig};
|
||||||
use std::{path::PathBuf, process::Command};
|
use std::{net::IpAddr, path::PathBuf, process::Command};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct PrettierOptions {
|
pub struct PrettierOptions {
|
||||||
@@ -12,7 +12,7 @@ pub struct PrettierOptions {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct WebServerInfo {
|
pub struct WebServerInfo {
|
||||||
pub ip: String,
|
pub ip: IpAddr,
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,27 +77,24 @@ pub fn print_console_info(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(WebServerInfo { ip, port }) = web_info {
|
if let Some(WebServerInfo { ip, port }) = web_info {
|
||||||
if config.dioxus_config.web.https.enabled == Some(true) {
|
let https = config.dioxus_config.web.https.enabled == Some(true);
|
||||||
println!(
|
let prefix = if https { "https://" } else { "http://" };
|
||||||
" > Local address: {}",
|
println!(
|
||||||
format!("https://localhost:{}/", port).blue()
|
" > Local address: {}",
|
||||||
);
|
format!("{prefix}localhost:{}/", port).blue()
|
||||||
println!(
|
);
|
||||||
" > Network address: {}",
|
println!(
|
||||||
format!("https://{}:{}/", ip, port).blue()
|
" > Network address: {}",
|
||||||
);
|
format!("{prefix}{}:{}/", ip, port).blue()
|
||||||
println!(" > HTTPS: {}", "Enabled".to_string().green());
|
);
|
||||||
} else {
|
println!(
|
||||||
println!(
|
" > HTTPS: {}",
|
||||||
" > Local address: {}",
|
if https {
|
||||||
format!("http://localhost:{}/", port).blue()
|
"Enabled".to_string().green()
|
||||||
);
|
} else {
|
||||||
println!(
|
"Disabled".to_string().red()
|
||||||
" > Network address: {}",
|
}
|
||||||
format!("http://{}:{}/", ip, port).blue()
|
);
|
||||||
);
|
|
||||||
println!(" > HTTPS status: {}", "Disabled".to_string().red());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use crate::{
|
|||||||
use dioxus_cli_config::CrateConfig;
|
use dioxus_cli_config::CrateConfig;
|
||||||
use dioxus_rsx::hot_reload::*;
|
use dioxus_rsx::hot_reload::*;
|
||||||
use std::{
|
use std::{
|
||||||
net::{SocketAddr, UdpSocket},
|
net::{IpAddr, SocketAddr, UdpSocket},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -25,7 +25,11 @@ use super::HotReloadState;
|
|||||||
pub async fn startup(config: CrateConfig, serve_cfg: &ConfigOptsServe) -> Result<()> {
|
pub async fn startup(config: CrateConfig, serve_cfg: &ConfigOptsServe) -> Result<()> {
|
||||||
set_ctrlc_handler(&config);
|
set_ctrlc_handler(&config);
|
||||||
|
|
||||||
let ip = get_ip().unwrap_or(String::from("0.0.0.0"));
|
let ip = serve_cfg
|
||||||
|
.server_arguments
|
||||||
|
.addr
|
||||||
|
.or_else(get_ip)
|
||||||
|
.unwrap_or(IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)));
|
||||||
|
|
||||||
let hot_reload_state = build_hotreload_filemap(&config);
|
let hot_reload_state = build_hotreload_filemap(&config);
|
||||||
|
|
||||||
@@ -34,13 +38,13 @@ pub async fn startup(config: CrateConfig, serve_cfg: &ConfigOptsServe) -> Result
|
|||||||
|
|
||||||
/// Start the server without hot reload
|
/// Start the server without hot reload
|
||||||
pub async fn serve(
|
pub async fn serve(
|
||||||
ip: String,
|
ip: IpAddr,
|
||||||
config: CrateConfig,
|
config: CrateConfig,
|
||||||
hot_reload_state: HotReloadState,
|
hot_reload_state: HotReloadState,
|
||||||
opts: &ConfigOptsServe,
|
opts: &ConfigOptsServe,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let skip_assets = opts.skip_assets;
|
let skip_assets = opts.skip_assets;
|
||||||
let port = opts.port;
|
let port = opts.server_arguments.port;
|
||||||
|
|
||||||
// Since web platform doesn't use `rust_flags`, this argument is explicitly
|
// Since web platform doesn't use `rust_flags`, this argument is explicitly
|
||||||
// set to `None`.
|
// set to `None`.
|
||||||
@@ -60,10 +64,7 @@ pub async fn serve(
|
|||||||
move || build(&config, &hot_reload_state, skip_assets)
|
move || build(&config, &hot_reload_state, skip_assets)
|
||||||
},
|
},
|
||||||
&config,
|
&config,
|
||||||
Some(WebServerInfo {
|
Some(WebServerInfo { ip, port }),
|
||||||
ip: ip.clone(),
|
|
||||||
port,
|
|
||||||
}),
|
|
||||||
hot_reload_state.clone(),
|
hot_reload_state.clone(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -80,23 +81,21 @@ pub async fn serve(
|
|||||||
warnings: first_build_result.warnings,
|
warnings: first_build_result.warnings,
|
||||||
elapsed_time: first_build_result.elapsed_time,
|
elapsed_time: first_build_result.elapsed_time,
|
||||||
},
|
},
|
||||||
Some(WebServerInfo {
|
Some(WebServerInfo { ip, port }),
|
||||||
ip: ip.clone(),
|
|
||||||
port,
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Router
|
// Router
|
||||||
let router = setup_router(config.clone(), hot_reload_state).await?;
|
let router = setup_router(config.clone(), hot_reload_state).await?;
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
start_server(port, router, opts.open, rustls_config, &config).await?;
|
start_server(ip, port, router, opts.open, rustls_config, &config).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts dx serve with no hot reload
|
/// Starts dx serve with no hot reload
|
||||||
async fn start_server(
|
async fn start_server(
|
||||||
|
ip: IpAddr,
|
||||||
port: u16,
|
port: u16,
|
||||||
router: axum::Router,
|
router: axum::Router,
|
||||||
start_browser: bool,
|
start_browser: bool,
|
||||||
@@ -107,20 +106,11 @@ async fn start_server(
|
|||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
crate::plugin::PluginManager::on_serve_start(config)?;
|
crate::plugin::PluginManager::on_serve_start(config)?;
|
||||||
|
|
||||||
// Bind the server to `[::]` and it will LISTEN for both IPv4 and IPv6. (required IPv6 dual stack)
|
let addr: SocketAddr = SocketAddr::from((ip, port));
|
||||||
let addr: SocketAddr = format!("0.0.0.0:{}", port).parse().unwrap();
|
|
||||||
|
|
||||||
// Open the browser
|
// Open the browser
|
||||||
if start_browser {
|
if start_browser {
|
||||||
let protocol = match rustls {
|
open_browser(config, ip, port, rustls.is_some());
|
||||||
Some(_) => "https",
|
|
||||||
None => "http",
|
|
||||||
};
|
|
||||||
let base_path = match config.dioxus_config.web.app.base_path.as_deref() {
|
|
||||||
Some(base_path) => format!("/{}", base_path.trim_matches('/')),
|
|
||||||
None => "".to_owned(),
|
|
||||||
};
|
|
||||||
_ = open::that(format!("{protocol}://localhost:{port}{base_path}"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let svc = router.into_make_service();
|
let svc = router.into_make_service();
|
||||||
@@ -138,8 +128,18 @@ async fn start_server(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Open the browser to the address
|
||||||
|
pub(crate) fn open_browser(config: &CrateConfig, ip: IpAddr, port: u16, https: bool) {
|
||||||
|
let protocol = if https { "https" } else { "http" };
|
||||||
|
let base_path = match config.dioxus_config.web.app.base_path.as_deref() {
|
||||||
|
Some(base_path) => format!("/{}", base_path.trim_matches('/')),
|
||||||
|
None => "".to_owned(),
|
||||||
|
};
|
||||||
|
_ = open::that(format!("{protocol}://{ip}:{port}{base_path}"));
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the network ip
|
/// Get the network ip
|
||||||
fn get_ip() -> Option<String> {
|
fn get_ip() -> Option<IpAddr> {
|
||||||
let socket = match UdpSocket::bind("0.0.0.0:0") {
|
let socket = match UdpSocket::bind("0.0.0.0:0") {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(_) => return None,
|
Err(_) => return None,
|
||||||
@@ -151,7 +151,7 @@ fn get_ip() -> Option<String> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
match socket.local_addr() {
|
match socket.local_addr() {
|
||||||
Ok(addr) => Some(addr.ip().to_string()),
|
Ok(addr) => Some(addr.ip()),
|
||||||
Err(_) => None,
|
Err(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,13 +156,13 @@ impl<Cfg> LaunchBuilder<Cfg, SendContext> {
|
|||||||
/// - Any config will be converted into `Some(config)`
|
/// - Any config will be converted into `Some(config)`
|
||||||
/// - If the config is for another platform, it will be converted into `None`
|
/// - If the config is for another platform, it will be converted into `None`
|
||||||
pub trait TryIntoConfig<Config = Self> {
|
pub trait TryIntoConfig<Config = Self> {
|
||||||
fn into_config(self) -> Option<Config>;
|
fn into_config(self, config: &mut Option<Config>);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A config can always be converted into itself
|
// A config can always be converted into itself
|
||||||
impl<Cfg> TryIntoConfig<Cfg> for Cfg {
|
impl<Cfg> TryIntoConfig<Cfg> for Cfg {
|
||||||
fn into_config(self) -> Option<Cfg> {
|
fn into_config(self, config: &mut Option<Cfg>) {
|
||||||
Some(self)
|
*config = Some(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,15 +176,13 @@ impl<Cfg> TryIntoConfig<Cfg> for Cfg {
|
|||||||
feature = "fullstack"
|
feature = "fullstack"
|
||||||
))]
|
))]
|
||||||
impl TryIntoConfig<current_platform::Config> for () {
|
impl TryIntoConfig<current_platform::Config> for () {
|
||||||
fn into_config(self) -> Option<current_platform::Config> {
|
fn into_config(self, config: &mut Option<current_platform::Config>) {}
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Cfg: Default + 'static, ContextFn: ?Sized> LaunchBuilder<Cfg, ContextFn> {
|
impl<Cfg: Default + 'static, ContextFn: ?Sized> LaunchBuilder<Cfg, ContextFn> {
|
||||||
/// Provide a platform-specific config to the builder.
|
/// Provide a platform-specific config to the builder.
|
||||||
pub fn with_cfg(mut self, config: impl TryIntoConfig<Cfg>) -> Self {
|
pub fn with_cfg(mut self, config: impl TryIntoConfig<Cfg>) -> Self {
|
||||||
self.platform_config = self.platform_config.or(config.into_config());
|
config.into_config(&mut self.platform_config);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,6 +232,21 @@ mod current_platform {
|
|||||||
#[cfg(feature = "fullstack")]
|
#[cfg(feature = "fullstack")]
|
||||||
pub use dioxus_fullstack::launch::*;
|
pub use dioxus_fullstack::launch::*;
|
||||||
|
|
||||||
|
#[cfg(all(feature = "fullstack", feature = "axum"))]
|
||||||
|
impl TryIntoConfig<crate::launch::current_platform::Config>
|
||||||
|
for ::dioxus_fullstack::prelude::ServeConfigBuilder
|
||||||
|
{
|
||||||
|
fn into_config(self, config: &mut Option<crate::launch::current_platform::Config>) {
|
||||||
|
match config {
|
||||||
|
Some(config) => config.set_server_cfg(self),
|
||||||
|
None => {
|
||||||
|
*config =
|
||||||
|
Some(crate::launch::current_platform::Config::new().with_server_cfg(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "desktop", feature = "mobile"))]
|
#[cfg(any(feature = "desktop", feature = "mobile"))]
|
||||||
if_else_cfg! {
|
if_else_cfg! {
|
||||||
if not(feature = "fullstack") {
|
if not(feature = "fullstack") {
|
||||||
@@ -242,9 +255,25 @@ mod current_platform {
|
|||||||
#[cfg(not(feature = "desktop"))]
|
#[cfg(not(feature = "desktop"))]
|
||||||
pub use dioxus_mobile::launch::*;
|
pub use dioxus_mobile::launch::*;
|
||||||
} else {
|
} else {
|
||||||
impl TryIntoConfig<crate::launch::current_platform::Config> for ::dioxus_desktop::Config {
|
if_else_cfg! {
|
||||||
fn into_config(self) -> Option<crate::launch::current_platform::Config> {
|
if feature = "desktop" {
|
||||||
None
|
impl TryIntoConfig<crate::launch::current_platform::Config> for ::dioxus_desktop::Config {
|
||||||
|
fn into_config(self, config: &mut Option<crate::launch::current_platform::Config>) {
|
||||||
|
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<crate::launch::current_platform::Config> for ::dioxus_mobile::Config {
|
||||||
|
fn into_config(self, config: &mut Option<crate::launch::current_platform::Config>) {
|
||||||
|
match config {
|
||||||
|
Some(config) => config.set_mobile_cfg(self),
|
||||||
|
None => *config = Some(crate::launch::current_platform::Config::new().with_mobile_cfg(self)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,9 +285,7 @@ mod current_platform {
|
|||||||
pub use dioxus_static_site_generation::launch::*;
|
pub use dioxus_static_site_generation::launch::*;
|
||||||
} else {
|
} else {
|
||||||
impl TryIntoConfig<crate::launch::current_platform::Config> for ::dioxus_static_site_generation::Config {
|
impl TryIntoConfig<crate::launch::current_platform::Config> for ::dioxus_static_site_generation::Config {
|
||||||
fn into_config(self) -> Option<crate::launch::current_platform::Config> {
|
fn into_config(self, config: &mut Option<crate::launch::current_platform::Config>) {}
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -268,9 +295,20 @@ mod current_platform {
|
|||||||
if not(any(feature = "desktop", feature = "mobile", feature = "fullstack", feature = "static-generation")) {
|
if not(any(feature = "desktop", feature = "mobile", feature = "fullstack", feature = "static-generation")) {
|
||||||
pub use dioxus_web::launch::*;
|
pub use dioxus_web::launch::*;
|
||||||
} else {
|
} else {
|
||||||
impl TryIntoConfig<crate::launch::current_platform::Config> for ::dioxus_web::Config {
|
if_else_cfg! {
|
||||||
fn into_config(self) -> Option<crate::launch::current_platform::Config> {
|
if feature = "fullstack" {
|
||||||
None
|
impl TryIntoConfig<crate::launch::current_platform::Config> for ::dioxus_web::Config {
|
||||||
|
fn into_config(self, config: &mut Option<crate::launch::current_platform::Config>) {
|
||||||
|
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<crate::launch::current_platform::Config> for ::dioxus_web::Config {
|
||||||
|
fn into_config(self, config: &mut Option<crate::launch::current_platform::Config>) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -290,9 +328,7 @@ mod current_platform {
|
|||||||
pub use dioxus_liveview::launch::*;
|
pub use dioxus_liveview::launch::*;
|
||||||
} else {
|
} else {
|
||||||
impl<R: ::dioxus_liveview::LiveviewRouter> TryIntoConfig<crate::launch::current_platform::Config> for ::dioxus_liveview::Config<R> {
|
impl<R: ::dioxus_liveview::LiveviewRouter> TryIntoConfig<crate::launch::current_platform::Config> for ::dioxus_liveview::Config<R> {
|
||||||
fn into_config(self) -> Option<crate::launch::current_platform::Config> {
|
fn into_config(self, config: &mut Option<crate::launch::current_platform::Config>) {}
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ tower-layer = { version = "0.3.2", optional = true }
|
|||||||
web-sys = { version = "0.3.61", optional = true, features = ["Window", "Document", "Element", "HtmlDocument", "Storage", "console"] }
|
web-sys = { version = "0.3.61", optional = true, features = ["Window", "Document", "Element", "HtmlDocument", "Storage", "console"] }
|
||||||
|
|
||||||
dioxus-cli-config = { workspace = true, features = ["read-config"], optional = true }
|
dioxus-cli-config = { workspace = true, features = ["read-config"], optional = true }
|
||||||
|
clap = { version = "4.5.7", optional = true, features = ["derive"] }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
tokio = { workspace = true, features = ["rt", "sync"], optional = true }
|
tokio = { workspace = true, features = ["rt", "sync"], optional = true }
|
||||||
@@ -92,7 +93,9 @@ server = [
|
|||||||
"dep:tracing-futures",
|
"dep:tracing-futures",
|
||||||
"dep:pin-project",
|
"dep:pin-project",
|
||||||
"dep:thiserror",
|
"dep:thiserror",
|
||||||
"dep:dioxus-cli-config"
|
"dep:dioxus-cli-config",
|
||||||
|
"dep:clap",
|
||||||
|
"dioxus-cli-config/cli"
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
|
|||||||
@@ -7,11 +7,12 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let cfg = server_only!(dioxus::fullstack::Config::new().incremental(
|
LaunchBuilder::fullstack()
|
||||||
IncrementalRendererConfig::default().invalidate_after(std::time::Duration::from_secs(120)),
|
.with_cfg(server_only!(ServeConfig::builder().incremental(
|
||||||
));
|
IncrementalRendererConfig::default()
|
||||||
|
.invalidate_after(std::time::Duration::from_secs(120)),
|
||||||
LaunchBuilder::fullstack().with_cfg(cfg).launch(app);
|
)))
|
||||||
|
.launch(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app() -> Element {
|
fn app() -> Element {
|
||||||
|
|||||||
@@ -6,15 +6,9 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
/// Settings for a fullstack app.
|
/// Settings for a fullstack app.
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[cfg(feature = "server")]
|
|
||||||
pub(crate) server_fn_route: &'static str,
|
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
pub(crate) server_cfg: ServeConfigBuilder,
|
pub(crate) server_cfg: ServeConfigBuilder,
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
|
||||||
pub(crate) addr: std::net::SocketAddr,
|
|
||||||
|
|
||||||
#[cfg(feature = "web")]
|
#[cfg(feature = "web")]
|
||||||
pub(crate) web_cfg: dioxus_web::Config,
|
pub(crate) web_cfg: dioxus_web::Config,
|
||||||
|
|
||||||
@@ -29,10 +23,6 @@ pub struct Config {
|
|||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
#[cfg(feature = "server")]
|
|
||||||
server_fn_route: "",
|
|
||||||
#[cfg(feature = "server")]
|
|
||||||
addr: std::net::SocketAddr::from(([0, 0, 0, 0], 8080)),
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
server_cfg: ServeConfigBuilder::new(),
|
server_cfg: ServeConfigBuilder::new(),
|
||||||
#[cfg(feature = "web")]
|
#[cfg(feature = "web")]
|
||||||
@@ -51,24 +41,6 @@ impl Config {
|
|||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the address to serve the app on.
|
|
||||||
#[cfg(feature = "server")]
|
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
|
||||||
pub fn addr(self, addr: impl Into<std::net::SocketAddr>) -> Self {
|
|
||||||
let addr = addr.into();
|
|
||||||
Self { addr, ..self }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the route to the server functions.
|
|
||||||
#[cfg(feature = "server")]
|
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
|
||||||
pub fn server_fn_route(self, server_fn_route: &'static str) -> Self {
|
|
||||||
Self {
|
|
||||||
server_fn_route,
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the incremental renderer config.
|
/// Set the incremental renderer config.
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||||
@@ -82,77 +54,52 @@ impl Config {
|
|||||||
/// Set the server config.
|
/// Set the server config.
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||||
pub fn server_cfg(self, server_cfg: ServeConfigBuilder) -> Self {
|
pub fn with_server_cfg(self, server_cfg: ServeConfigBuilder) -> Self {
|
||||||
Self { server_cfg, ..self }
|
Self { server_cfg, ..self }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the server config.
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
||||||
|
pub fn set_server_cfg(&mut self, server_cfg: ServeConfigBuilder) {
|
||||||
|
self.server_cfg = server_cfg;
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the web config.
|
/// Set the web config.
|
||||||
#[cfg(feature = "web")]
|
#[cfg(feature = "web")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "web")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "web")))]
|
||||||
pub fn web_cfg(self, web_cfg: dioxus_web::Config) -> Self {
|
pub fn with_web_config(self, web_cfg: dioxus_web::Config) -> Self {
|
||||||
Self { web_cfg, ..self }
|
Self { web_cfg, ..self }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the desktop config.
|
/// Set the web config.
|
||||||
|
#[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
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "desktop")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "desktop")))]
|
||||||
pub fn desktop_cfg(self, desktop_cfg: dioxus_desktop::Config) -> Self {
|
pub fn with_desktop_config(self, desktop_cfg: dioxus_desktop::Config) -> Self {
|
||||||
Self {
|
Self {
|
||||||
desktop_cfg,
|
desktop_cfg,
|
||||||
..self
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the desktop config.
|
||||||
|
#[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.
|
/// Set the mobile config.
|
||||||
#[cfg(feature = "mobile")]
|
#[cfg(feature = "mobile")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "mobile")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "mobile")))]
|
||||||
pub fn mobile_cfg(self, mobile_cfg: dioxus_mobile::Config) -> Self {
|
pub fn with_mobile_cfg(self, mobile_cfg: dioxus_mobile::Config) -> Self {
|
||||||
Self { mobile_cfg, ..self }
|
Self { mobile_cfg, ..self }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
|
|
||||||
/// Launch a server application
|
|
||||||
pub async fn launch_server(
|
|
||||||
self,
|
|
||||||
build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static,
|
|
||||||
context_providers: crate::launch::ContextProviders,
|
|
||||||
) {
|
|
||||||
use std::any::Any;
|
|
||||||
|
|
||||||
let addr = self.addr;
|
|
||||||
println!("Listening on http://{}", addr);
|
|
||||||
let cfg = self.server_cfg.build();
|
|
||||||
let server_fn_route = self.server_fn_route;
|
|
||||||
|
|
||||||
#[cfg(feature = "axum")]
|
|
||||||
{
|
|
||||||
use crate::axum_adapter::{render_handler, DioxusRouterExt};
|
|
||||||
use axum::routing::get;
|
|
||||||
use tower::ServiceBuilder;
|
|
||||||
|
|
||||||
let ssr_state = SSRState::new(&cfg);
|
|
||||||
let router =
|
|
||||||
axum::Router::new().register_server_functions_with_context(context_providers);
|
|
||||||
#[cfg(not(any(feature = "desktop", feature = "mobile")))]
|
|
||||||
let router = {
|
|
||||||
let mut router = router.serve_static_assets(cfg.assets_path.clone()).await;
|
|
||||||
|
|
||||||
#[cfg(all(feature = "hot-reload", debug_assertions))]
|
|
||||||
{
|
|
||||||
use dioxus_hot_reload::HotReloadRouterExt;
|
|
||||||
router = router.forward_cli_hot_reloading();
|
|
||||||
}
|
|
||||||
|
|
||||||
router.fallback(get(render_handler).with_state((
|
|
||||||
cfg,
|
|
||||||
Arc::new(build_virtual_dom),
|
|
||||||
ssr_state,
|
|
||||||
)))
|
|
||||||
};
|
|
||||||
let router = router.into_make_service();
|
|
||||||
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
|
||||||
axum::serve(listener, router).await.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,7 @@ pub fn launch(
|
|||||||
tokio::runtime::Runtime::new()
|
tokio::runtime::Runtime::new()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.block_on(async move {
|
.block_on(async move {
|
||||||
platform_config.launch_server(factory, contexts).await;
|
launch_server(platform_config, factory, contexts).await;
|
||||||
});
|
});
|
||||||
|
|
||||||
unreachable!("Launching a fullstack app should never return")
|
unreachable!("Launching a fullstack app should never return")
|
||||||
@@ -50,7 +50,8 @@ pub fn launch(
|
|||||||
contexts: Vec<Box<dyn Fn() -> Box<dyn Any + Send + Sync> + Send + Sync>>,
|
contexts: Vec<Box<dyn Fn() -> Box<dyn Any + Send + Sync> + Send + Sync>>,
|
||||||
platform_config: Config,
|
platform_config: Config,
|
||||||
) {
|
) {
|
||||||
let factory = virtual_dom_factory(root, Arc::new(contexts));
|
let contexts = Arc::new(contexts);
|
||||||
|
let factory = virtual_dom_factory(root, contexts);
|
||||||
let cfg = platform_config.web_cfg.hydrate(true);
|
let cfg = platform_config.web_cfg.hydrate(true);
|
||||||
dioxus_web::launch::launch_virtual_dom(factory(), cfg)
|
dioxus_web::launch::launch_virtual_dom(factory(), cfg)
|
||||||
}
|
}
|
||||||
@@ -63,7 +64,8 @@ pub fn launch(
|
|||||||
contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,
|
contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,
|
||||||
platform_config: Config,
|
platform_config: Config,
|
||||||
) -> ! {
|
) -> ! {
|
||||||
let factory = virtual_dom_factory(root, Arc::new(contexts));
|
let contexts = Arc::new(contexts);
|
||||||
|
let factory = virtual_dom_factory(root, contexts);
|
||||||
let cfg = platform_config.desktop_cfg;
|
let cfg = platform_config.desktop_cfg;
|
||||||
dioxus_desktop::launch::launch_virtual_dom(factory(), cfg)
|
dioxus_desktop::launch::launch_virtual_dom(factory(), cfg)
|
||||||
}
|
}
|
||||||
@@ -76,10 +78,67 @@ pub fn launch(
|
|||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub fn launch(
|
pub fn launch(
|
||||||
root: fn() -> Element,
|
root: fn() -> Element,
|
||||||
contexts: Vec<Box<dyn Fn() -> Box<dyn Any> + Send + Sync>>,
|
contexts: Vec<Box<dyn Fn() -> Box<dyn Any + Send + Sync> + Send + Sync>>,
|
||||||
platform_config: Config,
|
platform_config: Config,
|
||||||
) {
|
) -> ! {
|
||||||
let factory = virtual_dom_factory(root, Arc::new(contexts));
|
let contexts = Arc::new(contexts);
|
||||||
|
let factory = virtual_dom_factory(root, contexts.clone());
|
||||||
let cfg = platform_config.mobile_cfg;
|
let cfg = platform_config.mobile_cfg;
|
||||||
dioxus_mobile::launch::launch_virtual_dom(factory(), cfg)
|
dioxus_mobile::launch::launch_virtual_dom(factory(), cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
) {
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
let args = dioxus_cli_config::ServeArguments::from_cli()
|
||||||
|
.unwrap_or_else(dioxus_cli_config::ServeArguments::parse);
|
||||||
|
let addr = args
|
||||||
|
.addr
|
||||||
|
.unwrap_or(std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)));
|
||||||
|
let addr = std::net::SocketAddr::new(addr, args.port);
|
||||||
|
println!("Listening on http://{}", addr);
|
||||||
|
|
||||||
|
#[cfg(feature = "axum")]
|
||||||
|
{
|
||||||
|
use crate::axum_adapter::DioxusRouterExt;
|
||||||
|
|
||||||
|
let router = axum::Router::new().register_server_functions_with_context(context_providers);
|
||||||
|
#[cfg(not(any(feature = "desktop", feature = "mobile")))]
|
||||||
|
let router = {
|
||||||
|
use crate::prelude::SSRState;
|
||||||
|
|
||||||
|
let cfg = platform_config.server_cfg.build();
|
||||||
|
|
||||||
|
let ssr_state = SSRState::new(&cfg);
|
||||||
|
let mut router = router.serve_static_assets(cfg.assets_path.clone()).await;
|
||||||
|
|
||||||
|
#[cfg(all(feature = "hot-reload", debug_assertions))]
|
||||||
|
{
|
||||||
|
use dioxus_hot_reload::HotReloadRouterExt;
|
||||||
|
router = router.forward_cli_hot_reloading();
|
||||||
|
}
|
||||||
|
|
||||||
|
router.fallback(
|
||||||
|
axum::routing::get(crate::axum_adapter::render_handler).with_state((
|
||||||
|
cfg,
|
||||||
|
Arc::new(build_virtual_dom),
|
||||||
|
ssr_state,
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let router = router.into_make_service();
|
||||||
|
let listener = tokio::net::TcpListener::bind(addr).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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,8 @@ mod render;
|
|||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
mod serve_config;
|
mod serve_config;
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
pub use serve_config::*;
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
mod server_context;
|
mod server_context;
|
||||||
|
|||||||
@@ -8,11 +8,7 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
LaunchBuilder::fullstack()
|
LaunchBuilder::fullstack().launch(app);
|
||||||
.with_cfg(ssr! {
|
|
||||||
dioxus::fullstack::Config::default().addr(std::net::SocketAddr::from(([127, 0, 0, 1], 3333)))
|
|
||||||
})
|
|
||||||
.launch(app);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app() -> Element {
|
fn app() -> Element {
|
||||||
|
|||||||
@@ -82,15 +82,17 @@ module.exports = defineConfig({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
cwd: path.join(process.cwd(), "web"),
|
cwd: path.join(process.cwd(), "web"),
|
||||||
command: "cargo run --package dioxus-cli --release -- serve",
|
command:
|
||||||
port: 8080,
|
'cargo run --package dioxus-cli --release -- serve --addr "127.0.0.1" --port 9999',
|
||||||
|
port: 9999,
|
||||||
timeout: 20 * 60 * 1000,
|
timeout: 20 * 60 * 1000,
|
||||||
reuseExistingServer: !process.env.CI,
|
reuseExistingServer: !process.env.CI,
|
||||||
stdout: "pipe",
|
stdout: "pipe",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cwd: path.join(process.cwd(), 'fullstack'),
|
cwd: path.join(process.cwd(), "fullstack"),
|
||||||
command: 'cargo run --package dioxus-cli --release -- serve --platform fullstack',
|
command:
|
||||||
|
'cargo run --package dioxus-cli --release -- serve --platform fullstack --addr "127.0.0.1" --port 3333',
|
||||||
port: 3333,
|
port: 3333,
|
||||||
timeout: 20 * 60 * 1000,
|
timeout: 20 * 60 * 1000,
|
||||||
reuseExistingServer: !process.env.CI,
|
reuseExistingServer: !process.env.CI,
|
||||||
|
|||||||
@@ -1,94 +1,93 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
const { test, expect, defineConfig } = require('@playwright/test');
|
const { test, expect, defineConfig } = require("@playwright/test");
|
||||||
|
|
||||||
test('button click', async ({ page }) => {
|
test("button click", async ({ page }) => {
|
||||||
await page.goto('http://localhost:8080');
|
await page.goto("http://localhost:9999");
|
||||||
|
|
||||||
// Expect the page to contain the counter text.
|
// Expect the page to contain the counter text.
|
||||||
const main = page.locator('#main');
|
const main = page.locator("#main");
|
||||||
await expect(main).toContainText('hello axum! 0');
|
await expect(main).toContainText("hello axum! 0");
|
||||||
|
|
||||||
// Click the increment button.
|
// Click the increment button.
|
||||||
let button = page.locator('button.increment-button');
|
let button = page.locator("button.increment-button");
|
||||||
await button.click();
|
await button.click();
|
||||||
|
|
||||||
// Expect the page to contain the updated counter text.
|
// Expect the page to contain the updated counter text.
|
||||||
await expect(main).toContainText('hello axum! 1');
|
await expect(main).toContainText("hello axum! 1");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('svg', async ({ page }) => {
|
test("svg", async ({ page }) => {
|
||||||
await page.goto('http://localhost:8080');
|
await page.goto("http://localhost:9999");
|
||||||
|
|
||||||
// Expect the page to contain the svg.
|
// Expect the page to contain the svg.
|
||||||
const svg = page.locator('svg');
|
const svg = page.locator("svg");
|
||||||
|
|
||||||
// Expect the svg to contain the circle.
|
// Expect the svg to contain the circle.
|
||||||
const circle = svg.locator('circle');
|
const circle = svg.locator("circle");
|
||||||
await expect(circle).toHaveAttribute('cx', '50');
|
await expect(circle).toHaveAttribute("cx", "50");
|
||||||
await expect(circle).toHaveAttribute('cy', '50');
|
await expect(circle).toHaveAttribute("cy", "50");
|
||||||
await expect(circle).toHaveAttribute('r', '40');
|
await expect(circle).toHaveAttribute("r", "40");
|
||||||
await expect(circle).toHaveAttribute('stroke', 'green');
|
await expect(circle).toHaveAttribute("stroke", "green");
|
||||||
await expect(circle).toHaveAttribute('fill', 'yellow');
|
await expect(circle).toHaveAttribute("fill", "yellow");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('raw attribute', async ({ page }) => {
|
test("raw attribute", async ({ page }) => {
|
||||||
await page.goto('http://localhost:8080');
|
await page.goto("http://localhost:9999");
|
||||||
|
|
||||||
// Expect the page to contain the div with the raw attribute.
|
// Expect the page to contain the div with the raw attribute.
|
||||||
const div = page.locator('div.raw-attribute-div');
|
const div = page.locator("div.raw-attribute-div");
|
||||||
await expect(div).toHaveAttribute('raw-attribute', 'raw-attribute-value');
|
await expect(div).toHaveAttribute("raw-attribute", "raw-attribute-value");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('hidden attribute', async ({ page }) => {
|
test("hidden attribute", async ({ page }) => {
|
||||||
await page.goto('http://localhost:8080');
|
await page.goto("http://localhost:9999");
|
||||||
|
|
||||||
// Expect the page to contain the div with the hidden attribute.
|
// Expect the page to contain the div with the hidden attribute.
|
||||||
const div = page.locator('div.hidden-attribute-div');
|
const div = page.locator("div.hidden-attribute-div");
|
||||||
await expect(div).toHaveAttribute('hidden', 'true');
|
await expect(div).toHaveAttribute("hidden", "true");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('dangerous inner html', async ({ page }) => {
|
test("dangerous inner html", async ({ page }) => {
|
||||||
await page.goto('http://localhost:8080');
|
await page.goto("http://localhost:9999");
|
||||||
|
|
||||||
// Expect the page to contain the div with the dangerous inner html.
|
// Expect the page to contain the div with the dangerous inner html.
|
||||||
const div = page.locator('div.dangerous-inner-html-div');
|
const div = page.locator("div.dangerous-inner-html-div");
|
||||||
await expect(div).toContainText('hello dangerous inner html');
|
await expect(div).toContainText("hello dangerous inner html");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('input value', async ({ page }) => {
|
test("input value", async ({ page }) => {
|
||||||
await page.goto('http://localhost:8080');
|
await page.goto("http://localhost:9999");
|
||||||
|
|
||||||
// Expect the page to contain the input with the value.
|
// Expect the page to contain the input with the value.
|
||||||
const input = page.locator('input');
|
const input = page.locator("input");
|
||||||
await expect(input).toHaveValue('hello input');
|
await expect(input).toHaveValue("hello input");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('style', async ({ page }) => {
|
test("style", async ({ page }) => {
|
||||||
await page.goto('http://localhost:8080');
|
await page.goto("http://localhost:9999");
|
||||||
|
|
||||||
// Expect the page to contain the div with the style.
|
// Expect the page to contain the div with the style.
|
||||||
const div = page.locator('div.style-div');
|
const div = page.locator("div.style-div");
|
||||||
await expect(div).toHaveText('colored text');
|
await expect(div).toHaveText("colored text");
|
||||||
await expect(div).toHaveCSS('color', 'rgb(255, 0, 0)');
|
await expect(div).toHaveCSS("color", "rgb(255, 0, 0)");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('eval', async ({ page }) => {
|
test("eval", async ({ page }) => {
|
||||||
await page.goto('http://localhost:8080');
|
await page.goto("http://localhost:9999");
|
||||||
|
|
||||||
// Expect the page to contain the div with the eval and have no text.
|
// Expect the page to contain the div with the eval and have no text.
|
||||||
const div = page.locator('div.eval-result');
|
const div = page.locator("div.eval-result");
|
||||||
await expect(div).toHaveText('');
|
await expect(div).toHaveText("");
|
||||||
|
|
||||||
// Click the button to run the eval.
|
// Click the button to run the eval.
|
||||||
let button = page.locator('button.eval-button');
|
let button = page.locator("button.eval-button");
|
||||||
await button.click();
|
await button.click();
|
||||||
|
|
||||||
// Check that the title changed.
|
// Check that the title changed.
|
||||||
await expect(page).toHaveTitle('Hello from Dioxus Eval!');
|
await expect(page).toHaveTitle("Hello from Dioxus Eval!");
|
||||||
|
|
||||||
// Check that the div has the eval value.
|
// Check that the div has the eval value.
|
||||||
await expect(div).toHaveText('returned eval value');
|
await expect(div).toHaveText("returned eval value");
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Shutdown the li
|
// Shutdown the li
|
||||||
Reference in new issue
Block a user