When to use this skill
- Build a Bevy client for
wasm32-unknown-unknown. - Choose the broadly compatible WebGL2 path or WebGPU path.
- Generate JS bindings, serve assets, and reduce artifact size.
- Diagnose missing canvas, insecure-context, codec, or asset-path failures.
Canonical manifest
[package]
name = "myclient"
version = "0.1.0"
edition = "2024"
[dependencies] bevy = { version = "0.19", default-features = false, features = [ "3d", "ui", "touch", ] }
[profile.wasm-release] inherits = "release" opt-level = "z" lto = "fat" codegen-units = 1 strip = "debuginfo"
The `3d` profile supplies Bevy's renderer, Winit, and the default WebGL2 path.
Add `audio` only if needed; browser audio must begin after user interaction.
## Build and serve
```bash
rustup target add wasm32-unknown-unknown
cargo install wasm-bindgen-cli
cargo build --profile wasm-release --target wasm32-unknown-unknown
wasm-bindgen --target web --out-name myclient --out-dir public \
target/wasm32-unknown-unknown/wasm-release/myclient.wasm
# Optional: install Binaryen first, then verify this actually reduces size.
wasm-opt -Oz public/myclient_bg.wasm -o public/myclient_bg.opt.wasm
mv public/myclient_bg.opt.wasm public/myclient_bg.wasm
python3 -m http.server --directory public 8080
Copy or link the project assets/ directory to public/assets/; Bevy fetches
assets over HTTP relative to the served page/asset root.
Minimal HTML
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>My Bevy game</title>
<style>html,body,#bevy{width:100%;height:100%;margin:0;background:#000}</style>
</head>
<body>
<canvas id="bevy"></canvas>
<script type="module">
import init from "./myclient.js";
await init();
</script>
</body>
</html>
Bind the primary window to that existing canvas. Otherwise Window::default() has
canvas: None, so Bevy/Winit creates and appends a second canvas:
use bevy::{prelude::*, window::WindowPlugin};
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
canvas: Some("#bevy".into()),
fit_canvas_to_parent: true,
..default()
}),
..default()
}))
.run();
}
Serve over HTTP; opening the file directly does not provide the fetch and module semantics the generated glue expects.
WebGL2 versus WebGPU
| Choice | Cargo features | Choose when |
|---|---|---|
| WebGL2 | profile defaults / webgl2 |
Browser reach matters; no compute dependency |
| WebGPU | add webgpu |
A tested target browser supports it and effects need compute |
In Bevy 0.19, webgpu overrides webgl2. One binary cannot dynamically fall
back between them. Produce two artifacts or choose one based on the supported
browser matrix. Check current browser support during release testing rather
than hard-coding browser-version claims in application logic.
Gotchas
3d_apionly supplies types; it does not render. Use3dor compose3d_bevy_renderwith the app/platform collections.default-features = falsematters. Cargo features are additive; adding3dwithout disabling defaults keeps the full default set.- WebGL2 has no compute shaders. Hanabi and other compute-heavy plugins require the WebGPU artifact and their own current Bevy-compatible release.
- Browser filesystems and child processes are not native capabilities. Capture, save, and export flows need explicit web APIs/JS interop.
- Audio playback is normally blocked until a user gesture resumes the browser audio context.
- Asset 404s are deployment-layout errors: inspect the requested URL in browser
devtools and make the served
assets/tree match it. - A configured
Window.canvasselector must resolve before startup and cannot be changed later. If you omit it, remove any placeholder canvas and let Bevy append its own. - Test on actual target browsers and GPUs.
cargo checkcannot validate adapter limits, shader translation, texture formats, or browser security policy.
See also
bevy-cargo-features— profile composition.bevy-rendering— renderer extension choices.bevy-assets— asynchronous asset loading.bevy-vfx— compute-dependent effects.