Make desktop fullstack work with the CLI (#2862)

* Make desktop fullstack work with the CLI
* Simplify desktop fullstack example
* move the profiles to the workspace
This commit is contained in:
Evan Almloff authored and GitHub committed 2024-08-20 14:57:51 -07:00
1 parent 6a46a66c9a
commit 5a7a91323a
23 files changed
+239 -259

No files matched your search

+2 -1
View File
@@ -231,7 +231,7 @@ impl BuildRequest {
Ok(Some(assets))
})
.await
.unwrap()
.map_err(|e| anyhow::anyhow!(e))?
}
pub fn copy_assets_dir(&self) -> anyhow::Result<()> {
@@ -257,6 +257,7 @@ impl BuildRequest {
match self.build_arguments.platform {
Some(Platform::Fullstack | Platform::StaticGeneration) => match self.target_platform {
TargetPlatform::Web => out_dir.join("public"),
TargetPlatform::Desktop => out_dir.join("desktop"),
_ => out_dir,
},
_ => out_dir,
+66 -43
View File
@@ -1,41 +1,60 @@
use toml_edit::Item;
use crate::builder::Build;
use crate::dioxus_crate::DioxusCrate;
use crate::builder::BuildRequest;
use std::path::PathBuf;
use std::io::Write;
use super::TargetPlatform;
static CLIENT_RUST_FLAGS: &[&str] = &["-Cdebuginfo=none", "-Cstrip=debuginfo"];
static CLIENT_PROFILE: &str = "dioxus-client";
static SERVER_PROFILE: &str = "dioxus-server";
// The `opt-level=2` increases build times, but can noticeably decrease time
// between saving changes and being able to interact with an app. The "overall"
// time difference (between having and not having the optimization) can be
// almost imperceptible (~1 s) but also can be very noticeable (~6 s) — depends
// on setup (hardware, OS, browser, idle load).
static SERVER_RUST_FLAGS: &[&str] = &["-O"];
static DEBUG_RUST_FLAG: &str = "-Cdebug-assertions";
// Find or create the client and server profiles in the .cargo/config.toml file
fn initialize_profiles(config: &DioxusCrate) -> crate::Result<()> {
let config_path = config.workspace_dir().join(".cargo/config.toml");
let mut config = match std::fs::read_to_string(&config_path) {
Ok(config) => config.parse::<toml_edit::DocumentMut>().map_err(|e| {
crate::Error::Other(anyhow::anyhow!("Failed to parse .cargo/config.toml: {}", e))
})?,
Err(_) => Default::default(),
};
fn add_debug_rust_flags(build: &Build, flags: &mut Vec<String>) {
if !build.release {
flags.push(DEBUG_RUST_FLAG.to_string());
}
}
if let Item::Table(table) = config
.as_table_mut()
.entry("profile")
.or_insert(Item::Table(Default::default()))
{
if let toml_edit::Entry::Vacant(entry) = table.entry(CLIENT_PROFILE) {
let mut client = toml_edit::Table::new();
client.insert("inherits", Item::Value("dev".into()));
client.insert("opt-level", Item::Value(2.into()));
entry.insert(Item::Table(client));
}
fn fullstack_rust_flags(build: &Build, base_flags: &[&str]) -> Vec<String> {
// If we are forcing debug mode, don't add any debug flags
if build.force_debug {
return Default::default();
if let toml_edit::Entry::Vacant(entry) = table.entry(SERVER_PROFILE) {
let mut server = toml_edit::Table::new();
server.insert("inherits", Item::Value("dev".into()));
server.insert("opt-level", Item::Value(2.into()));
entry.insert(Item::Table(server));
}
}
let mut rust_flags = base_flags.iter().map(ToString::to_string).collect();
add_debug_rust_flags(build, &mut rust_flags);
rust_flags
}
// Write the config back to the file
if let Some(parent) = config_path.parent() {
std::fs::create_dir_all(parent)?;
}
let file = std::fs::File::create(config_path)?;
let mut buf_writer = std::io::BufWriter::new(file);
write!(buf_writer, "{}", config)?;
// Fullstack builds run the server and client builds parallel by default
// To make them run in parallel, we need to set up different target directories for the server and client within /.dioxus
fn get_target_directory(build: &Build, target: PathBuf) -> Option<PathBuf> {
(!build.force_sequential).then_some(target)
Ok(())
}
impl BuildRequest {
@@ -43,63 +62,67 @@ impl BuildRequest {
config: DioxusCrate,
build_arguments: Build,
serve: bool,
) -> Vec<Self> {
vec![
) -> Result<Vec<Self>, crate::Error> {
initialize_profiles(&config)?;
Ok(vec![
Self::new_client(serve, &config, &build_arguments),
Self::new_server(serve, &config, &build_arguments),
]
])
}
fn new_with_target_directory_rust_flags_and_features(
serve: bool,
config: &DioxusCrate,
build: &Build,
target_directory: PathBuf,
rust_flags: &[&str],
feature: String,
feature: Option<String>,
target_platform: TargetPlatform,
) -> Self {
let config = config.clone();
let mut build = build.clone();
// Set the target directory we are building the server in
let target_dir = get_target_directory(&build, target_directory);
// Add the server feature to the features we pass to the build
build.target_args.features.push(feature);
if let Some(feature) = feature {
build.target_args.features.push(feature);
}
// Add the server flags to the build arguments
let rust_flags = fullstack_rust_flags(&build, rust_flags);
Self {
serve,
build_arguments: build.clone(),
dioxus_crate: config,
rust_flags,
target_dir,
rust_flags: Default::default(),
target_dir: None,
target_platform,
}
}
fn new_server(serve: bool, config: &DioxusCrate, build: &Build) -> Self {
let mut build = build.clone();
if build.profile.is_none() {
build.profile = Some(CLIENT_PROFILE.to_string());
}
let client_feature = build.auto_detect_server_feature(config);
Self::new_with_target_directory_rust_flags_and_features(
serve,
config,
build,
config.server_target_dir(),
SERVER_RUST_FLAGS,
build.target_args.server_feature.clone(),
&build,
build.target_args.server_feature.clone().or(client_feature),
TargetPlatform::Server,
)
}
fn new_client(serve: bool, config: &DioxusCrate, build: &Build) -> Self {
let mut build = build.clone();
if build.profile.is_none() {
build.profile = Some(SERVER_PROFILE.to_string());
}
let (client_feature, client_platform) = build.auto_detect_client_platform(config);
Self::new_with_target_directory_rust_flags_and_features(
serve,
config,
build,
config.client_target_dir(),
CLIENT_RUST_FLAGS,
build.target_args.client_feature.clone(),
TargetPlatform::Web,
&build,
build.target_args.client_feature.clone().or(client_feature),
client_platform,
)
}
}
+20 -5
View File
@@ -6,6 +6,7 @@ use dioxus_cli_config::{Platform, RuntimeCLIArguments};
use futures_util::stream::select_all;
use futures_util::StreamExt;
use std::net::SocketAddr;
use std::str::FromStr;
use std::{path::PathBuf, process::Stdio};
use tokio::process::{Child, Command};
@@ -29,6 +30,20 @@ pub enum TargetPlatform {
Liveview,
}
impl FromStr for TargetPlatform {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"web" => Ok(Self::Web),
"desktop" => Ok(Self::Desktop),
"axum" | "server" => Ok(Self::Server),
"liveview" => Ok(Self::Liveview),
_ => Err(()),
}
}
}
impl std::fmt::Display for TargetPlatform {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@@ -62,7 +77,7 @@ impl BuildRequest {
serve: bool,
dioxus_crate: &DioxusCrate,
build_arguments: impl Into<Build>,
) -> Vec<Self> {
) -> crate::Result<Vec<Self>> {
let build_arguments = build_arguments.into();
let platform = build_arguments.platform();
let single_platform = |platform| {
@@ -76,15 +91,15 @@ impl BuildRequest {
target_dir: Default::default(),
}]
};
match platform {
Platform::Web => single_platform(TargetPlatform::Web),
Ok(match platform {
Platform::Liveview => single_platform(TargetPlatform::Liveview),
Platform::Web => single_platform(TargetPlatform::Web),
Platform::Desktop => single_platform(TargetPlatform::Desktop),
Platform::StaticGeneration | Platform::Fullstack => {
Self::new_fullstack(dioxus_crate.clone(), build_arguments, serve)
Self::new_fullstack(dioxus_crate.clone(), build_arguments, serve)?
}
_ => unimplemented!("Unknown platform: {platform:?}"),
}
})
}
pub(crate) async fn build_all_parallel(
+75 -9
View File
@@ -1,7 +1,12 @@
use std::str::FromStr;
use anyhow::Context;
use dioxus_cli_config::Platform;
use crate::{builder::BuildRequest, dioxus_crate::DioxusCrate};
use crate::{
builder::{BuildRequest, TargetPlatform},
dioxus_crate::DioxusCrate,
};
use super::*;
@@ -29,12 +34,12 @@ pub struct TargetArgs {
pub features: Vec<String>,
/// The feature to use for the client in a fullstack app [default: "web"]
#[clap(long, default_value_t = { "web".to_string() })]
pub client_feature: String,
#[clap(long)]
pub client_feature: Option<String>,
/// The feature to use for the server in a fullstack app [default: "server"]
#[clap(long, default_value_t = { "server".to_string() })]
pub server_feature: String,
#[clap(long)]
pub server_feature: Option<String>,
/// Rustc platform triple
#[clap(long)]
@@ -109,7 +114,7 @@ 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());
let build_requests = BuildRequest::create(false, dioxus_crate, self.clone())?;
BuildRequest::build_all_parallel(build_requests).await?;
Ok(())
}
@@ -121,7 +126,47 @@ impl Build {
Ok(())
}
pub(crate) fn auto_detect_client_platform(
&self,
resolved: &DioxusCrate,
) -> (Option<String>, TargetPlatform) {
self.find_dioxus_feature(resolved, |platform| {
matches!(platform, TargetPlatform::Web | TargetPlatform::Desktop)
})
.unwrap_or_else(|| (Some("web".to_string()), TargetPlatform::Web))
}
pub(crate) fn auto_detect_server_feature(&self, resolved: &DioxusCrate) -> Option<String> {
self.find_dioxus_feature(resolved, |platform| {
matches!(platform, TargetPlatform::Server)
})
.map(|(feature, _)| feature)
.unwrap_or_else(|| Some("server".to_string()))
}
fn auto_detect_platform(&self, resolved: &DioxusCrate) -> Platform {
self.auto_detect_platform_with_filter(resolved, |_| true).1
}
fn auto_detect_platform_with_filter(
&self,
resolved: &DioxusCrate,
filter_platform: fn(&Platform) -> bool,
) -> (Option<String>, Platform) {
self.find_dioxus_feature(resolved, filter_platform)
.unwrap_or_else(|| {
let default_platform = resolved.dioxus_config.application.default_platform;
(Some(default_platform.to_string()), default_platform)
})
}
fn find_dioxus_feature<P: FromStr>(
&self,
resolved: &DioxusCrate,
filter_platform: fn(&P) -> bool,
) -> Option<(Option<String>, P)> {
// First check the enabled features for any renderer enabled
for dioxus in resolved.krates.krates_by_name("dioxus") {
let Some(features) = resolved.krates.get_enabled_features(dioxus.kid) else {
continue;
@@ -129,13 +174,34 @@ impl Build {
if let Some(platform) = features
.iter()
.find_map(|platform| platform.parse::<Platform>().ok())
.find_map(|platform| platform.parse::<P>().ok())
.filter(filter_platform)
{
return platform;
return Some((None, platform));
}
}
resolved.dioxus_config.application.default_platform
// Then check the features that might get enabled
if let Some(platform) = resolved
.package()
.features
.iter()
.find_map(|(feature, enables)| {
enables
.iter()
.find_map(|f| {
f.strip_prefix("dioxus/")
.or_else(|| feature.strip_prefix("dep:dioxus/"))
.and_then(|f| f.parse::<P>().ok())
.filter(filter_platform)
})
.map(|platform| (Some(feature.clone()), platform))
})
{
return Some(platform);
}
None
}
/// Get the platform from the build arguments
-6
View File
@@ -29,12 +29,6 @@ impl Clean {
remove_dir_all(out_dir)?;
}
let fullstack_out_dir = dioxus_crate.fullstack_out_dir();
if fullstack_out_dir.is_dir() {
remove_dir_all(fullstack_out_dir)?;
}
Ok(())
}
}
-16
View File
@@ -192,22 +192,6 @@ impl DioxusCrate {
.join(&self.dioxus_config.application.out_dir)
}
/// Compose an out directory for the fullstack platform. See `out_dir()`
/// method.
pub fn fullstack_out_dir(&self) -> PathBuf {
self.workspace_dir().join(".dioxus")
}
/// Compose a target directory for the server (fullstack-only?).
pub fn server_target_dir(&self) -> PathBuf {
self.fullstack_out_dir().join("ssr")
}
/// Compose a target directory for the client (fullstack-only?).
pub fn client_target_dir(&self) -> PathBuf {
self.fullstack_out_dir().join("web")
}
/// Get the workspace directory for the crate
pub fn workspace_dir(&self) -> PathBuf {
self.krates.workspace_root().as_std_path().to_path_buf()
+4 -2
View File
@@ -49,10 +49,10 @@ impl Builder {
}
/// Start a new build - killing the current one if it exists
pub fn build(&mut self) {
pub fn build(&mut self) -> Result<()> {
self.shutdown();
let build_requests =
BuildRequest::create(true, &self.config, self.serve.build_arguments.clone());
BuildRequest::create(true, &self.config, self.serve.build_arguments.clone())?;
let mut set = tokio::task::JoinSet::new();
@@ -85,6 +85,8 @@ impl Builder {
}
Ok(all_results)
}));
Ok(())
}
/// Wait for any new updates to the builder - either it completed or gave us a message etc
+3 -3
View File
@@ -57,7 +57,7 @@ pub async fn serve_all(
let mut builder = Builder::new(&dioxus_crate, &serve);
// Start the first build
builder.build();
builder.build()?;
let mut server = Server::start(&serve, &dioxus_crate);
let mut watcher = Watcher::start(&serve, &dioxus_crate);
@@ -94,7 +94,7 @@ pub async fn serve_all(
} else {
// If the change is not binary patchable, rebuild the project
// We're going to kick off a new build, interrupting the current build if it's ongoing
builder.build();
builder.build()?;
// Clear the hot reload changes
watcher.clear_hot_reload_changes();
@@ -196,7 +196,7 @@ pub async fn serve_all(
Ok(false) => {}
// Request a rebuild.
Ok(true) => {
builder.build();
builder.build()?;
server.start_build().await
},
// Shutdown the server.
-1
View File
@@ -57,7 +57,6 @@ impl Watcher {
"target",
"node_modules",
"dist",
".dioxus",
&out_dir_str,
];
for path in excluded_paths {