Skip to content

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
On Windows, I enabled the Windows Subsystem for Linux and used Rust in the Linux terminal.

Install wasm-pack

Once installed Rust, run the following command in the terminal.

cargo install wasm-pack
Believe it or not, that's all you need to create WebAssembly. Let's go back to the JavaScript/TypeScript world.

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

AppRun Hello World

AppRun Hello World

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.

Counter with WASM

Counter with WASM

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.

Message from WASM

Message from wasm

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.