Awesome Rust Libraries with Wasm Pack
Wasm Pack is a tool for compiling Rust libraries into WebAssembly modules, making them compatible with modern web technologies.
This article explores an architecture that I found to be really useful and how it can be applied to other projects.
The Problem
Reusing code is a big problem in the web development world.
However, when you're building web3 apps, sometimes you end up reimplementing the same logic over and over again, or even worse, re-creating formulas with javascript and it's numeric precision problems.
The Solution
One solution I found is to use Rust with Wasm Pack to compile the logic into WebAssembly and then use it in the browser.
Actually, you can see this architecture in action in my magic-curves library.
Here, I'm going to show you how I implemented it.
The Architecture
The most important thing here is to make use of rust workspaces so you can avoid shipping unnecessary wasm modules and code to rust clients and only ship the wasm modules where needed.
First, let's create a workspace:
mkdir wasm-math
cd wasm-math
touch Cargo.toml
Then, we add the following to the Cargo.toml
file:
[workspace]
resolver = "2"
members = ["./crates/*"]
At this point, if you're familiar with the JavaScript ecosystem, you may see how this is really similar to a monorepo setup with either pnpm or yarn workspaces.
Now, let's add a package to the workspace:
cargo new --lib crates/wasm-math-lib
This will create a new library in the crates
directory.
You'll notice that the library is added to the workspace members list, so
let's clean up the Cargo.toml
file by removing the wasm-math-lib
from the
members list.
[workspace]
resolver = "2"
- members = ["./crates/*", "crates/wasm-math-lib"]
+ members = ["./crates/*"]
Now, if you explore the rust file that was generated, you'll see that it's a simple library that adds two numbers together.
pub fn add(left: usize, right: usize) -> usize {
left + right
}
This is great, now, let's add a new binary to the workspace, using wasm-pack:
First, let's install wasm-pack:
cargo install wasm-pack
Now, let's create a new binary:
cd crates
wasm-pack new wasm-math-web
This will create a new wasm module in the crates/wasm-math-web
directory,
it will feature some boilerplate code, but you can see that it's simple.
Let's add the wasm-math-lib
dependency to the wasm-math-web
package:
[dependencies]
wasm-math-lib = { path = "../wasm-math-lib" }
Now, let's add the following to the src/lib.rs
file in the wasm-math-web
directory:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn add(a: u64, b: u64) -> u64 {
wasm_math_lib::add(a, b)
}
Note: wasm_bindgen
is a macro that generates the JavaScript bindings for the
wasm module, there's a lot of docs here
Now, let's build the wasm module:
cd crates/wasm-math-web
wasm-pack build
This will create a pkg
directory with the wasm module and a wasm-math-web.d.ts
file, which is the typescript definitions for the wasm module.
You're going to see something like this:
/* tslint:disable */
/* eslint-disable */
/**
* @param {bigint} a
* @param {bigint} b
* @returns {bigint}
*/
export function add(a: bigint, b: bigint): bigint;
Ideally, you publish your wasm lib for rust clients on crates.io.
cd .. # Go back to the wasm-math directory
cargo publish -p wasm-math-lib
This way you'll publish your rust client.
Now, for your npm clients, you can publish your wasm package:
cd crates/wasm-math-web # Go to the wasm-math-web directory
wasm-pack publish
The Result
Now, if you're using a Rust library in a JavaScript project, you can simply use the wasm module, either by loading it or using plugins on your target framework.
For example, in a Vite React project, you can use the wasm module like this:
vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
import wasm from 'vite-plugin-wasm';
import topLevelAwait from 'vite-plugin-top-level-await';
export default defineConfig({
plugins: [react(), wasm(), topLevelAwait()],
});
If you do this, you'll be able to use the wasm module in your React project directly like this:
import { add } from 'wasm-math-web';
console.log(add(1, 2));
Now, you can use the wasm module in your React project.
Conclusion
This is a really simple example, but you can see how this can be applied to other projects by providing a multi-package workspace and then using the wasm-pack tool to compile the wasm module for each package.
This way, you can have a really nice separation of concerns and you can reuse code between your Rust and JavaScript projects.
I really hope this article was helpful for you, and if you have any questions, please reach out to me on Twitter.