🕸️ Using rust modules in JavaScript/Web Development (Part 1 / 3) [Plain JS]

One of the promising reasons I started learning rust is that it can be used to build modules for web using web-assembly. This blog post will cover how you can build a rust module and use it as regular package in the JavaScript environment. Remember rust is not meant to replace JS. I see it as complementary add-on to the places where JS is comparatively slower (heavy computations).

Topics that will be covered:

Prerequisite:

I am assuming that you have setup rust compiler tools along with cargo and have some knowledge of rust programming.

Note: For those looking to learn Rust:

The best place to learn is the included book with the rustup docs that come along with the tools when you setup rust on your machine.

Install rustup and just do rustup docs in your terminal. Boom! a browser window will open up and you’ll get links to completely offline books that teaches you rust. How cool is that !

Lets Begin

Setting up the rust project

Lets start by creating a simple calculator program in rust.

  1. Create a new project: cargo new --lib web
  2. cd web
  3. Change the contents of src/lib.rs to
#[no_mangle]
pub fn add(first: i32, second:i32) -> i32 {
  first + second
}
#[no_mangle]
pub fn subtract(first: i32, second:i32) -> i32 {
  first - second
}
#[no_mangle]
pub fn multiply(first: i32, second:i32) -> i32 {
  first * second
}

4. To check if it works as expected create another file src/main.rs with the contents

extern crate web;
fn main(){
  println!(" 4 - 2 = {}",web::subtract(4,2));
  println!(" 4 + 2 = {}",web::add(4,2));
  println!(" 4 * 2 = {}",web::multiply(4,2));
}

5. Lets run it: do cargo run

You should get an output similar to this:

➜  web git:(master) ✗ > cargo run
Compiling web v0.1.0 (file:///Users/atulr/Projects/Hobby/rust/projects/web)
Finished dev [unoptimized + debuginfo] target(s) in 0.39 secs
Running `target/debug/web`
4 - 2 = 2
4 + 2 = 6
4 * 2 = 8

So now we have a rust library (crate) that can perform operations like add, subtract, multiply. Great! Lets try to use this library in JavaScript.

Using rust modules for front-end (plain JavaScript)

Building wasm from rust

We wont be able to use the rust file or the output binary directly onto the web project because JavaScript doesn't understand rust nor it understands the binary.

In short:

We will tell rust to output a wasm binary instead of the machine binary. wasm in short is a web assembly binary that modern browsers can run. To know more about web assembly visit: http://webassembly.org/

so the flow would look something like this:

src/lib.rs -> web.wasm and then JS + wasm -> output

To do this lets install few dependencies first:

rustup update
rustup target add wasm32-unknown-unknown
cargo install --git https://github.com/alexcrichton/wasm-gc

This will install a compiler target wasm32 which will be used to natively compile our rust file to wasm format.

Along with this we also install wasm-gc .Think of wasm-gc as something similar to minification/dead code removal.

Now lets compile

rustc --target wasm32-unknown-unknown --crate-type=cdylib src/lib.rs -o web.big.wasm
wasm-gc web.big.wasm web.wasm

Note: If you prefer using cargo to do the build:

  1. Modify the Cargo.toml file and add the following
[package]
...
...
[lib]
crate-type = ["cdylib"]
[dependencies]
...
...

2. Remove the file src/main.rs otherwise this will not compile since we changed the crate-type to cdylib.

3. run cargo build --target wasm32-unknown-unknown --release

You should get an output like this:

➜  web git:(master) ✗ > cargo build --target wasm32-unknown-unknown --release
Compiling web v0.1.0 (file:///Users/atulr/Projects/Hobby/rust/projects/web)
Finished release [optimized] target(s) in 0.73 secs

4. You should find the web.wasm file at web/target/wasm32-unknown-unknown/release/web.wasm

Integrating wasm with JavaScript

Lets create a new html/css/js project.

  1. mkdir webwasm && cd webwasm
  2. touch index.html index.js
  3. mkdir assets
  4. mv path/to/the/web.wasm ./assets/web.wasm

After doing this you should have a folder structure as follows:

 webwasm
├── assets
│ └── web.wasm
├── index.html
└── index.js

Add the following content to webwasm/index.html

<html>
<head>
<title>Web assembly test</title>
</head>
<body>
<script src="./index.js" type="text/javascript"></script>
</body>
</html>

And add the following content to webwasm/index.js

((function(){
  console.log('loading...');
fetch('./assets/web.wasm')
.then(response => response.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes, {}))
.then(wasmContainer => {
const {add,subtract,multiply} = wasmContainer.instance.exports;
console.log('4 + 2 = ', add(4, 2));
console.log('4 - 2 = ', subtract(4, 2));
console.log('4 * 2 = ', multiply(4, 2));
}).catch(err=>console.log(err));
})())

Lets understand this line by line.

((function(){
...
...
...
})())

This is a immediately invoked function (iife). We used this to run our code without polluting the global scope. Although you can safely skip this part also.

fetch('./assets/web.wasm')
.then(response => response.arrayBuffer())

Here we fetch our web.wasm file as an arrayBuffer

.then(bytes => WebAssembly.instantiate(bytes, {}))

As per the MDN documentation

The primary overload takes the WebAssembly binary code, in the form of a typed array or ArrayBuffer, and performs both compilation and instantiation in one step. The returned Promise resolves to both a compiled WebAssembly.Module and its first WebAssembly.Instance.

In essence this gives us a webassembly instance using which we can run the exported modules.

.then(wasmContainer => {
const {add,subtract,multiply} = wasmContainer.instance.exports;
console.log('4 + 2 = ', add(4, 2));
console.log('4 - 2 = ', subtract(4, 2));
console.log('4 * 2 = ', multiply(4, 2));
}).catch(err=>console.log(err));

Here, wasmContainer is basically a webassembly instance.

The exported functions can be found at wasmContainer.instance.exports

const {add,subtract,multiply} = wasmContainer.instance.exports;
console.log('4 + 2 = ', add(4, 2));
console.log('4 - 2 = ', subtract(4, 2));
console.log('4 * 2 = ', multiply(4, 2));

This one is pretty straightforward. It just runs the logic in the web-assembly instance and returns the value back to JavaScript.

To run our index.html file we need to use a http-server.

Just install

npm install -g http-server or

yarn global add http-server

Now in cd to the webwasm directory and then do:

http-server

You should see:

➜  webwasm > http-server
Starting up http-server, serving ./
Available on:
http://127.0.0.1:8080
http://10.31.194.120:8080
Hit CTRL-C to stop the server

Now open up http://127.0.0.1:8080 on your browser.

Running rust modules in JS using Webassembly

Open up the console and you should see the result.

Boom! Power of rust on the frontend.💪🏻 🌮

This was a lot of manual work:

  • Build wasm
  • Then copy over the generated wasm to the assets folder, etc.

To solve these take a look at a more streamlined process using webpack in the next post

The following topics will be covered in the upcoming posts. Stay tuned!

Source Code:

https://github.com/master-atul/rust-module-in-javascript-blog-series

References: