Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic

### 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.
- Unstable support for building and running web apps using **Wasm multi-threading** features
- Use `bevy run web --unstable multi-threading` to run an app using multi-threaded Wasm
- Can be configured in `Cargo.toml` by setting `package.metadata.bevy_cli.unstable.web-multi-threading = true`
Expand Down
31 changes: 16 additions & 15 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
- [Quick Start](cli/quick-start.md)
- [Scaffolding](cli/scaffolding.md)
- [Web Apps](cli/web.md)
- [Wasm Multi-threading (unstable)](cli/web/multi-threading.md)
- [Default `index.html`](cli/web/default-index-html.md)
- [Wasm Multi-threading (unstable)](cli/web/multi-threading.md)
- [`getrandom`](cli/web/getrandom.md)
- [Default `index.html`](cli/web/default-index-html.md)
- [Linter](cli/linter.md)
- [Configuration](cli/configuration.md)
- [Configuration Reference](cli/configuration/reference.md)
- [Configuration Reference](cli/configuration/reference.md)
- [Troubleshooting](cli/troubleshooting.md)
- [Changelog](cli/changelog.md)
- [Migration Guide](cli/migration.md)
Expand All @@ -23,10 +24,10 @@
- [About](linter/index.md)
- [Installation](linter/install.md)
- [Usage](linter/usage.md)
- [Toggling Lints in `Cargo.toml`](linter/usage/toggling-lints-cargo-toml.md)
- [Detecting `bevy_lint`](linter/usage/detecting-bevy-lint.md)
- [Registering `bevy` as a Tool](linter/usage/register-bevy-tool.md)
- [Toggling Lints in Code](linter/usage/toggling-lints-code.md)
- [Toggling Lints in `Cargo.toml`](linter/usage/toggling-lints-cargo-toml.md)
- [Detecting `bevy_lint`](linter/usage/detecting-bevy-lint.md)
- [Registering `bevy` as a Tool](linter/usage/register-bevy-tool.md)
- [Toggling Lints in Code](linter/usage/toggling-lints-code.md)
- [Compatibility](linter/compatibility.md)
- [Environmental Variables](linter/environmental-variables.md)
- [Rust Analyzer Support](linter/rust-analyzer.md)
Expand All @@ -46,13 +47,13 @@
- [About](contribute/linter/index.md)
- [Tutorial](contribute/linter/tutorial.md)
- [How-To](contribute/linter/how-to.md)
- [Setup Your Editor](contribute/linter/how-to/editor.md)
- [Work with Types](contribute/linter/how-to/types.md)
- [Parse Method Calls](contribute/linter/how-to/methods.md)
- [Release `bevy_lint`](contribute/linter/how-to/release.md)
- [Upgrade the Rust Toolchain](contribute/linter/how-to/upgrade-rust-toolchain.md)
- [Setup Your Editor](contribute/linter/how-to/editor.md)
- [Work with Types](contribute/linter/how-to/types.md)
- [Parse Method Calls](contribute/linter/how-to/methods.md)
- [Release `bevy_lint`](contribute/linter/how-to/release.md)
- [Upgrade the Rust Toolchain](contribute/linter/how-to/upgrade-rust-toolchain.md)
- [Reference](contribute/linter/reference.md)
- [Lint Module Docs](contribute/linter/reference/lint-module-docs.md)
- [Macro-Generated Code](contribute/linter/reference/macros.md)
- [Coding Conventions and Best Practices](contribute/linter/reference/conventions.md)
- [Lint Module Docs](contribute/linter/reference/lint-module-docs.md)
- [Macro-Generated Code](contribute/linter/reference/macros.md)
- [Coding Conventions and Best Practices](contribute/linter/reference/conventions.md)
- [Explanation](contribute/linter/explanation.md)
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 pulled in the web feature,
the backend would be used everywhere or the build would be broken.
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, depending on the Bevy version used).

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).
8 changes: 8 additions & 0 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 @@ -33,6 +34,13 @@ pub fn build_web(
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");
}

#[cfg(feature = "unstable")]
support_multi_threading(args);

Expand Down
117 changes: 117 additions & 0 deletions src/web/getrandom.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
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();

// Since this is only called with the `web` commands,
// we can safely assume that Wasm = web in this context.
// Still, we allow the user to override this assumption
// by configuring a different backend
if !rustflags.contains("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;
Expand Down
Loading