This example uses Rust Web Assembly compiled for WASI (the Web Assembly
System Interface) running in the browser using WasmerJS, and uses wasm-bindgen
to make it easy to pass data from JavaScript to Rust and vice versa.
Rust is compiled for target wasm32-wasi
and bindings are generated using
wasm-bindgen
plus a small amount of post-processing to adapt the bindings for
WASI.
For a non-Rust example and Svelte + Wasmer/WASI template see simple-svelte-wasmer-webpack which was the starting point for this project.
- A Svelte WASM/WASI app with Rust subsystem (using target
wasm32-wasi
) - JavaScript and Rust both accessing the WasmerJS/wasmFS filesystem
- Calling Rust from JavaScript and vice versa using
wasm-bindgen+
- Passing and returning JavaScript and Rust native types with no mucking about
- Passing and returning JavaScript objects and arrays to/from Rust structs
Help! I'd like to add more examples of passing variables using wasm-bindgen
so if you know how to do something I'm not showing yet please open an issue or PR (see Rust main.rs).
Note: wasm-bindgen+
indicates a small amount of post-processing to make the wasm-bindgen
output suitable for use with WasmerJS in the browser.
Most of the work is done by WasmerJS and wasm-bindgen
(CLI because wasm-pack
does not support WASI at this time). It was tricky to work out how to do this, but the solution is straightforward.
The main difficulty is that the JavaScript bindings generated by wasm-bindgen
import the corresponding wasm-bindgen
WASM output and this fails because the imports needed by the WASM are not available when the WASI is initialised.
A script (scripts/wbg_to_wasi.js) has been provided which creates a modified version of the JavaScript generated by wasm-bindgen
. You don't need to invoke the script yourself because it is included in the build commands which handle everything. So the following is just to document how it works.
In the following example the wasm-bindgen
generated JavaScript is in rust-wasi_bg.js, which the script copies to a new file rust-wasi_bg_wasi.js with changes. Firstly the import of the WASM file:
import * as wasm from './rust-wasi_bg.wasm';
is replaced by a way to provide the imports after the bindings module has loaded:
let wasm;
export function setBindingsWasm(w){ wasm = w; }
Secondly, imported JavaScript module names are changed to a relative path (because you can't specify a relative path with the #[wasm_bindgen]
macro). So a line such as:
import { js_test_n } from 'test';
is replaced with:
import { js_test_n } from './js-wasi/test.js';
If necessary you can customise the path by editing the command which invokes the script (see package.json). I suspect there's a better way to handle this second modification - PR's welcome!
Although this repository implements the app using Svelte, it follows the WasmerJS example documentation closely and should be simple to apply in any web framework.
IMPORTANT: your Rust main()
function must be empty if you want to call Rust functions from JavaScript under Wasmer WASI. As in:
main(){}
To make use of this, your web app does three things (see for example src/App.svelte).
First import the modified bindings as wasm
:
import * as wasm from './rust-wasi_bg_wasi.js';
Provide modified bindings along with any other imports (in this case a test
module, 'test.js') when initialising the Web Assembly. Note that you must use the name of the original bindings module './rust-wasi_bg.js':
imports = {test,...{'./rust-wasi_bg.js': await import('./rust-wasi_bg_wasi')},...imports};
let instance = await WebAssembly.instantiate(wasmModule, {
...imports
});
Finally, provide the wasm module to the JavaScript bindings immediately after the call to wasi.start(...)
:
wasi.start(instance);
wasm.setBindingsWasm(instance.exports);
That's it. From there on you can make calls to WASM via the bindings using the wasm
import './rust-wasi_bg_wasi.js'. For example:
wasm.rust_print_bg_n(256); // Call Rust, print number to stdout
As well as NodeJS and Rust you need the Rust wasm32-wasi
target:
rustup target add wasm32-wasi
And the wasm-bindgen
CLI:
cargo install wasm-bindgen-cli
Note: make sure wasm-bindgen --version
matches the version of the wasm-bingen
module in Cargo.toml
(/src/rust-rust-wasi/Cargo.toml). If the versions don't match after doing cargo install wasm-bindgen-cli && wasm-bindgen --version
, modify the version referred to in Cargo.toml
to match the CLI.
You should only need the first and second parts of the version to match, so for example wasm-bindgen --version
of 'wasm-bindgen 0.2.69' should work fine with Cargo.toml 'wasm-bindgen = "^0.2"').
If you don't have yarn
use npm run
instead of yarn
in the following:
git clone https://github.com/happybeing/svelte-wasi-with-rust
cd svelte-wasi-with-rust
yarn && yarn dev-wasm-bindgen && yarn dev
Once the development build completes you can visit the app at localhost:8080.
To build for release:
yarn build
To test, use yarn serve public
and visit localhost:5000
To deploy, upload everything in /public
The App code is in src/App.svelte
with Rust and JavaScript subsystems in src/rust-wasi
and src/js-wasi
.
To re-build the wasm and serve the result:
yarn dev-wasm-bindgen
yarn dev
If you have inotifywait
(e.g. on Linux) you can use yarn dev
and yarn watch-wasm-bindgen
together, and changes to any part of the app including the Rust subsystem will automatically re-build everything and reload the browser.
To do this, in one terminal watch and re-build the app with:
yarn dev
Then in another terminal, watch and re-build the Rust subsystem with:
yarn watch-wasm-bindgen
If you're using VSCode, we recommend installing the offical Svelte extension as well as the offical Rust extension. If you are using other editors, your may need to install a plugin in order to get syntax highlighting and intellisense for both Svelte and Rust.