Skip to content

Commit c91a3a0

Browse files
authored
De-duplicate web build code (#231)
# Objective Closes #229. The `bevy run web` command cannot just wrap `cargo run`, as we don't want to run a native application, but run the code in the user's browser. Hence, we have to manually build with `cargo build` instead. This causes quite a lot of code (also including calling `wasm-bindgen`) to be duplicated between `bevy build web` and `bevy run web`. To ensure consistency and aid development, the code should be shared where possible. This will enable us to build more complex features like #226. # Solution - Extract reusable part of `bevy build web` into dedicated function. - Add a conversion from run args to build args. - Call the `bevy build web` logic in `bevy run web` as well. The functionality _should_ be equivalent. In the future, we might refine how exactly the shared build function is called. For example, I assume that we will pass the binary targets we want to build from the outside instead of determining it in the function itself, that will enable us to implement changes like #200 and #230.
1 parent 6702a64 commit c91a3a0

File tree

7 files changed

+116
-110
lines changed

7 files changed

+116
-110
lines changed

src/bin/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use anyhow::Result;
2-
use bevy_cli::{build::BuildArgs, run::RunArgs};
2+
use bevy_cli::{build::args::BuildArgs, run::RunArgs};
33
use clap::{Args, Parser, Subcommand};
44

55
fn main() -> Result<()> {

src/build/mod.rs

Lines changed: 57 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use args::BuildSubcommands;
1+
use anyhow::{bail, Context as _};
2+
use args::{BuildArgs, BuildSubcommands};
23

34
use crate::{
45
external_cli::{cargo, rustup, wasm_bindgen, CommandHelpers},
@@ -9,51 +10,73 @@ use crate::{
910
},
1011
};
1112

12-
pub use self::args::BuildArgs;
13-
14-
mod args;
13+
pub mod args;
1514

1615
pub fn build(args: &BuildArgs) -> anyhow::Result<()> {
17-
let mut cargo_args = args.cargo_args_builder();
16+
if args.is_web() {
17+
build_web(args)?;
18+
} else {
19+
let cargo_args = args.cargo_args_builder();
20+
cargo::build::command().args(cargo_args).ensure_status()?;
21+
}
1822

19-
if let Some(BuildSubcommands::Web(web_args)) = &args.subcommand {
20-
ensure_web_setup(args.skip_prompts)?;
23+
Ok(())
24+
}
2125

22-
let metadata = cargo::metadata::metadata_with_args(["--no-deps"])?;
23-
let bin_target = select_run_binary(
24-
&metadata,
25-
args.cargo_args.package_args.package.as_deref(),
26-
args.cargo_args.target_args.bin.as_deref(),
27-
args.cargo_args.target_args.example.as_deref(),
28-
args.target().as_deref(),
29-
args.profile(),
30-
)?;
26+
/// Build the Bevy app for use in the browser.
27+
///
28+
/// The following steps will be performed:
29+
/// - Installing required tooling
30+
/// - Setting up default web compilation profiles
31+
/// - Compiling to Wasm
32+
/// - Optimizing the Wasm binary (in release mode)
33+
/// - Creating JavaScript bindings
34+
/// - Creating a bundled folder (if requested)
35+
pub fn build_web(args: &BuildArgs) -> anyhow::Result<WebBundle> {
36+
let Some(BuildSubcommands::Web(web_args)) = &args.subcommand else {
37+
bail!("tried to build for the web without matching arguments");
38+
};
39+
40+
ensure_web_setup(args.skip_prompts)?;
41+
42+
let metadata = cargo::metadata::metadata_with_args(["--no-deps"])?;
43+
let bin_target = select_run_binary(
44+
&metadata,
45+
args.cargo_args.package_args.package.as_deref(),
46+
args.cargo_args.target_args.bin.as_deref(),
47+
args.cargo_args.target_args.example.as_deref(),
48+
args.target().as_deref(),
49+
args.profile(),
50+
)?;
3151

32-
cargo_args = cargo_args.append(configure_default_web_profiles(&metadata)?);
52+
let cargo_args = args
53+
.cargo_args_builder()
54+
.append(configure_default_web_profiles(&metadata)?);
3355

34-
println!("Compiling to WebAssembly...");
35-
cargo::build::command().args(cargo_args).ensure_status()?;
56+
println!("Compiling to WebAssembly...");
57+
cargo::build::command().args(cargo_args).ensure_status()?;
3658

37-
println!("Bundling JavaScript bindings...");
38-
wasm_bindgen::bundle(&bin_target)?;
59+
println!("Bundling JavaScript bindings...");
60+
wasm_bindgen::bundle(&bin_target)?;
3961

40-
#[cfg(feature = "wasm-opt")]
41-
if args.is_release() {
42-
crate::web::wasm_opt::optimize_bin(&bin_target)?;
43-
}
62+
#[cfg(feature = "wasm-opt")]
63+
if args.is_release() {
64+
crate::web::wasm_opt::optimize_bin(&bin_target)?;
65+
}
4466

45-
if web_args.create_packed_bundle {
46-
let web_bundle = create_web_bundle(&metadata, args.profile(), bin_target, true)?;
67+
let web_bundle = create_web_bundle(
68+
&metadata,
69+
args.profile(),
70+
&bin_target,
71+
web_args.create_packed_bundle,
72+
)
73+
.context("Failed to create web bundle")?;
4774

48-
if let WebBundle::Packed(PackedBundle { path }) = &web_bundle {
49-
println!("Created bundle at file://{}", path.display());
50-
}
51-
}
52-
} else {
53-
cargo::build::command().args(cargo_args).ensure_status()?;
75+
if let WebBundle::Packed(PackedBundle { path }) = &web_bundle {
76+
println!("Created bundle at file://{}", path.display());
5477
}
5578

56-
Ok(())
79+
Ok(web_bundle)
5780
}
5881

5982
pub(crate) fn ensure_web_setup(skip_prompts: bool) -> anyhow::Result<()> {

src/external_cli/cargo/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ fn program() -> OsString {
1313
env::var_os("BEVY_CLI_CARGO").unwrap_or("cargo".into())
1414
}
1515

16-
#[derive(Debug, Args)]
16+
#[derive(Debug, Args, Clone)]
1717
#[command(next_help_heading = "Feature Selection")]
1818
pub struct CargoFeatureArgs {
1919
/// Space or comma separated list of features to activate
@@ -38,7 +38,7 @@ impl CargoFeatureArgs {
3838
}
3939
}
4040

41-
#[derive(Debug, Args)]
41+
#[derive(Debug, Args, Clone)]
4242
#[command(next_help_heading = "Compilation Options")]
4343
pub struct CargoCompilationArgs {
4444
/// Build artifacts in release mode, with optimizations.
@@ -111,7 +111,7 @@ impl CargoCompilationArgs {
111111
}
112112
}
113113

114-
#[derive(Debug, Args)]
114+
#[derive(Debug, Args, Clone)]
115115
#[command(next_help_heading = "Manifest Options")]
116116
pub struct CargoManifestArgs {
117117
/// Path to Cargo.toml

src/external_cli/cargo/run.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub(crate) fn command() -> Command {
1313
command
1414
}
1515

16-
#[derive(Debug, Args)]
16+
#[derive(Debug, Args, Clone)]
1717
pub struct CargoRunArgs {
1818
#[clap(flatten)]
1919
pub package_args: CargoPackageRunArgs,
@@ -38,7 +38,7 @@ impl CargoRunArgs {
3838
}
3939
}
4040

41-
#[derive(Debug, Args)]
41+
#[derive(Debug, Args, Clone)]
4242
#[command(next_help_heading = "Package Selection")]
4343
pub struct CargoPackageRunArgs {
4444
/// Package with the target to run
@@ -52,7 +52,7 @@ impl CargoPackageRunArgs {
5252
}
5353
}
5454

55-
#[derive(Debug, Args)]
55+
#[derive(Debug, Args, Clone)]
5656
#[command(next_help_heading = "Target Selection")]
5757
pub struct CargoTargetRunArgs {
5858
/// Build only the specified binary.

src/run/args.rs

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
use clap::{ArgAction, Args, Subcommand};
22

3-
use crate::external_cli::{arg_builder::ArgBuilder, cargo::run::CargoRunArgs};
3+
use crate::{
4+
build::args::{BuildArgs, BuildSubcommands, BuildWebArgs},
5+
external_cli::{arg_builder::ArgBuilder, cargo::run::CargoRunArgs},
6+
};
47

5-
#[derive(Debug, Args)]
8+
use super::cargo::build::{CargoBuildArgs, CargoPackageBuildArgs, CargoTargetBuildArgs};
9+
10+
#[derive(Debug, Args, Clone)]
611
pub struct RunArgs {
712
/// The subcommands available for the run command.
813
#[command(subcommand)]
@@ -23,35 +28,19 @@ impl RunArgs {
2328
matches!(self.subcommand, Some(RunSubcommands::Web(_)))
2429
}
2530

26-
/// Whether to build with optimizations.
27-
#[cfg(feature = "wasm-opt")]
28-
pub(crate) fn is_release(&self) -> bool {
29-
self.cargo_args.compilation_args.is_release
30-
}
31-
32-
/// The profile used to compile the app.
33-
pub(crate) fn profile(&self) -> &str {
34-
self.cargo_args.compilation_args.profile(self.is_web())
35-
}
36-
37-
/// The targeted platform.
38-
pub(crate) fn target(&self) -> Option<String> {
39-
self.cargo_args.compilation_args.target(self.is_web())
40-
}
41-
4231
/// Generate arguments for `cargo`.
4332
pub(crate) fn cargo_args_builder(&self) -> ArgBuilder {
4433
self.cargo_args.args_builder(self.is_web())
4534
}
4635
}
4736

48-
#[derive(Debug, Subcommand)]
37+
#[derive(Debug, Subcommand, Clone)]
4938
pub enum RunSubcommands {
5039
/// Run your app in the browser.
5140
Web(RunWebArgs),
5241
}
5342

54-
#[derive(Debug, Args)]
43+
#[derive(Debug, Args, Clone)]
5544
pub struct RunWebArgs {
5645
/// The port to run the web server on.
5746
#[arg(short, long, default_value_t = 4000)]
@@ -65,3 +54,38 @@ pub struct RunWebArgs {
6554
#[arg(short = 'b', long = "bundle", action = ArgAction::SetTrue, default_value_t = false)]
6655
pub create_packed_bundle: bool,
6756
}
57+
58+
impl From<RunArgs> for BuildArgs {
59+
fn from(args: RunArgs) -> Self {
60+
BuildArgs {
61+
skip_prompts: args.skip_prompts,
62+
cargo_args: CargoBuildArgs {
63+
compilation_args: args.cargo_args.compilation_args,
64+
feature_args: args.cargo_args.feature_args,
65+
manifest_args: args.cargo_args.manifest_args,
66+
package_args: CargoPackageBuildArgs {
67+
package: args.cargo_args.package_args.package,
68+
is_workspace: false,
69+
exclude: None,
70+
},
71+
target_args: CargoTargetBuildArgs {
72+
bin: args.cargo_args.target_args.bin,
73+
example: args.cargo_args.target_args.example,
74+
is_all_targets: false,
75+
is_benches: false,
76+
is_bins: false,
77+
is_examples: false,
78+
is_lib: false,
79+
is_tests: false,
80+
bench: None,
81+
test: None,
82+
},
83+
},
84+
subcommand: args.subcommand.map(|subcommand| match subcommand {
85+
RunSubcommands::Web(web_args) => BuildSubcommands::Web(BuildWebArgs {
86+
create_packed_bundle: web_args.create_packed_bundle,
87+
}),
88+
}),
89+
}
90+
}
91+
}

src/run/mod.rs

Lines changed: 5 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
use std::path::PathBuf;
22

3-
use anyhow::Context;
43
use args::RunSubcommands;
54

65
use crate::{
7-
build::ensure_web_setup,
6+
build::build_web,
87
external_cli::{
98
cargo::{self, metadata::Metadata},
10-
wasm_bindgen, CommandHelpers,
11-
},
12-
web::{
13-
bundle::{create_web_bundle, PackedBundle, WebBundle},
14-
profiles::configure_default_web_profiles,
9+
CommandHelpers,
1510
},
1611
};
1712

@@ -21,46 +16,9 @@ mod args;
2116
mod serve;
2217

2318
pub fn run(args: &RunArgs) -> anyhow::Result<()> {
24-
let mut cargo_args = args.cargo_args_builder();
25-
2619
if let Some(RunSubcommands::Web(web_args)) = &args.subcommand {
27-
ensure_web_setup(args.skip_prompts)?;
28-
29-
let metadata = cargo::metadata::metadata_with_args(["--no-deps"])?;
30-
let bin_target = select_run_binary(
31-
&metadata,
32-
args.cargo_args.package_args.package.as_deref(),
33-
args.cargo_args.target_args.bin.as_deref(),
34-
args.cargo_args.target_args.example.as_deref(),
35-
args.target().as_deref(),
36-
args.profile(),
37-
)?;
38-
39-
cargo_args = cargo_args.append(configure_default_web_profiles(&metadata)?);
40-
41-
// If targeting the web, run a web server with the WASM build
42-
println!("Compiling to WebAssembly...");
43-
cargo::build::command().args(cargo_args).ensure_status()?;
44-
45-
println!("Bundling JavaScript bindings...");
46-
wasm_bindgen::bundle(&bin_target)?;
47-
48-
#[cfg(feature = "wasm-opt")]
49-
if args.is_release() {
50-
crate::web::wasm_opt::optimize_bin(&bin_target)?;
51-
}
52-
53-
let web_bundle = create_web_bundle(
54-
&metadata,
55-
args.profile(),
56-
bin_target,
57-
web_args.create_packed_bundle,
58-
)
59-
.context("Failed to create web bundle")?;
60-
61-
if let WebBundle::Packed(PackedBundle { path }) = &web_bundle {
62-
println!("Created bundle at file://{}", path.display());
63-
}
20+
let build_args = args.clone().into();
21+
let web_bundle = build_web(&build_args)?;
6422

6523
let port = web_args.port;
6624
let url = format!("http://localhost:{port}");
@@ -79,6 +37,7 @@ pub fn run(args: &RunArgs) -> anyhow::Result<()> {
7937

8038
serve::serve(web_bundle, port)?;
8139
} else {
40+
let cargo_args = args.cargo_args_builder();
8241
// For native builds, wrap `cargo run`
8342
cargo::run::command().args(cargo_args).ensure_status()?;
8443
}

src/web/bundle.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ pub enum WebBundle {
5656
pub fn create_web_bundle(
5757
metadata: &Metadata,
5858
profile: &str,
59-
bin_target: BinTarget,
59+
bin_target: &BinTarget,
6060
packed: bool,
6161
) -> anyhow::Result<WebBundle> {
6262
let assets_path = Path::new("assets");
@@ -80,7 +80,7 @@ pub fn create_web_bundle(
8080
Index::Folder(custom_web_folder.to_path_buf())
8181
} else {
8282
println!("No custom `web` folder found, using defaults.");
83-
Index::Static(default_index(&bin_target))
83+
Index::Static(default_index(bin_target))
8484
},
8585
};
8686

@@ -92,7 +92,7 @@ pub fn create_web_bundle(
9292
.target_directory
9393
.join("bevy_web")
9494
.join(profile)
95-
.join(bin_target.bin_name);
95+
.join(&bin_target.bin_name);
9696

9797
// Remove the previous bundle
9898
// The error can be ignored, because the folder doesn't need to exist yet

0 commit comments

Comments
 (0)