# Bevy Remote Wasm
[](https://crates.io/crates/bevy_remote_wasm)
[](https://docs.rs/bevy_remote_wasm)
[](LICENSE-MIT)
A plugin for the [Bevy Remote Protocol](https://docs.rs/bevy/latest/bevy/remote/) that exposes remote methods to Wasm/JavaScript.
## Usage
### Setup
1. Add `bevy_remote` to your dependencies, and `bevy_remote_wasm` only for `wasm` targets.
```toml
[dependencies]
bevy = "0.18"
bevy_remote = { version = "0.18", default-features = false }
[target.'cfg(target_family = "wasm")'.dependencies]
bevy_remote_wasm = "0.1"
```
Don't depend on `bevy/bevy_remote` (`bevy = { version = "0.18", features = ["bevy_remote"] }`) because it enables the default `http` transport, which does not compile on Wasm target (until [this fix](https://github.com/bevyengine/bevy/pull/23367/) is merged and released).
This is why it is recommended to depend on the separate crate `bevy_remote` with no default features (so `bevy_remote/http` is not enabled).
2. Add `RemotePlugin` to your app, and add `RemoteWasmPlugin` only when compiling for Wasm.
```rust
use bevy::prelude::*;
use bevy_remote::RemotePlugin;
fn main() {
let mut app = App::new();
app.add_plugins((DefaultPlugins, RemotePlugin::default()));
#[cfg(target_family = "wasm")]
app.add_plugins(bevy_remote_wasm::RemoteWasmPlugin);
app.run();
}
```
3. Build for the `wasm32-unknown-unknown` target, then generate the JS bindings with [`wasm-bindgen`](https://wasm-bindgen.github.io/wasm-bindgen/) directly (`wasm-bindgen --target web --out-dir <output_dir> target/wasm32-unknown-unknown/debug/<crate_name>.wasm`) or through a tool such as [`wasm-pack`](https://wasm-bindgen.github.io/wasm-pack/) or [Trunk](https://trunkrs.dev/).
4. In JavaScript, load the generated JS module, call `init()`, then await `getBridge()`:
```js
import init, { getBridge } from "/example.js";
await init();
const bridge = await getBridge();
const response = await bridge.main["rpc.discover"]();
console.log(response.info.version);
```
The generated module path and `init` function depend on your build tool, but the initialization order does not: initialize the Wasm module first, then await the bridge.
`getBridge()` may be called as soon as the JS module loads, but it resolves only after Bevy publishes the bridge during app startup, which, depending on your setup, may take some time.
The `wasm-bindgen` output also includes TypeScript declarations for the root bridge (`BrpBridge`) and the default remote methods (`BuiltInBrpBridge`):
```ts
import type { BuiltInBrpBridge } from "./example.js";
const bridge = await getBridge() as BuiltInBrpBridge;
const response = await bridge.main['world.query']({ data: { option: 'all' } });
// Enjoy docs and autocompletion for built-in methods!
```
5. Serve the generated HTML, JS, and `.wasm` files together from a web server or bundler that supports Wasm imports.
A minimal example can be found in [`examples/minimal.rs`](./examples/minimal.rs) for the smallest Bevy setup and [`apps/get-version/index.html`](./apps/get-version/index.html) for an inline HTML module example.
### Wasm API
The plugin builds a bridge object with app-scoped method maps based on the methods registered with [`bevy_remote::RemotePlugin`](https://docs.rs/bevy/latest/bevy/remote/struct.RemotePlugin.html), more specifically through the [`bevy_remote::RemoteMethods`](https://docs.rs/bevy/latest/bevy/remote/struct.RemoteMethods.html) resource. Each time this resource is updated, the bridge is re-published with the new methods. The `getBridge()` function must be called again to get the updated bridge.
For Bevy `0.18`, the bridge exposes only the `main` app. Method keys inside that namespace use the BRP method names directly, so call them with bracket notation such as `bridge.main["world.query"]` and `bridge.main["world.list_components+watch"]`.
- Instant methods (run once, return a result):
```js
// Call the instant method with params but without callback.
// Returns the result wrapped in a Promise.
const result = await bridge.main['world.query']({ data: { option: 'all' } });
console.log(result);
// Call the instant method with params and a callback that will be called with the result.
// Returns `undefined` wrapped in a Promise.
await bridge.main['world.query']({ data: { option: 'all' } }, (result) => console.log(result));
```
- Watching methods (stream results):
```js
// Call the watching method with params and a callback that will be called on each result.
// Returns a closer function wrapped in a Promise.
const close = await bridge.main['world.list_components+watch']({ entity: 123 }, (result) => console.log(result));
// Stop the stream.
close();
```
### Version Table
| `0.18` | `0.1` |
## Development
### Tech Stack
This project is built with:
- [Rust](https://www.rust-lang.org/) and [Cargo](https://doc.rust-lang.org/cargo/) for the core crate.
- [Bevy](https://bevyengine.org/) and the [Bevy Remote Protocol](https://docs.rs/bevy/latest/bevy/remote/) for the runtime API.
- [WebAssembly](https://webassembly.org/) and [wasm-bindgen](https://rustwasm.org/docs/wasm-bindgen/) for the Wasm to JavaScript bridge.
- [TypeScript](https://www.typescriptlang.org/) and [npm](https://www.npmjs.com/) for the generated declaration files and the companion types package.
- [just](https://github.com/casey/just) for common development tasks.
Some `just` recipes depend on external tools and will fail fast if they are not installed.
### Common Tasks
Use the [`Justfile`](./Justfile) for the usual development workflow:
- Validate the whole repository with `just check`.
This runs Rust linting, formatting checks, a crate publish dry-run, and the TypeScript package check.
- Format the Rust code with `just format`.
This uses `cargo +nightly fmt`, so a nightly toolchain is required for formatting.
- Run the Wasm tests with `just test`.
Tests run on the `wasm32-unknown-unknown` target with `wasm-bindgen-test-runner`.
- Discover available demos with `just list-examples` and `just list-apps`.
- Build and serve demos locally with `just run-example`.
By default this builds the `minimal` example with the `get-version` app and serves it on `http://localhost:8080`.
- Build demo artifacts without serving them with `just build-example`.
Generated browser assets are written under `target/dist/<app>`.
### Contributing
The repository is split into a few small parts:
- [`src/`](./src/lib.rs) contains the core `RemoteWasmPlugin` implementation and the Wasm bridge exported to JavaScript.
- [`build.rs`](./build.rs) combines the TypeScript declaration files into a single output that `wasm-bindgen` embeds in the generated bindings.
- [`examples/`](./examples/) contains Rust-side Bevy examples such as the minimal app and the 3D demo.
- [`apps/`](./apps/) contains browser-facing HTML and JavaScript apps that load those examples and exercise the bridge.
- [`bevy-remote-wasm-types/`](./bevy-remote-wasm-types/) contains the type-only NPM package and its declaration files.
A good contribution flow is: update the Rust bridge in `src/`, keep the embedded TypeScript surface in sync and run one of the example apps locally. Validate with `just check` before submitting a pull request.
### Deployment
A [GitHub Actions](https://github.com/features/actions) CI workflow runs on pushes to `main` and on pull requests.
## License
Dual-licensed under Apache-2.0 and MIT. See [`LICENSE-APACHE`](./LICENSE-APACHE) and [`LICENSE-MIT`](./LICENSE-MIT).