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
@@ -1,4 +1,5 @@
|
||||
use dioxus_cli_config::Platform;
|
||||
use dioxus_cli_config::ServeArguments;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -80,10 +81,9 @@ impl From<ConfigOptsServe> for ConfigOptsBuild {
|
||||
#[derive(Clone, Debug, Default, Deserialize, Parser)]
|
||||
#[command(group = clap::ArgGroup::new("release-incompatible").multiple(true).conflicts_with("release"))]
|
||||
pub struct ConfigOptsServe {
|
||||
/// Port of dev server
|
||||
#[clap(long)]
|
||||
#[clap(default_value_t = 8080)]
|
||||
pub port: u16,
|
||||
/// Arguments for the serve command
|
||||
#[clap(flatten)]
|
||||
pub(crate) server_arguments: ServeArguments,
|
||||
|
||||
/// Open the app in the default browser [default: true]
|
||||
#[clap(long, default_value_t = false)]
|
||||
@@ -156,6 +156,10 @@ pub struct ConfigOptsServe {
|
||||
#[clap(long)]
|
||||
pub target: Option<String>,
|
||||
|
||||
/// Additional arguments to pass to the executable
|
||||
#[clap(long)]
|
||||
pub args: Vec<String>,
|
||||
|
||||
/// Extra arguments passed to cargo build
|
||||
#[clap(last = true)]
|
||||
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...");
|
||||
|
||||
@@ -100,7 +100,13 @@ async fn serve<P: Platform + Send + 'static>(
|
||||
let _watcher = setup_file_watcher(
|
||||
{
|
||||
let config = config.clone();
|
||||
move || platform.write().unwrap().rebuild(&config)
|
||||
let serve = serve.clone();
|
||||
move || {
|
||||
platform
|
||||
.write()
|
||||
.unwrap()
|
||||
.rebuild(&config, &serve, Vec::new())
|
||||
}
|
||||
},
|
||||
&config,
|
||||
None,
|
||||
@@ -238,6 +244,8 @@ fn start_desktop(
|
||||
config: &CrateConfig,
|
||||
skip_assets: bool,
|
||||
rust_flags: Option<String>,
|
||||
args: &Vec<String>,
|
||||
env: Vec<(String, String)>,
|
||||
) -> Result<(RAIIChild, BuildResult)> {
|
||||
// Run the desktop application
|
||||
// Only used for the fullstack platform,
|
||||
@@ -251,7 +259,9 @@ fn start_desktop(
|
||||
.clone()
|
||||
.ok_or(anyhow::anyhow!("No executable found after desktop build"))?,
|
||||
)
|
||||
.args(args)
|
||||
.env(active, "true")
|
||||
.envs(env)
|
||||
.spawn()?,
|
||||
);
|
||||
|
||||
@@ -259,6 +269,7 @@ fn start_desktop(
|
||||
}
|
||||
|
||||
pub(crate) struct DesktopPlatform {
|
||||
args: Vec<String>,
|
||||
currently_running_child: RAIIChild,
|
||||
skip_assets: bool,
|
||||
}
|
||||
@@ -270,8 +281,10 @@ impl DesktopPlatform {
|
||||
config: &CrateConfig,
|
||||
serve: &ConfigOptsServe,
|
||||
rust_flags: Option<String>,
|
||||
env: Vec<(String, String)>,
|
||||
) -> 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...");
|
||||
|
||||
@@ -287,6 +300,7 @@ impl DesktopPlatform {
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
args: serve.args.clone(),
|
||||
currently_running_child: child,
|
||||
skip_assets: serve.skip_assets,
|
||||
})
|
||||
@@ -298,6 +312,7 @@ impl DesktopPlatform {
|
||||
&mut self,
|
||||
config: &CrateConfig,
|
||||
rust_flags: Option<String>,
|
||||
env: Vec<(String, String)>,
|
||||
) -> Result<BuildResult> {
|
||||
// Gracefully shtudown the desktop app
|
||||
// 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
|
||||
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;
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// was moved there.
|
||||
// Since desktop platform doesn't use `rust_flags`, this argument is
|
||||
// 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
|
||||
// was moved there.
|
||||
// Since desktop platform doesn't use `rust_flags`, this argument is
|
||||
// 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::{
|
||||
cfg::{ConfigOptsBuild, ConfigOptsServe},
|
||||
Result,
|
||||
BuildResult, Result,
|
||||
};
|
||||
|
||||
use super::{desktop, Platform};
|
||||
@@ -61,6 +61,13 @@ fn make_desktop_config(config: &CrateConfig, serve: &ConfigOptsServe) -> CrateCo
|
||||
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 {
|
||||
serve: ConfigOptsServe,
|
||||
desktop: desktop::DesktopPlatform,
|
||||
@@ -68,7 +75,11 @@ struct 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
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -76,15 +87,30 @@ impl Platform for FullstackPlatform {
|
||||
|
||||
let desktop_config = make_desktop_config(config, serve);
|
||||
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(
|
||||
&desktop_config,
|
||||
serve,
|
||||
Some(server_rust_flags.clone()),
|
||||
desktop_env,
|
||||
)?;
|
||||
thread_handle
|
||||
.join()
|
||||
.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 {
|
||||
desktop,
|
||||
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 desktop_config = make_desktop_config(crate_config, &self.serve);
|
||||
let result = self
|
||||
.desktop
|
||||
.rebuild_with_options(&desktop_config, Some(self.server_rust_flags.clone()));
|
||||
let mut desktop_env = env.clone();
|
||||
add_serve_options_to_env(serve, &mut desktop_env);
|
||||
let result = self.desktop.rebuild_with_options(
|
||||
&desktop_config,
|
||||
Some(self.server_rust_flags.clone()),
|
||||
desktop_env,
|
||||
);
|
||||
thread_handle
|
||||
.join()
|
||||
.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 {
|
||||
fn start(config: &CrateConfig, serve: &ConfigOptsServe) -> Result<Self>
|
||||
fn start(
|
||||
config: &CrateConfig,
|
||||
serve: &ConfigOptsServe,
|
||||
env: Vec<(String, String)>,
|
||||
) -> Result<Self>
|
||||
where
|
||||
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 {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::server::Diagnostic;
|
||||
use colored::Colorize;
|
||||
use dioxus_cli_config::{crate_root, CrateConfig};
|
||||
use std::{path::PathBuf, process::Command};
|
||||
use std::{net::IpAddr, path::PathBuf, process::Command};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PrettierOptions {
|
||||
@@ -12,7 +12,7 @@ pub struct PrettierOptions {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WebServerInfo {
|
||||
pub ip: String,
|
||||
pub ip: IpAddr,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
@@ -77,27 +77,24 @@ pub fn print_console_info(
|
||||
}
|
||||
|
||||
if let Some(WebServerInfo { ip, port }) = web_info {
|
||||
if config.dioxus_config.web.https.enabled == Some(true) {
|
||||
println!(
|
||||
" > Local address: {}",
|
||||
format!("https://localhost:{}/", port).blue()
|
||||
);
|
||||
println!(
|
||||
" > Network address: {}",
|
||||
format!("https://{}:{}/", ip, port).blue()
|
||||
);
|
||||
println!(" > HTTPS: {}", "Enabled".to_string().green());
|
||||
} else {
|
||||
println!(
|
||||
" > Local address: {}",
|
||||
format!("http://localhost:{}/", port).blue()
|
||||
);
|
||||
println!(
|
||||
" > Network address: {}",
|
||||
format!("http://{}:{}/", ip, port).blue()
|
||||
);
|
||||
println!(" > HTTPS status: {}", "Disabled".to_string().red());
|
||||
}
|
||||
let https = config.dioxus_config.web.https.enabled == Some(true);
|
||||
let prefix = if https { "https://" } else { "http://" };
|
||||
println!(
|
||||
" > Local address: {}",
|
||||
format!("{prefix}localhost:{}/", port).blue()
|
||||
);
|
||||
println!(
|
||||
" > Network address: {}",
|
||||
format!("{prefix}{}:{}/", ip, port).blue()
|
||||
);
|
||||
println!(
|
||||
" > HTTPS: {}",
|
||||
if https {
|
||||
"Enabled".to_string().green()
|
||||
} else {
|
||||
"Disabled".to_string().red()
|
||||
}
|
||||
);
|
||||
}
|
||||
println!();
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::{
|
||||
use dioxus_cli_config::CrateConfig;
|
||||
use dioxus_rsx::hot_reload::*;
|
||||
use std::{
|
||||
net::{SocketAddr, UdpSocket},
|
||||
net::{IpAddr, SocketAddr, UdpSocket},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
@@ -25,7 +25,11 @@ use super::HotReloadState;
|
||||
pub async fn startup(config: CrateConfig, serve_cfg: &ConfigOptsServe) -> Result<()> {
|
||||
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);
|
||||
|
||||
@@ -34,13 +38,13 @@ pub async fn startup(config: CrateConfig, serve_cfg: &ConfigOptsServe) -> Result
|
||||
|
||||
/// Start the server without hot reload
|
||||
pub async fn serve(
|
||||
ip: String,
|
||||
ip: IpAddr,
|
||||
config: CrateConfig,
|
||||
hot_reload_state: HotReloadState,
|
||||
opts: &ConfigOptsServe,
|
||||
) -> Result<()> {
|
||||
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
|
||||
// set to `None`.
|
||||
@@ -60,10 +64,7 @@ pub async fn serve(
|
||||
move || build(&config, &hot_reload_state, skip_assets)
|
||||
},
|
||||
&config,
|
||||
Some(WebServerInfo {
|
||||
ip: ip.clone(),
|
||||
port,
|
||||
}),
|
||||
Some(WebServerInfo { ip, port }),
|
||||
hot_reload_state.clone(),
|
||||
)
|
||||
.await?;
|
||||
@@ -80,23 +81,21 @@ pub async fn serve(
|
||||
warnings: first_build_result.warnings,
|
||||
elapsed_time: first_build_result.elapsed_time,
|
||||
},
|
||||
Some(WebServerInfo {
|
||||
ip: ip.clone(),
|
||||
port,
|
||||
}),
|
||||
Some(WebServerInfo { ip, port }),
|
||||
);
|
||||
|
||||
// Router
|
||||
let router = setup_router(config.clone(), hot_reload_state).await?;
|
||||
|
||||
// Start server
|
||||
start_server(port, router, opts.open, rustls_config, &config).await?;
|
||||
start_server(ip, port, router, opts.open, rustls_config, &config).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Starts dx serve with no hot reload
|
||||
async fn start_server(
|
||||
ip: IpAddr,
|
||||
port: u16,
|
||||
router: axum::Router,
|
||||
start_browser: bool,
|
||||
@@ -107,20 +106,11 @@ async fn start_server(
|
||||
#[cfg(feature = "plugin")]
|
||||
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 = format!("0.0.0.0:{}", port).parse().unwrap();
|
||||
let addr: SocketAddr = SocketAddr::from((ip, port));
|
||||
|
||||
// Open the browser
|
||||
if start_browser {
|
||||
let protocol = match rustls {
|
||||
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}"));
|
||||
open_browser(config, ip, port, rustls.is_some());
|
||||
}
|
||||
|
||||
let svc = router.into_make_service();
|
||||
@@ -138,8 +128,18 @@ async fn start_server(
|
||||
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
|
||||
fn get_ip() -> Option<String> {
|
||||
fn get_ip() -> Option<IpAddr> {
|
||||
let socket = match UdpSocket::bind("0.0.0.0:0") {
|
||||
Ok(s) => s,
|
||||
Err(_) => return None,
|
||||
@@ -151,7 +151,7 @@ fn get_ip() -> Option<String> {
|
||||
};
|
||||
|
||||
match socket.local_addr() {
|
||||
Ok(addr) => Some(addr.ip().to_string()),
|
||||
Ok(addr) => Some(addr.ip()),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user