Simplify the launch builder (#2967)

* improve documentation for the fullstack server context

* Add a section about axum integration to the crate root docs

* make serve_dioxus_application accept the cfg builder directly

* remove unused server_fn module

* improve fullstack config docs

* improve documentation for the server function macro

* fix axum router extension link

* Fix doc tests

* Fix launch builder

* Simplify the launch builder

* don't re-export launch in the prelude

* refactor fullstack launch

* Fix fullstack launch builder

* Update static generation with the new builder api

* fix some formatting/overly broad launch replacements

* fix custom menu example

* fix fullstack/static generation examples

* Fix static generation launch

* A few small formatting fixes

* Fix a few doc tests

* implement LaunchConfig for serve configs

* fix fullstack launch with separate web and server launch methods

* fix check with all features

* dont expose inner core module

* clippy and check

* fix readme

---------

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
Evan AlmloffandJonathan Kelley authored and GitHub committed 2024-10-10 16:00:58 -07:00
1 parent 2d7bef44e8
commit 20d146d9bd
126 files changed
+618 -989

No files matched your search

+69 -25
View File
@@ -150,6 +150,49 @@ impl BuildRequest {
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct OpenArguments {
fullstack_address: Option<SocketAddr>,
devserver_addr: Option<SocketAddr>,
always_on_top: Option<bool>,
workspace: PathBuf,
asset_root: PathBuf,
app_title: String,
out_dir: PathBuf,
serve: bool,
}
impl OpenArguments {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
serve: &ServeArguments,
fullstack_address: Option<SocketAddr>,
dioxus_crate: &DioxusCrate,
) -> Self {
Self {
devserver_addr: Some(serve.address.address()),
always_on_top: Some(serve.always_on_top.unwrap_or(true)),
serve: true,
fullstack_address,
workspace: dioxus_crate.workspace_dir().to_path_buf(),
asset_root: dioxus_crate.asset_dir().to_path_buf(),
app_title: dioxus_crate.dioxus_config.application.name.clone(),
out_dir: dioxus_crate.out_dir().to_path_buf(),
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn new_for_static_generation_build(dioxus_crate: &DioxusCrate) -> Self {
Self {
workspace: dioxus_crate.workspace_dir().to_path_buf(),
asset_root: dioxus_crate.asset_dir().to_path_buf(),
app_title: dioxus_crate.dioxus_config.application.name.clone(),
out_dir: dioxus_crate.out_dir().to_path_buf(),
..Default::default()
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct BuildResult {
pub executable: PathBuf,
@@ -158,20 +201,12 @@ pub(crate) struct BuildResult {
impl BuildResult {
/// Open the executable if this is a native build
#[allow(clippy::too_many_arguments)]
pub fn open(
&self,
serve: &ServeArguments,
fullstack_address: Option<SocketAddr>,
workspace: &std::path::Path,
asset_root: &std::path::Path,
devserver_addr: SocketAddr,
app_title: String,
out_dir: PathBuf,
) -> std::io::Result<Option<Child>> {
pub fn open(&self, arguments: OpenArguments) -> std::io::Result<Option<Child>> {
match self.target_platform {
TargetPlatform::Web => {
tracing::info!(dx_src = ?TraceSrc::Dev, "Serving web app on http://{} 🎉", serve.address.address());
if let Some(address) = arguments.fullstack_address {
tracing::info!(dx_src = ?TraceSrc::Dev, "Serving web app on http://{} 🎉", address);
}
return Ok(None);
}
TargetPlatform::Desktop => {
@@ -181,7 +216,7 @@ impl BuildResult {
// shut this up for now - the web app will take priority in logging
}
TargetPlatform::Liveview => {
if let Some(fullstack_address) = fullstack_address {
if let Some(fullstack_address) = arguments.fullstack_address {
tracing::info!(
dx_src = ?TraceSrc::Dev,
"Launching liveview server on http://{:?} 🎉",
@@ -191,7 +226,9 @@ impl BuildResult {
}
}
tracing::info!(dx_src = ?TraceSrc::Dev, "Press [o] to open the app manually.");
if arguments.serve {
tracing::info!(dx_src = ?TraceSrc::Dev, "Press [o] to open the app manually.");
}
let executable = self.executable.canonicalize()?;
let mut cmd = Command::new(executable);
@@ -199,29 +236,36 @@ impl BuildResult {
// Set the env vars that the clients will expect
// These need to be stable within a release version (ie 0.6.0)
cmd.env(dioxus_cli_config::CLI_ENABLED_ENV, "true");
if let Some(addr) = fullstack_address {
if let Some(addr) = arguments.fullstack_address {
cmd.env(dioxus_cli_config::SERVER_IP_ENV, addr.ip().to_string());
cmd.env(dioxus_cli_config::SERVER_PORT_ENV, addr.port().to_string());
}
cmd.env(
dioxus_cli_config::ALWAYS_ON_TOP_ENV,
serve.always_on_top.unwrap_or(true).to_string(),
);
if let Some(always_on_top) = arguments.always_on_top {
cmd.env(
dioxus_cli_config::ALWAYS_ON_TOP_ENV,
always_on_top.to_string(),
);
}
cmd.env(
dioxus_cli_config::ASSET_ROOT_ENV,
asset_root.display().to_string(),
arguments.asset_root.display().to_string(),
);
if let Some(devserver_addr) = arguments.devserver_addr {
cmd.env(
dioxus_cli_config::DEVSERVER_RAW_ADDR_ENV,
devserver_addr.to_string(),
);
}
cmd.env(dioxus_cli_config::APP_TITLE_ENV, arguments.app_title);
cmd.env(
dioxus_cli_config::DEVSERVER_RAW_ADDR_ENV,
devserver_addr.to_string(),
dioxus_cli_config::OUT_DIR,
arguments.out_dir.display().to_string(),
);
cmd.env(dioxus_cli_config::APP_TITLE_ENV, app_title);
cmd.env(dioxus_cli_config::OUT_DIR, out_dir.display().to_string());
cmd.stderr(Stdio::piped())
.stdout(Stdio::piped())
.kill_on_drop(true)
.current_dir(workspace);
.current_dir(arguments.workspace);
Ok(Some(cmd.spawn()?))
}
+15 -2
View File
@@ -1,6 +1,6 @@
use std::str::FromStr;
use crate::config::Platform;
use crate::{builder::OpenArguments, config::Platform};
use anyhow::Context;
use crate::{
@@ -115,7 +115,20 @@ impl Build {
pub async fn build(&mut self, dioxus_crate: &mut DioxusCrate) -> Result<()> {
self.resolve(dioxus_crate)?;
let build_requests = BuildRequest::create(false, dioxus_crate, self.clone())?;
BuildRequest::build_all_parallel(build_requests).await?;
let builds = BuildRequest::build_all_parallel(build_requests).await?;
// If this is a static generation build, building involves running the server to generate static files
if self.platform.unwrap() == Platform::StaticGeneration {
println!("Building static site...");
for build in builds {
if let Some(mut result) =
build.open(OpenArguments::new_for_static_generation_build(dioxus_crate))?
{
result.wait().await?;
}
}
println!("Static site built!");
}
Ok(())
}
+10 -16
View File
@@ -1,6 +1,7 @@
use std::future::{poll_fn, Future, IntoFuture};
use std::task::Poll;
use crate::builder::OpenArguments;
use crate::cli::serve::Serve;
use crate::dioxus_crate::DioxusCrate;
use crate::tracer::CLILogControl;
@@ -156,23 +157,14 @@ pub async fn serve_all(
builder.children.clear();
}
let asset_dir = dioxus_crate
.dioxus_config
.application
.asset_dir
.canonicalize()
.unwrap_or(std::path::PathBuf::from("./assets"));
// If we have a build result, open it
for build_result in results.iter() {
let child = build_result.open(
OpenArguments::new(
&serve.server_arguments,
server.fullstack_address(),
&dioxus_crate.workspace_dir(),
&asset_dir,
server.ip,
dioxus_crate.dioxus_config.application.name.clone(),
dioxus_crate.out_dir()
&dioxus_crate
)
);
match child {
Ok(Some(child_proc)) => builder.children.push((build_result.target_platform, child_proc)),
@@ -192,12 +184,12 @@ pub async fn serve_all(
server.send_reload_command().await;
},
// If the process exited *cleanly*, we can exit
// If the desktop process exited *cleanly*, we can exit
Ok(BuilderUpdate::ProcessExited { status, target_platform }) => {
// Then remove the child process
builder.children.retain(|(platform, _)| *platform != target_platform);
match status {
Ok(status) => {
match (target_platform, status) {
(TargetPlatform::Desktop, Ok(status)) => {
if status.success() {
break;
}
@@ -205,7 +197,9 @@ pub async fn serve_all(
tracing::error!(dx_src = ?TraceSrc::Dev, "Application exited with status: {status}");
}
},
Err(e) => {
// Ignore the static generation platform exiting
(_ , Ok(_)) => {},
(_, Err(e)) => {
tracing::error!(dx_src = ?TraceSrc::Dev, "Application exited with error: {e}");
}
}
+27 -8
View File
@@ -114,7 +114,7 @@ impl Server {
// If we're serving a fullstack app, we need to find a port to proxy to
let fullstack_port = if matches!(
serve.build_arguments.platform(),
Platform::Liveview | Platform::Fullstack | Platform::StaticGeneration
Platform::Liveview | Platform::Fullstack
) {
get_available_port(addr.ip())
} else {
@@ -368,7 +368,7 @@ fn setup_router(
// server the dir if it's web, otherwise let the fullstack server itself handle it
match platform {
Platform::Web => {
Platform::Web | Platform::StaticGeneration => {
// Route file service to output the .wasm and assets if this is a web build
let base_path = format!(
"/{}",
@@ -382,9 +382,9 @@ fn setup_router(
.trim_matches('/')
);
router = router.nest_service(&base_path, build_serve_dir(serve, config));
router = router.nest_service(&base_path, build_serve_dir(serve, config, platform));
}
Platform::Liveview | Platform::Fullstack | Platform::StaticGeneration => {
Platform::Liveview | Platform::Fullstack => {
// For fullstack and static generation, forward all requests to the server
let address = fullstack_address.unwrap();
@@ -449,7 +449,11 @@ fn setup_router(
Ok(router)
}
fn build_serve_dir(serve: &Serve, cfg: &DioxusCrate) -> axum::routing::MethodRouter {
fn build_serve_dir(
serve: &Serve,
cfg: &DioxusCrate,
platform: Platform,
) -> axum::routing::MethodRouter {
static CORS_UNSAFE: (HeaderValue, HeaderValue) = (
HeaderValue::from_static("unsafe-none"),
HeaderValue::from_static("unsafe-none"),
@@ -465,7 +469,11 @@ fn build_serve_dir(serve: &Serve, cfg: &DioxusCrate) -> axum::routing::MethodRou
false => CORS_UNSAFE.clone(),
};
let out_dir = cfg.out_dir();
let out_dir = match platform {
// Static generation only serves files from the public directory
Platform::StaticGeneration => cfg.out_dir().join("public"),
_ => cfg.out_dir(),
};
let index_on_404 = cfg.dioxus_config.web.watcher.index_on_404;
get_service(
@@ -479,7 +487,7 @@ fn build_serve_dir(serve: &Serve, cfg: &DioxusCrate) -> axum::routing::MethodRou
let out_dir = out_dir.clone();
move |response| async move { Ok(no_cache(index_on_404, &out_dir, response)) }
})
.service(ServeDir::new(out_dir)),
.service(ServeDir::new(&out_dir)),
)
.handle_error(|error: Infallible| async move {
(
@@ -500,7 +508,18 @@ fn no_cache(
// If there's a 404 and we're supposed to index on 404, upgrade that failed request to the index.html
// We might want to isnert a header here saying we *did* that but oh well
if response.status() == StatusCode::NOT_FOUND && index_on_404 {
let body = Body::from(std::fs::read_to_string(out_dir.join("index.html")).unwrap());
// First try to find a 404.html or 404/index.html file
let out_dir_404_html = out_dir.join("404.html");
let out_dir_404_index_html = out_dir.join("404").join("index.html");
let path = if out_dir_404_html.exists() {
out_dir_404_html
} else if out_dir_404_index_html.exists() {
out_dir_404_index_html
} else {
// If we can't find a 404.html or 404/index.html, just use the index.html
out_dir.join("index.html")
};
let body = Body::from(std::fs::read_to_string(path).unwrap());
response = Response::builder()
.status(StatusCode::OK)