1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
use dioxus_dx_wire_format::StructuredBuildArtifacts;
use crate::{
cli::*, Anonymized, AppBuilder, BuildArtifacts, BuildId, BuildMode, BuildRequest, BundleFormat,
Platform, TargetArgs, Workspace,
};
/// Build the Rust Dioxus app and all of its assets.
///
/// Produces a final output build. If a "server" feature is present in the package's Cargo.toml, it will
/// be considered a fullstack app and the server will be built as well.
#[derive(Clone, Debug, Default, Parser)]
pub struct BuildArgs {
/// Enable fullstack mode [default: false]
///
/// This is automatically detected from `dx serve` if the "fullstack" feature is enabled by default.
#[arg(
long,
default_missing_value = "true",
num_args = 0..=1,
)]
pub(crate) fullstack: Option<bool>,
/// Pre-render all routes returned from the app's `/static_routes` endpoint [default: false]
#[clap(long)]
pub(crate) ssg: bool,
/// Force a "fat" binary, required to use `dx build-tools hotpatch`
#[clap(long)]
pub(crate) fat_binary: bool,
/// This flag only applies to fullstack builds. By default fullstack builds will run the server
/// and client builds in parallel. This flag will force the build to run the server build first, then the client build. [default: false]
///
/// If CI is enabled, this will be set to true by default.
///
#[clap(
long, default_missing_value = "true",
num_args = 0..=1,
)]
pub(crate) force_sequential: Option<bool>,
/// Arguments for the build itself
#[clap(flatten)]
pub(crate) build_arguments: TargetArgs,
}
impl BuildArgs {
pub(crate) fn force_sequential_build(&self) -> bool {
self.force_sequential
.unwrap_or_else(|| std::env::var("CI").is_ok())
}
}
impl Anonymized for BuildArgs {
fn anonymized(&self) -> Value {
json! {{
"fullstack": self.fullstack,
"ssg": self.ssg,
"build_arguments": self.build_arguments.anonymized(),
}}
}
}
pub struct BuildTargets {
pub client: BuildRequest,
pub server: Option<BuildRequest>,
}
impl CommandWithPlatformOverrides<BuildArgs> {
/// We need to decompose the combined `BuildArgs` into the individual targets that we need to build.
///
/// Only in a few cases do we spin out an additional server binary:
/// - the fullstack feature is passed
/// - the fullstack flag is enabled
/// - the server flag is enabled
///
/// The buildtargets configuration comes in two flavors:
/// - implied via the `fullstack` feature
/// - explicit when using `@server and @client`
///
/// We use the client arguments to build the client target, and then make a few changes to make
/// the server target.
///
/// The `--fullstack` feature is basically the same as passing `--features fullstack`
///
/// Some examples:
/// ```shell, ignore
/// dx serve --target wasm32-unknown-unknown --fullstack # serves both client and server
/// dx serve --target wasm32-unknown-unknown --features fullstack # serves both client and server
/// dx serve --target wasm32-unknown-unknown # only serves the client
/// dx serve --target wasm32-unknown-unknown # servers both if `fullstack` is enabled on dioxus
/// dx serve @client --target wasm32-unknown-unknown # only serves the client
/// dx serve @client --target wasm32-unknown-unknown --fullstack # serves both client and server
/// ```
///
/// Currently it is not possible to serve the server without the client, but this could be added in the future.
pub async fn into_targets(mut self) -> Result<BuildTargets> {
let workspace = Workspace::current().await?;
// do some logging to ensure dx matches the dioxus version since we're not always API compatible
workspace.check_dioxus_version_against_cli();
// The client args are the `@client` arguments, or the shared build arguments if @client is not specified.
let client_args = &self.client.as_ref().unwrap_or(&self.shared).build_arguments;
// Create the client build request
let client = BuildRequest::new(client_args, workspace.clone()).await?;
// Create the server build request if needed
let mut server = None;
if matches!(self.shared.fullstack, Some(true))
|| client.fullstack_feature_enabled()
|| self.server.is_some()
{
match self.server.as_mut() {
Some(server_args) => {
// Make sure we set the client target here so @server knows to place its output into the @client target directory.
server_args.build_arguments.client_target = Some(client.main_target.clone());
// We don't override anything except the bundle format since @server usually implies a server output
server_args.build_arguments.bundle = server_args
.build_arguments
.bundle
.or(Some(BundleFormat::Server));
server = Some(
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;
args.renderer = Some(crate::Renderer::Server);
args.bundle = Some(crate::BundleFormat::Server);
args.target = Some(target_lexicon::Triple::host());
server = Some(BuildRequest::new(&args, workspace.clone()).await?);
}
}
}
Ok(BuildTargets { client, server })
}
pub async fn build(self) -> Result<StructuredOutput> {
tracing::info!("Building project...");
let force_sequential = self.shared.force_sequential_build();
let ssg = self.shared.ssg;
let mode = match self.shared.fat_binary {
true => BuildMode::Fat,
false => BuildMode::Base { run: false },
};
let targets = self.into_targets().await?;
let build_client = Self::build_client_inner(&targets.client, mode.clone());
let build_server = Self::build_server_inner(&targets.server, mode.clone(), ssg);
let (client, server) = match force_sequential {
true => (build_client.await, build_server.await),
false => tokio::join!(build_client, build_server),
};
Ok(StructuredOutput::BuildsFinished {
client: client?.into_structured_output(),
server: server?.map(|s| s.into_structured_output()),
})
}
pub(crate) async fn build_client_inner(
request: &BuildRequest,
mode: BuildMode,
) -> Result<BuildArtifacts> {
AppBuilder::started(request, mode, BuildId::PRIMARY)?
.finish_build()
.await
.inspect(|_| {
tracing::info!(path = ?request.root_dir(), "Client build completed successfully! 🚀");
})
}
pub(crate) async fn build_server_inner(
request: &Option<BuildRequest>,
mode: BuildMode,
ssg: bool,
) -> Result<Option<BuildArtifacts>> {
let Some(server) = request.as_ref() else {
return Ok(None);
};
// If the server is present, we need to build it as well
let mut server_build = AppBuilder::started(server, mode, BuildId::SECONDARY)?;
let server_artifacts = server_build.finish_build().await?;
// Run SSG and cache static routes
if ssg {
server_build.pre_render_static_routes(None, None).await?;
}
tracing::info!(path = ?server.root_dir(), "Server build completed successfully! 🚀");
Ok(Some(server_artifacts))
}
}
impl BuildArtifacts {
pub(crate) fn into_structured_output(self) -> StructuredBuildArtifacts {
// Extract the tip crate's args for the structured output.
// The tip crate is identified by replacing hyphens with underscores in the target name,
// but since we don't have the BuildRequest here, we look for the entry with link_args
// (only the tip crate has link_args attached) or fall back to any entry.
let (rustc_args, rustc_envs) = self
.workspace_rustc
.rustc_args
.iter()
.find(|(k, _v)| k.ends_with(".bin"))
.map(|f| (f.1.args.clone(), f.1.envs.clone()))
.unwrap_or_default();
StructuredBuildArtifacts {
path: self.root_dir,
exe: self.exe,
rustc_args,
rustc_envs,
link_args: self.workspace_rustc.link_args,
assets: self.assets.unique_assets().cloned().collect(),
}
}
}