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.
wasm32-wasi)wasm-bindgen+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.