๐ธ๏ธ 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:
- Part 1 โ Using rust modules on front-end (Plain JavaScript) (this post)
- Part 2 โ Using rust modules on front-end (Webpack) (https://medium.com/@atulanand94/using-rust-modules-for-javascript-web-development-part-2-7a86eaec5ee9)
- Part 3 โ Using rust modules on back-end (NodeJS). (https://medium.com/@atulanand94/using-rust-modules-for-javascript-web-development-part-3-3-nodejs-7c71e4ae23fe)
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.
- Create a new project:
cargo new --lib web
cd web
- 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.wasmwasm-gc web.big.wasm web.wasm
Note: If you prefer using cargo to do the build:
- 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.
mkdir webwasm && cd webwasm
touch index.html index.js
mkdir assets
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 compiledWebAssembly.Module
and its firstWebAssembly.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.
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!
- Using rust modules on front-end (Webpack)
https://medium.com/@atulanand94/using-rust-modules-for-javascript-web-development-part-2-7a86eaec5ee9 - Using rust modules on back-end (NodeJS).
https://medium.com/@atulanand94/using-rust-modules-for-javascript-web-development-part-3-3-nodejs-7c71e4ae23fe
Source Code:
https://github.com/master-atul/rust-module-in-javascript-blog-series