Embed Rust wasm into React
Tags: webassembly, rust, react, javascript
Published: 2020-08-19 21:00:29

WebAssembly is coming out to play. It's time for us, developers, to also move forward and push our heavy lifting computation algorithms into the low-level architecture in our products/libs/components and, together, speed up the web client as a whole.

What should know (at a high-level) before we progress:
- Modern JavaScript;
- React;
- react-app-rewired pipeline;
- Rust;

If you are looking for a non-Rust WASM approach

First, let's keep in mind how WebAssembly actually runs in a web page.



##How does WebAssembly run in a webpage?

WebAssembly is a low-level module with a sandboxed shared memory allocated and managed by the browser.
Today, we instance these WebAssembly modules via JavaScript and can then interop with their shared memory and call exported module functionality.

![WebAssembly runtime](https://dev-to-uploads.s3.amazonaws.com/i/x4u4ay6u7kejesbg49e2.png)

Now we're ready to see how we can make our Rust code take some algorithmic load from our modern React web app.

---

##When to use WASM instead of JS

JavaScript does an epic job in the web platform - it's perfect to describe events, functional flows and passing arbitrary small sets of data around. It has direct runtime scope integration into the window, giving you direct scope interoperability between different JavaScript modules loaded on the page with a simple single-threaded approach.
Perhaps one day we'll find an even better approach, but that is not the goal of WebAssembly.

WebAssembly thrives in heavy data manipulation and complex algorithmic logic and, last but not least, large chains of function calls.
Think about image/video/sound data manipulation, heavy string operations, game/scene object interpolations, etc.

You can read more at [this great article by Koala42](https://koala42.com/using-webassembly-in-your-reactjs-app/), where, near the bottom, he demonstrates how can WebAssembly be much slower or much faster than JS in 2 fairly similar tasks.

> If you are curious, check the difference of `factorize()` and `factorialize_fib()` in both [Javascript](https://github.com/Koala42/webassembly-react/blob/master/src/utils.js) and [Rust](https://github.com/Koala42/webassembly-react/blob/master/wasm-koala-blog/src/lib.rs) in his example source code to understand how is WebAssembly faster for `factorialize_fib()`, but slower than JS on the simpler `factorize()`.

---

##But the stack milord!

Indeed the modern web app is not bare metal anymore. Our JSX+ES7 codebase and 3rd party modules are bundled on-the-fly by complex build pipelines transpiling, collating and cleaning up code into a shinny optimised web app output that we actually don't know much about anymore. 
This process takes away most of the cross-browser/transpile pain, but makes it hard to introduce something new or tweak under the hood.

> Rant: I hope web standards adoption trends and perhaps a standardised react alternative ([Web Components?](https://www.webcomponents.org/)) will one day bring us back to simpler build pipelines, closer to bare metal web and JS...

So, how do we integrate the modern stack with WebAssembly?

##Back to the future

Let's assume you have some kind of react app with a typical create-react-app base template structure. This example purposely showcases the git repo.

```
- myApp
  | - .git/
  | - node_modules/
  | - public/
  | - src/
  | - config-overrides.js
  | - package.json
```

---

---
---

There are **3 different approaches** to integrating WASM modules into your react app:
1. Use a provided wasm module via npm
2. Hook your own local wasm npm module into an app
3. Embed a wasm module directly into your app git repo

---
---

---

###Use case 1. Use a provided wasm module via npm

This option is so simple you may even already be using 3rd party WASM modules without knowing.

You just have to add the published npm module into your `package.json` and use it directly.


```bash
npm install rust-wasm-react-calculator --save

```

Then simply use it in your react source code

```javascript
// import
import { calculate } from "rust-wasm-react-calculator";

// and use
alert(calculate("14+5"));
```

I know, that's way too simple - this is why we should be using WebAssembly today for whatever is meaningful in terms of performance (keep in mind - it's actually *slower* for most common tasks!)


<br><br>

Next, let's see how we can create our own

---

###Use case 2. Hook your own local wasm npm module into an app

First, in order to create and manage your wasm npm module, let's make sure you have `wasm-pack` installed with all permissions it needs

```bash
sudo npm i -g wasm-pack --unsafe-perm=true
```

Then, outside our app codebase, let's create our hello world wasm npm module

```bash
wasm-pack new helloWorld
```

You will get something like

```
- myApp
  | - .git/
  | - node_modules/
  | - public/
  | - src/
  | - config-overrides.js
  | - package.json
- helloWorld
  | - .git/
  | - src/
  | - tests/
  | - ... cargo files, etc
```

You now can see the actual Rust source code generated in `helloWorld/src/lib.rs`. 
The public methods here will be available to be called in JS, and `#wasm-bindgen` takes care of passing things around on our behalf. 
Read more about [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) if you need to know how it works in greater depth.

Our piece of interesting code in `helloWorld/src/lib.rs`:

```rust
#[wasm_bindgen]
pub fn greet() {
    alert("Hello, worldview test!");
}
```

With a quick `wasm-pack build` an actual npm module will be generated into `helloWorld/pkg` - this is a ready-made wasm npm module with all the methods and bindgen stuff in it - much like the `rust-wasm-react-calculator` one we used for the example (1.)

To test it locally with your app, you can import it directly as a local npm module in your `package.json` with

```json
{
  //...
  dependencies: {
    //...
    "helloWorldWasm": "file:../helloWorld/pkg"
  }
}
```

and use it in your app code like

```javascript
// import
import { greet } from "helloWorldWasm";

// and use
greet();
```

> Note: auto-update of the development server will not work out of the box - you will need to first rebuild `helloWorld` and then refresh your app. 


---

---

###Use case 3. Embed a wasm module directly into your app git repo

Finally, we come to the option where you really want to make rust part of your app and its source code.

We start off similarly to 2., by creating our own `wasm-pack` module.

Like before, in order to create and manage your wasm npm module, let's make sure you have `wasm-pack` installed with all permissions it needs

```bash
sudo npm i -g wasm-pack --unsafe-perm=true
```

Then, in the root of your app source, let's create our hello world wasm npm module

```bash
wasm-pack new helloHelper
```

You will get something like

```
- myApp
  | - .git/
  | - node_modules/
  | - public/
  | - src/
  | - config-overrides.js
  | - package.json
  | - helloHelper
     | - .git/
     | - src/
     | - tests/
     | - ... cargo files, etc
```

Next we need to remove `.git` from `helloHelper`. We do not want to have a submodule here, we want to have our helper as part of our main app repo itself.

```bash
rm -rf helloHelper/.git/
```
> Note: It would be much easier if `wasm-pack` would allow us to create a module directly without VCS embedded, I've pointed this out as a [feature request in wasm-pack](https://github.com/rustwasm/wasm-pack/issues/901)

The final step is to hook it up to our react build, for this we will leverage [wasm-pack-plugin](https://github.com/wasm-tool/wasm-pack-plugin). 
Kick off by adding it to your app

```bash
npm i @wasm-tool/wasm-pack-plugin --save
```

Now we will let `wasm-pack-plugin` manage the `wasm-pack build` on our behalf on its own, with its own watcher and outputting the npm module (`pkg`) of `helloHelper` directly into our own app `/src` code. From there the react watcher itself also picks it up automatically and refreshes our app automatically when running locally.

To achieve this we need to hook `helloHelper` into our `config-overrides.js` using `@wasm-tool/wasm-pack-plugin` and make it part of the build. Additionally, we also need to ensure `file-loader` does not attempt to load `.wasm` file on its own.

In my case I am already using `customize-cra`, so I will just add two `config` filter methods of my own, but you could modify `config` directly in the same way.

```javascript
const path = require("path");
const {
  override
} = require("customize-cra");

const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");

module.exports = override(
  // make the file loader ignore wasm files
  config => {
    config.module.rules.find(rule => {
      return (rule.oneOf || []).find(item => {
        if (item.loader && item.loader.indexOf("file-loader") >= 0) {
          item.exclude.push(/\.wasm$/); //exclude wasm
          return true; //ignore remaining rules
        }
      });
    });

    return config;
  },

  //hook up our helloHelper wasm module
  config => {
    config.plugins = (config.plugins || []).concat([
      new WasmPackPlugin({
        crateDirectory: path.resolve(__dirname, "./helloHelper"),
        extraArgs: "--no-typescript",
        outDir: path.resolve(__dirname, "./src/helloHelperWasm")
      })
    ]);

    return config;
  }
);
```

And then we simply use our new local module directly in react:

```javascript
// import
import { greet } from "./helloHelperWasm";

// and use
greet();
```

There you have it - let's `npm start` and let the real fun begin :)