Awesome Rust Libraries with Wasm Pack

Web Rust

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.