Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions examples/07-fullstack/multipart_form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,14 @@ fn app() -> Element {
#[post("/api/upload-multipart")]
async fn upload(mut form: MultipartFormData) -> Result<()> {
while let Ok(Some(field)) = form.next_field().await {
let name = field.name().unwrap_or("<none>").to_string();
let file_name = field.file_name().unwrap_or("<none>").to_string();
let content_type = field.content_type().unwrap_or("<none>").to_string();
let size = field.bytes().await.unwrap().len();

info!(
"Field name: {:?}, filename: {:?}, content_type: {:?}",
field.name().to_owned(),
field.file_name(),
field.content_type(),
"Field name: {:?}, filename: {:?}, content_type: {:?}, size: {:?}",
name, file_name, content_type, size
);
}

Expand Down
1 change: 1 addition & 0 deletions examples/07-fullstack/server_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Option 1:
For simple, synchronous, thread-safe data, we can use statics with atomic types or mutexes.
The `LazyLock` type from the standard library is a great choice for simple, synchronous data
*/
#[cfg(feature = "server")]
static MESSAGES: LazyLock<Mutex<Vec<String>>> = LazyLock::new(|| Mutex::new(Vec::new()));

#[post("/api/messages")]
Expand Down
10 changes: 6 additions & 4 deletions examples/08-apis/form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
//! in the "values" field.

use dioxus::prelude::*;
use std::collections::HashMap;

fn main() {
dioxus::launch(app);
}

fn app() -> Element {
let mut values = use_signal(HashMap::new);
let mut submitted_values = use_signal(HashMap::new);
let mut values = use_signal(Vec::new);
let mut submitted_values = use_signal(Vec::new);

rsx! {
div { style: "display: flex",
Expand Down Expand Up @@ -63,7 +62,7 @@ fn app() -> Element {
input { r#type: "text", name: "full-name" }

label { r#for: "email", "Email (matching <name>@example.com)" }
input { r#type: "email", pattern: ".+@example\\.com", size: "30", required: "true", id: "email", name: "email" }
input { r#type: "email", size: "30", id: "email", name: "email" }

label { r#for: "password", "Password" }
input { r#type: "password", name: "password" }
Expand Down Expand Up @@ -122,6 +121,9 @@ fn app() -> Element {
input { r#type: "checkbox", name: "cbox", value: "yellow", id: "cbox-yellow" }
}

// File input
label { r#for: "headshot", "Headshot" }
input { r#type: "file", name: "headshot", id: "headshot", multiple: true, accept: ".png,.jpg,.jpeg" }

// Buttons will submit your form by default.
button { r#type: "submit", value: "Submit", "Submit the form" }
Expand Down
17 changes: 11 additions & 6 deletions packages/cli/src/build/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?;
Expand Down Expand Up @@ -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;
};

Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand All @@ -351,6 +355,7 @@ impl AppBuilder {
let ctx = BuildContext {
tx: self.tx.clone(),
mode,
build_id,
};
async move { request.build(&ctx).await }
});
Expand Down
11 changes: 9 additions & 2 deletions packages/cli/src/build/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand All @@ -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)]
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/build/pre_render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
21 changes: 10 additions & 11 deletions packages/cli/src/build/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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(());
}

Expand Down Expand Up @@ -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!(
Expand All @@ -1261,7 +1260,6 @@ impl BuildRequest {

Ok(BuildArtifacts {
time_end,
bundle,
exe,
direct_rustc,
time_start,
Expand All @@ -1270,6 +1268,7 @@ impl BuildRequest {
depinfo,
root_dir: self.root_dir(),
patch_cache: None,
build_id: ctx.build_id,
})
}

Expand Down Expand Up @@ -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(());
}

Expand Down Expand Up @@ -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());
}

Expand Down
12 changes: 8 additions & 4 deletions packages/cli/src/cli/build.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use dioxus_dx_wire_format::StructuredBuildArtifacts;

use crate::{
cli::*, Anonymized, AppBuilder, BuildArtifacts, BuildMode, BuildRequest, BundleFormat,
TargetArgs, Workspace,
cli::*, Anonymized, AppBuilder, BuildArtifacts, BuildId, BuildMode, BuildRequest, BundleFormat,
Platform, TargetArgs, Workspace,
};

/// Build the Rust Dioxus app and all of its assets.
Expand Down Expand Up @@ -128,6 +128,10 @@ impl CommandWithPlatformOverrides<BuildArgs> {
BuildRequest::new(&server_args.build_arguments, workspace.clone()).await?,
);
}
None if client_args.platform == Platform::Server => {
// If the user requests a server build with `--server`, then we don't need to build a separate server binary.
// There's no client to use, so even though fullstack is true, we only build the server.
}
None => {
let mut args = self.shared.build_arguments.clone();
args.platform = crate::Platform::Server;
Expand Down Expand Up @@ -171,7 +175,7 @@ impl CommandWithPlatformOverrides<BuildArgs> {
request: &BuildRequest,
mode: BuildMode,
) -> Result<BuildArtifacts> {
AppBuilder::started(request, mode)?
AppBuilder::started(request, mode, BuildId::PRIMARY)?
.finish_build()
.await
.inspect(|_| {
Expand All @@ -189,7 +193,7 @@ impl CommandWithPlatformOverrides<BuildArgs> {
};

// If the server is present, we need to build it as well
let mut server_build = AppBuilder::started(server, mode)?;
let mut server_build = AppBuilder::started(server, mode, BuildId::SECONDARY)?;
let server_artifacts = server_build.finish_build().await?;

// Run SSG and cache static routes
Expand Down
11 changes: 6 additions & 5 deletions packages/cli/src/cli/bundle.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{AppBuilder, BuildArgs, BuildMode, BuildRequest, BundleFormat};
use crate::{AppBuilder, BuildArgs, BuildId, BuildMode, BuildRequest, BundleFormat};
use anyhow::{bail, Context};
use path_absolutize::Absolutize;
use std::collections::HashMap;
Expand Down Expand Up @@ -40,16 +40,17 @@ impl Bundle {
let BuildTargets { client, server } = self.args.into_targets().await?;

let mut server_artifacts = None;
let client_artifacts = AppBuilder::started(&client, BuildMode::Base { run: false })?
.finish_build()
.await?;
let client_artifacts =
AppBuilder::started(&client, BuildMode::Base { run: false }, BuildId::PRIMARY)?
.finish_build()
.await?;

tracing::info!(path = ?client.root_dir(), "Client build completed successfully! 🚀");

if let Some(server) = server.as_ref() {
// If the server is present, we need to build it as well
server_artifacts = Some(
AppBuilder::started(server, BuildMode::Base { run: false })?
AppBuilder::started(server, BuildMode::Base { run: false }, BuildId::SECONDARY)?
.finish_build()
.await?,
);
Expand Down
16 changes: 13 additions & 3 deletions packages/cli/src/cli/hotpatch.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
platform_override::CommandWithPlatformOverrides, AppBuilder, BuildArgs, BuildMode,
platform_override::CommandWithPlatformOverrides, AppBuilder, BuildArgs, BuildId, BuildMode,
HotpatchModuleCache, Result, StructuredOutput,
};
use anyhow::Context;
Expand Down Expand Up @@ -34,8 +34,16 @@ impl HotpatchTip {
pub async fn run(self) -> Result<StructuredOutput> {
let targets = self.build_args.into_targets().await?;

let patch_server = self.patch_server.unwrap_or(false);

let build_id = if patch_server {
BuildId::SECONDARY
} else {
BuildId::PRIMARY
};

// Select which target to patch
let request = if self.patch_server.unwrap_or_default() {
let request = if patch_server {
targets.server.as_ref().context("No server to patch!")?
} else {
&targets.client
Expand Down Expand Up @@ -68,7 +76,9 @@ impl HotpatchTip {
cache: cache.clone(),
};

let artifacts = AppBuilder::started(request, mode)?.finish_build().await?;
let artifacts = AppBuilder::started(request, mode, build_id)?
.finish_build()
.await?;
let patch_exe = request.patch_exe(artifacts.time_start);

Ok(StructuredOutput::Hotpatch {
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/serve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,12 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &TraceController) -> Resu
pid,
} => {
devserver
.send_hotreload(builder.applied_hot_reload_changes(BuildId::CLIENT))
.send_hotreload(builder.applied_hot_reload_changes(BuildId::PRIMARY))
.await;

if builder.server.is_some() {
devserver
.send_hotreload(builder.applied_hot_reload_changes(BuildId::SERVER))
.send_hotreload(builder.applied_hot_reload_changes(BuildId::SECONDARY))
.await;
}

Expand Down
Loading
Loading