Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic

**All Changes**: [`cli-v0.1.0-alpha.1...main`](https://github.com/TheBevyFlock/bevy_cli/compare/cli-v0.1.0-alpha.1...main)

### Added

- The `bevy build web` and `bevy run web` commands will now automatically apply the web backend for `getrandom` if necessary. It requires both a feature and a rustflag to be enabled by the user, which can quickly lead to compile errors when not set up correctly.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it's just me as a none native speaker but at first it read for me like

"for bevy build web to automatically configure it, you have to enable a feature and some rustflags" until I then realize that the "It" refers to getrandom.


### Changed

- You can now customize the flags passed to `wasm-opt` in both CLI and `Cargo.toml`. Simply pass a list of flags you want to use, e.g. `--wasm-opt=-Oz --wasm-opt=--enable-bulk-memory` in the CLI or `wasm-opt = ["-Oz", "--enable-bulk-memory"]` in the config.
Expand Down
1 change: 1 addition & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- [Quick Start](cli/quick-start.md)
- [Scaffolding](cli/scaffolding.md)
- [Web Apps](cli/web.md)
- [`getrandom`](cli/web/getrandom.md)
- [Default `index.html`](cli/web/default-index-html.md)
- [Linter](cli/linter.md)
- [Configuration](cli/configuration.md)
Expand Down
52 changes: 52 additions & 0 deletions docs/src/cli/web/getrandom.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# `getrandom`

`getrandom` is a popular crate that allows retrieving random data from system resources.
It is used by `bevy` and many other crates.
It provides multiple backends which retrieve the random data from different sources.

This works great, except when targeting the web.
Unlike most other platforms, there is no compilation target available that guarantees web APIs to exist.
Just because you're building for `wasm32-unknown-unknown` doesn't necessarily mean you are making a web app --
you could also be building a standalone Wasm application.

So the target cannot be used to automatically activate the web backend.
Features are also inadequate: They are additive, so if _any_ dependency would pull in the web feature,
the backend would be used everywhere.
Considering the security-sensitive nature of random data, this was deemed unacceptable.

So in addition to a feature to make the backend available,
`getrandom` requires you to pass a `RUSTFLAG` to the compiler to activate the backend.
This guarantees that the backend can only be configured once (by "outer" package).

## Configuring the web backend

Since you are the application developer, you _know_ that you are building for the web and not just any Wasm target.
This allows you to set up the `getrandom` backend properly, for example like this:

```toml
[target.'cfg(all(target_family = "wasm", any(target_os = "unknown", target_os = "none")))'.dependencies]
getrandom = { version = "0.3", features = ["wasm_js"] }
getrandom_02 = { version = "0.2", features = ["js"], package = "getrandom" }
```

This activates the necessary feature flags for `getrandom`, accounting for both v0.2 and v0.3 (as they can both be in the dependency tree).

Additionally, you need to add `--cfg getrandom_backend="wasm_js"` to your `RUSTFLAGS`.
This can be done in several places:

- Setting `rustflags` in `.cargo/config.toml`
- Setting `build.rustflags` in `Cargo.toml`
- Setting the `RUSTFLAGS` env variable when running `cargo`

However, not that the rustflags are not merged, but _overwritten_.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
However, not that the rustflags are not merged, but _overwritten_.
However, note that the rustflags are not merged, but _overwritten_.

So if you have e.g. `rustflags` defined in your `~/.cargo/config.toml` to optimize your compile times,
but then have again `rustflags` defined in your workspace to set the `getrandom` backend,
only the workspace `rustflags` will be used.

## Automated by the CLI

The Bevy CLI automatically applies the web `getrandom` backend when `bevy build web` or `bevy run web` is used
and you haven't configured the backend yourself.
This allows more projects to work out-of-the-box for the web and simplifies the configuration.

You can still explicitly define the backend if you wish (or need to).
18 changes: 13 additions & 5 deletions src/web/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::{
external_cli::{cargo, wasm_bindgen, wasm_opt},
web::{
bundle::{PackedBundle, create_web_bundle},
getrandom::apply_getrandom_backend,
profiles::configure_default_web_profiles,
},
};
Expand All @@ -27,17 +28,19 @@ pub fn build_web(
metadata: &Metadata,
bin_target: &BinTarget,
) -> anyhow::Result<WebBundle> {
let web_args = args
.subcommand
.as_ref()
.map(|BuildSubcommands::Web(web_args)| web_args);

let mut profile_args = configure_default_web_profiles(metadata)?;
// `--config` args are resolved from left to right,
// so the default configuration needs to come before the user args
profile_args.append(&mut args.cargo_args.common_args.config);
args.cargo_args.common_args.config = profile_args;

// Apply the `getrandom` web backend if necessary
if let Some(target) = args.target()
Copy link
Collaborator

@DaAlbrecht DaAlbrecht Aug 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to check if the target specified is actually wasm32-unkown-unkown (or perhaps we should just overwrite the flag to wasm32-unkown-unkown when using the web subcommand)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are also other potential web targets like wasm32v1-none, so I think it's enough to know that the user used a web subcommand

&& apply_getrandom_backend(args, &target).context("failed to apply getrandom backend")?
{
info!("automatically configuring `getrandom` web backend");
}

let cargo_args = args.cargo_args_builder();

info!("compiling to WebAssembly...");
Expand All @@ -53,6 +56,11 @@ pub fn build_web(
wasm_bindgen::bundle(metadata, bin_target, args.auto_install())?;
wasm_opt::optimize_path(bin_target, args.auto_install(), &args.wasm_opt_args())?;

let web_args = args
.subcommand
.as_ref()
.map(|BuildSubcommands::Web(web_args)| web_args);

let web_bundle = create_web_bundle(
metadata,
args.profile(),
Expand Down
113 changes: 113 additions & 0 deletions src/web/getrandom.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use semver::{Version, VersionReq};

use crate::{commands::build::BuildArgs, external_cli::cargo};

/// Apply the web backend to getrandom.
///
/// This is a consistent pain point for users, so we try to automate it.
///
/// There's two things you need to do:
///
/// 1. Enabling the corresponding feature in the `getrandom` dependency
/// 2. Setting the `--cfg getrandom_backend="wasm_js"` RUSTFLAG
///
/// This function can handle both v2 and v3 dependencies.
///
/// If `getrandom` is not a dependency, nothing happens.
pub fn apply_getrandom_backend(args: &mut BuildArgs, target: &str) -> anyhow::Result<bool> {
// Obtaining the metadata again to filter dependencies and their features by the used target
let metadata = cargo::metadata::metadata_with_args(["--filter-platform", target])?;

let getrandom_packages = metadata
.packages
.iter()
.filter(|pkg| pkg.name.as_str() == "getrandom")
.collect::<Vec<_>>();

if getrandom_packages.is_empty() {
// Nothing to do when `getrandom` isn't used
return Ok(false);
}

let mut backend_applied = false;

// The package allows us to find the correct version of the dependency
let v2 = getrandom_packages
.iter()
.find(|pkg| VersionReq::parse("^0.2").unwrap().matches(&pkg.version));

// getrandom v2 needs the `js` feature enabled
if let Some(v2) = v2 {
// The resolved dependency includes the features that are enabled
let dep = metadata
.resolve
.as_ref()
.and_then(|resolve| resolve.nodes.iter().find(|node| node.id == v2.id));

let v2_feature = "js";

if let Some(dep) = dep
&& !dep.features.iter().any(|feature| **feature == v2_feature)
{
backend_applied = true;

add_feature_config(
&mut args.cargo_args.common_args.config,
&v2.version,
v2_feature,
);
}
}

// getrandom v3 needs the `wasm_js` feature enabled
// and the `--cfg getrandom_backend="wasm_js"` RUSTFLAG must be set
let v3 = getrandom_packages
.iter()
.find(|pkg| VersionReq::parse("^0.3").unwrap().matches(&pkg.version));

if let Some(v3) = v3 {
let dep = metadata
.resolve
.as_ref()
.and_then(|resolve| resolve.nodes.iter().find(|node| node.id == v3.id));

let v3_feature = "wasm_js";

if let Some(dep) = dep
&& !dep.features.iter().any(|feature| **feature == v3_feature)
{
backend_applied = true;

add_feature_config(
&mut args.cargo_args.common_args.config,
&v3.version,
v3_feature,
);
}

let mut rustflags = args
.cargo_args
.common_args
.rustflags
.clone()
.unwrap_or_default();

if !rustflags.contains("--cfg getrandom_backend") {
backend_applied = true;
rustflags += " --cfg getrandom_backend=\"wasm_js\"";
args.cargo_args.common_args.rustflags = Some(rustflags);
}
}

Ok(backend_applied)
}

/// Adds the feature configuration for the `getrandom` dependency
fn add_feature_config(config: &mut Vec<String>, version: &Version, feature: &str) {
// Distinguish entries by version to allow multiple versions to be configured
let table = format!("dependencies.getrandom_{}{}", version.major, version.minor);
// The config arg doesn't support inline tables, so each entries must be configured separately
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo:

Suggested change
// The config arg doesn't support inline tables, so each entries must be configured separately
// The config arg doesn't support inline tables, so each entry must be configured separately

config.push(format!(r#"{table}.package="getrandom""#));
config.push(format!(r#"{table}.version="{}""#, version));
config.push(format!(r#"{table}.features=["{feature}"]"#));
}
1 change: 1 addition & 0 deletions src/web/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

pub(crate) mod build;
pub(crate) mod bundle;
pub(crate) mod getrandom;
pub(crate) mod profiles;
pub(crate) mod run;
pub(crate) mod serve;
Loading