More fullstack fixes (#4737)

* allow `--server` only builds!

* rollback example

* capture `/:route` routes

* fix: properly implement file uploads in forms

* implement desktop form uploads

* Performant serialization of form bytes

* finish up file stuff

* cleanups

* add request headers to streaming context

* remove some unwraps
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2025-10-07 23:07:43 -07:00
1 parent fc01a002fd
commit 92792aded9
47 files changed
+1241 -703

No files matched your search

+11 -6
View File
@@ -154,19 +154,20 @@ impl AppBuilder {
}
/// Create a new `AppBuilder` and immediately start a build process.
pub fn started(request: &BuildRequest, mode: BuildMode) -> Result<Self> {
pub fn started(request: &BuildRequest, mode: BuildMode, build_id: BuildId) -> Result<Self> {
let mut builder = Self::new(request)?;
builder.start(mode);
builder.start(mode, build_id);
Ok(builder)
}
pub(crate) fn start(&mut self, mode: BuildMode) {
pub(crate) fn start(&mut self, mode: BuildMode, build_id: BuildId) {
self.build_task = tokio::spawn({
let request = self.build.clone();
let tx = self.tx.clone();
async move {
let ctx = BuildContext {
mode,
build_id,
tx: tx.clone(),
};
request.verify_tooling(&ctx).await?;
@@ -290,10 +291,12 @@ impl AppBuilder {
update
}
pub(crate) fn patch_rebuild(&mut self, changed_files: Vec<PathBuf>) {
pub(crate) fn patch_rebuild(&mut self, changed_files: Vec<PathBuf>, build_id: BuildId) {
// We need the rustc args from the original build to pass to the new build
let Some(artifacts) = self.artifacts.as_ref().cloned() else {
tracing::warn!("Ignoring patch rebuild since there is no existing build.");
tracing::warn!(
"Ignoring patch rebuild for {build_id:?} since there is no existing build."
);
return;
};
@@ -327,6 +330,7 @@ impl AppBuilder {
self.build_task = tokio::spawn({
let request = self.build.clone();
let ctx = BuildContext {
build_id,
tx: self.tx.clone(),
mode: BuildMode::Thin {
changed_files,
@@ -340,7 +344,7 @@ impl AppBuilder {
}
/// Restart this builder with new build arguments.
pub(crate) fn start_rebuild(&mut self, mode: BuildMode) {
pub(crate) fn start_rebuild(&mut self, mode: BuildMode, build_id: BuildId) {
// Abort all the ongoing builds, cleaning up any loose artifacts and waiting to cleanly exit
// And then start a new build, resetting our progress/stage to the beginning and replacing the old tokio task
self.abort_all(BuildStage::Restarting);
@@ -351,6 +355,7 @@ impl AppBuilder {
let ctx = BuildContext {
tx: self.tx.clone(),
mode,
build_id,
};
async move { request.build(&ctx).await }
});
+9 -2
View File
@@ -16,6 +16,7 @@ use std::{path::PathBuf, process::ExitStatus};
pub struct BuildContext {
pub tx: ProgressTx,
pub mode: BuildMode,
pub build_id: BuildId,
}
pub type ProgressTx = UnboundedSender<BuilderUpdate>;
@@ -24,8 +25,8 @@ pub type ProgressRx = UnboundedReceiver<BuilderUpdate>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub struct BuildId(pub(crate) usize);
impl BuildId {
pub const CLIENT: Self = Self(0);
pub const SERVER: Self = Self(1);
pub const PRIMARY: Self = Self(0);
pub const SECONDARY: Self = Self(1);
}
#[allow(clippy::large_enum_variant)]
@@ -78,6 +79,12 @@ pub enum BuilderUpdate {
}
impl BuildContext {
/// Returns true if this is a client build - basically, is this the primary build?
/// We try not to duplicate work between client and server builds, like asset copying.
pub(crate) fn is_primary_build(&self) -> bool {
self.build_id == BuildId::PRIMARY
}
pub(crate) fn status_wasm_bindgen_start(&self) {
_ = self.tx.unbounded_send(BuilderUpdate::Progress {
stage: BuildStage::RunningBindgen,
+1 -1
View File
@@ -45,7 +45,7 @@ pub(crate) async fn pre_render_static_routes(
devserver_ip,
Some(fullstack_address),
false,
BuildId::SERVER,
BuildId::SECONDARY,
);
// Run the server executable
let _child = Command::new(&server_exe)
+10 -11
View File
@@ -320,9 +320,9 @@
//! - xbuild: <https://github.com/rust-mobile/xbuild/blob/master/xbuild/src/command/build.rs>
use crate::{
AndroidTools, BuildContext, BundleFormat, DioxusConfig, Error, LinkAction, LinkerFlavor,
Platform, Renderer, Result, RustcArgs, TargetArgs, TraceSrc, WasmBindgen, WasmOptConfig,
Workspace, DX_RUSTC_WRAPPER_ENV_VAR,
AndroidTools, BuildContext, BuildId, BundleFormat, DioxusConfig, Error, LinkAction,
LinkerFlavor, Platform, Renderer, Result, RustcArgs, TargetArgs, TraceSrc, WasmBindgen,
WasmOptConfig, Workspace, DX_RUSTC_WRAPPER_ENV_VAR,
};
use anyhow::{bail, Context};
use cargo_metadata::diagnostic::Diagnostic;
@@ -445,7 +445,6 @@ pub enum BuildMode {
#[derive(Clone, Debug)]
pub struct BuildArtifacts {
pub(crate) root_dir: PathBuf,
pub(crate) bundle: BundleFormat,
pub(crate) exe: PathBuf,
pub(crate) direct_rustc: RustcArgs,
pub(crate) time_start: SystemTime,
@@ -454,6 +453,7 @@ pub struct BuildArtifacts {
pub(crate) mode: BuildMode,
pub(crate) patch_cache: Option<Arc<HotpatchModuleCache>>,
pub(crate) depinfo: RustcDepInfo,
pub(crate) build_id: BuildId,
}
impl BuildRequest {
@@ -989,10 +989,10 @@ impl BuildRequest {
_ = std::fs::File::create_new(self.windows_command_file());
if !matches!(ctx.mode, BuildMode::Thin { .. }) {
self.prepare_build_dir()?;
self.prepare_build_dir(ctx)?;
}
if self.bundle == BundleFormat::Server {
if !ctx.is_primary_build() {
return Ok(());
}
@@ -1250,7 +1250,6 @@ impl BuildRequest {
let assets = self.collect_assets(&exe, ctx).await?;
let time_end = SystemTime::now();
let mode = ctx.mode.clone();
let bundle = self.bundle;
let depinfo = RustcDepInfo::from_file(&exe.with_extension("d")).unwrap_or_default();
tracing::debug!(
@@ -1261,7 +1260,6 @@ impl BuildRequest {
Ok(BuildArtifacts {
time_end,
bundle,
exe,
direct_rustc,
time_start,
@@ -1270,6 +1268,7 @@ impl BuildRequest {
depinfo,
root_dir: self.root_dir(),
patch_cache: None,
build_id: ctx.build_id,
})
}
@@ -1460,7 +1459,7 @@ impl BuildRequest {
/// Should be the same on all platforms - just copy over the assets from the manifest into the output directory
async fn write_assets(&self, ctx: &BuildContext, assets: &AssetManifest) -> Result<()> {
// Server doesn't need assets - web will provide them
if self.bundle == BundleFormat::Server {
if !ctx.is_primary_build() {
return Ok(());
}
@@ -4320,14 +4319,14 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{
/// - extra scaffolding
///
/// It's not guaranteed that they're different from any other folder
pub(crate) fn prepare_build_dir(&self) -> Result<()> {
pub(crate) fn prepare_build_dir(&self, ctx: &BuildContext) -> Result<()> {
use std::fs::{create_dir_all, remove_dir_all};
use std::sync::OnceLock;
static INITIALIZED: OnceLock<Result<()>> = OnceLock::new();
let success = INITIALIZED.get_or_init(|| {
if self.bundle != BundleFormat::Server {
if ctx.is_primary_build() {
_ = remove_dir_all(self.exe_dir());
}