Assembling React application with Web Assembly
Continuing from my previous article, we now know what web assembly a.k.a WASM is and how easy and beneficial it can be! Now, we step up our game by having WASM work with ReactJS.
Initially, I was able to create and compiled wasm file in c++ and rust, but there was a piece of puzzle missing, which was not allowing me to complete it.
“When you are exhausted from trying, try just one more time.” #Chrisse Feros
I took a sip of coffee, and started thinking about it all over again,
“I am trying to have WASM within React, and the same must be true for you!”
“Which also means that I am good at Javascript, so are you!”
So why not try with AssemblyScript (which is a variant of typescript) with React
“And there I found my missing piece! I will show you how”
I am creating 2 separate projects
- assemblyscript-wasm : It will have file related to our wasm
- react-wasm: It has our react file with a wasm loader.
If you want a single project as against 2 separate ones that I created, feel free to do so.
“So, Let’s start assembling!”
Setting up a project in Assembly script is swift,
Initialize a new project using
npm init
And install both the loader and the compiler mentioned below,
npm install — save @assemblyscript/loadernpm install — save-dev assemblyscript
Now quickly set up a new AssemblyScript project using
npx asinit .
“Don’t forget to copy the . (dot)”
It will create directory structure and configuration files for you.
That’s it, you are now ready to have your functions compiled to WASM.
If you want to know more about the config and directory structure or you are stuck somewhere then AssemblyScript Quick Start section will help you, link
Open index.ts file inside the assembly directory, you will see add function already present,
export function concat(a: i32, b: i32): i32 { return a + b}
we’ll add new function concat to this file
export function concat(a: string, b: string): string { return a + b}
Both the functions are identical, if you remove the type. One is adding numbers and the other is concatenating strings.
You can have other function over here.
“It’s time for you to compile it”
🚀Fire the command!
npm run asbuild
And your optimized .wasm file will be ready for you inside the build folder.
Let’s create our react project now,
npx create-react-app react-wasm
We have to load the wasm file in our react app. So, we need a loader and we have one from AssemblyScript. Install the loader in your react project using below command.
npm install — save @assemblyscript/loader
Now that the loader is ready to use,
Create a file wasmLoader.js and add below code
const loader = require(“@assemblyscript/loader”);
We’ll create a function to load our wasm file whenever our application is first mounted.
export const loadWASM = () => { return loader.instantiate(fetch(“optimized.wasm”)) .then(result => { //storing the response inside a wasm variable for now wasm = result; return true; })
.catch(error => { console.error(‘Error loading WASM file’); return false; })}
Assembly script provides us with different methods to instantiate.
Here, I have used Asynchronous instantiate method, you can have
instantiateSync — Synchronously instantiates an AssemblyScript module from a WebAssembly. Module or binary buffer. Not recommended.
instantiateStreaming — Asynchronously instantiates an AssemblyScript module from a response, i.e. as obtained by fetch.
The loader basically instantiates the module using WebAssembly APIs, but also adds additional utility to it while evaluating export names and making a nice object structure of them.
Result object in our case contains
- Exports (having our functions add and concat)
- Instance
- Module
We are storing the result inside a wasm variable for now.
You can find more about loaders here
Next step is to call this loadWASM() method inside our app component once it is loaded.
So open app.js and insert below code
const [wasmLoaded, setWasmLoaded] = useState(false)useEffect(() => { loadWASM().then(result => setWasmLoaded(result));}, []);
Here, onMount we are fetching the file and on success we are setting flag wasmLoaded.
Now that WASM file is loaded, we can call our WASM function concat and add inside our app.js
useEffect(() => { if (wasmLoaded) { setConcat(concatString(‘React’, ‘WASM’)); setSum(addNumbers(4, 4)); }}, [wasmLoaded])
Here, we are calling two function first concatString and second addNumbers
Let’s define this functions
wasmLoader.js
export const addNumbers = (a, b) => wasm.exports.add(a, b);
“Nothing complicated in the above function. Right!”
Let’s take a look at our concateString function,
export const concatString = (aStr, bStr) => { const { concat } = wasm.exports; const { __allocString, __getString, __retain, __release } = wasm.exports; let aPtr = __retain(__allocString(aStr)) let bPtr = __retain(__allocString(bStr)) let cPtr = concat(aPtr, bPtr) let cStr = __getString(cPtr) __release(aPtr) __release(bPtr) __release(cPtr) return cStr}
At first glance, it seems to be complicated but it is really simple.
Let me try to explain it, the String arguments in our wasm(AssemblyScript) function cannot just be Javascript Strings, it needs to be allocated in the memory module.
Loaders do provide us with the function which we can use here.
So we have __allocString, if you are working with Array then you will have __allocArray.
__retain : retains a reference to a managed object, it is done so that the object does not become collected while we are working with it. We don’t retain a value returned by concat function.
__getString : Copies a string’s value from the module’s memory to a JavaScript string.
__release : release previously retained value
You must be thinking why didn’t we do all this in our add function,
Add function takes i32 which is a basic value and not an object. So don’t retain or release them.
We are almost done, it’s time to have it running.
Remove unwanted code from app.js and your final file will look like this
App.js
import React, { useState, useEffect } from ‘react’;import logo from ‘./logo.svg’;import ‘./App.css’;import { concatString, addNumbers, loadWASM } from ‘./util/AssemblyLoader’;function App() { const [concat, setConcat] = useState(‘’); const [sum, setSum] = useState(); const [wasmLoaded, setWasmLoaded] = useState(false) useEffect(() => { loadWASM().then(result => setWasmLoaded(result)); }, []); useEffect(() => { if (wasmLoaded) { setConcat(concatString(‘React’, ‘WASM’)); setSum(addNumbers(4, 4)); } }, [wasmLoaded])return ( <div className=”App”> <header className=”App-header”> <p>{concat}</p> Gr{sum}! </header> </div> );}export default App;
🚀Fire the command!
npm start
“Oops! I forgot to mention that you need to copy the optimized .wasm file to the public folder of the react-wasm project.”
Copy it and the WASM file will load in your react app.
What did we learn?
You can easily integrate WASM and ReactJs using AssemblyScript. However, it has some trade offs. You can read more about it here and decide for yourself if you need to use it in your project or not. This article talks about how easily you can interact with the WASM function from your react app. It was like opening a Pandora's box for me, hope it won’t be for you now!
“Thanks for reading this article!”