Webassembly app with Rust
Introduction¶
WebAssembly has many different definitions on the Internet. I like the one from MDN the most, which says WebAssembly is a new binary assembly-like language that can run in the modern web browsers at near-native speed. There are many tools to compile code written in C/C++, Rust, Go, C#, etc. to be WebAssembly. It tells us that we can create high-performance code, but not using JavaScript/TypeScript
I decided to play with Rust. Rust is another hot buzzword. It is a relatively new programming language focused on performance and safety, especially safe concurrency. -- Wikipedia
This post describes how to create a WebAssembly package using Rust and use it in the AppRun applications from a JavaScript/TypeScript developer point of view. You will see the minimum steps of adding and using WebAssembly into your JavaScript/TypeScript project.
Setup¶
First, you will need the Rust toolchain, including rustup, rustc, and cargo for compiling Rust code, and wasm-pack for building, testing and publishing Rust-generated WebAssembly.
Install Rust¶
To install Rust on Mac/Linux, run the following command in the terminal.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Install wasm-pack¶
Once installed Rust, run the following command in the terminal.
cargo install wasm-pack
- If you start from scratch, follow the next section to create an AppRun project.
- If you already have an existing project, jump to the section of Create WebAssembly Project.
Create AppRun Project¶
Run the commands to create an AppRun project:
mkdir your-app-name
cd your-app-name
npx apprun -i
Wait a few minutes for installing the npm packages, and then run the npm command:
npm start
You will see a hello world application running.
Next, we will add WebAssembly to this project.
Create WebAssembly Project¶
Let's create a Rust project by running the following command:
cargo new wasm --lib
The command creates a folder called wasm and two files under the folder your-app-name/wasm: Cargo.toml and src/lib.rs.
It is a regular Rust project, not a WebAssembly yet. You will need to add wasm-bindgen as the dependency to make it target WebAssembly. Open Cargo.toml and add the following sections.
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2.60"
js-sys = "0.3.37"
wasm-bindgen is a Rust library that facilitates high-level interactions between wasm modules and JavaScript. js-sys is the waw bindings to JS global APIs for projects using wasm-bindgen.
Now, you can use wasm-pack to build a WebAssembly.
cd wasm
wasm-pack build
Use WebPack¶
Since the AppRun project is a WebPack project, we can use the wasm-pack-plugin to unify the build process that creates the WebAssembly and JavaScript code at the same time. Go ahead to add the package:
npm i @wasm-tool/wasm-pack-plugin -D
And add the wasm-pack-plugin into the webpack.config.js.
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
module.exports = {
...
plugins: [
new WasmPackPlugin({
crateDirectory: path.resolve(__dirname, ".")
}),
]
...
}
Also, because the wasm-pack-plugin generates the dynamic import module, you need to modify tsconfig.json file to set the module to be esnext.
{
"compilerOptions": {
...
"module": "esnext",
...
}
}
Finally, the npm scripts: npm start and npm run build will build the TypeScript code as well the Rust code.
Let's write some Rust code.
WebAssembly and AppRun¶
We will demonstrate two interactions between the WebAssembly and the AppRun application.
- Call the WebAssembly from the AppRun application
- Call the AppRun application from the WebAssembly
Call WebAssembly¶
First, we create a Rust function in the wasm/src/lib.rs file.
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
This function adds two numbers. We can make a counter application from it. Here is the counter application in AppRun.
import app from 'apprun';
let wasm;
import('../wasm/pkg').then(module => wasm = module);
const state = {
title: 'Hello world - AppRun !',
count: 0
}
const add = (state, num) => ({
...state,
count: wasm.add(state.count, num)
});
const view = ({ title, count }) => <>
<h1>{title}</h1>
<h1>{count}</h1>
<button $onclick={[add, -1]}>-1</button>
<button $onclick={[add, +1]}>+1</button>
</>;
app.start(document.body, state, view);
You can see from the code above:
- Wasm-pack has created a JavaScript module that we can import dynamically.
- We can call the WebAssembly function just like a regular JavaScript function from a module.
Running the application, we have a counter that uses the WebAssembly function.
Next, let's see how does the WebAssembly function call AppRun functions.
Call the AppRun¶
Open wasm/src/lib.rs file and add the following functions.
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = app)]
fn run(event: &str, p: &str);
}
#[wasm_bindgen(start)]
pub fn start() {
run("@hello", "hello world from rust");
}
- The first function named run binds to the AppRun app.run function.
- The second function named start runs automatically when the WebAssembly is loaded.
- The start function calls the run function to send a '@hello' event to AppRun.
Back to AppRun code, we will handle the '@hello' event.
import app from 'apprun';
let wasm;
import('../wasm/pkg').then(module => wasm = module);
const state = {...}
const add = (state, num) => ({...});
const view = ({ title, count }) => <>...</>;
const update = {
'@hello': (state, title) => ({...state, title})
}
app.start(document.body, state, view, update);
Now, when the application starts, it displays the messages sent from the WebAssembly.
We have successfully made two-way interactions between the WebAssembly and the AppRun application.
Souce Code¶
You can run the live demo: https://yysun.github.io/apprun-rust.
Or visit the source.
{% github yysun/apprun-rust %}
You also can use this project as an AppRun application template. Run the command to create your application.
npx degit yysun/apprun-rust my-app
Conclusion¶
This post should give you a quick start to use Rust/WebAssembly in the AppRun applications. The demo project shows the two technologies interact with each other very well. You can use the demo project as a template.
We have now opened the door to a new world. There is much more potential to explore.