Skip to content

happybeing/svelte-wasi-with-rust

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Svelte Rust/WASI in Browser Example

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.

Features

  • 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.

How This Works

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.

Modified Build Process

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!

Implemention in the App

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

Setup

Prerequisites

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"').

Get the Code

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.

Builds

Release Builds

To build for release:

yarn build

To test, use yarn serve public and visit localhost:5000

To deploy, upload everything in /public

Development Builds

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.