Getting started with React + WebAssembly

Alexander Leon
5 min readJul 6, 2021

--

Hey all, Alex here. In part 1 of this WebAssembly mini-series, I’m going to guide you through getting a basic React+WebAssembly app going. Articles I found on the topic were either outdated or required the user to eject the Create-React-App (CRA) in order to modify the webpack config. I avoid ejecting webpack at all costs so as to not get stuck maintaining webpack settings.

Whether you are sticking with an un-ejected CRA or not, the sample code in this article should still work. Today, our WebAssembly code will be compiled from C.

Prerequisites

Since we’re creating a React/WebAssembly app, your machine needs to be able to:

  1. create React apps and
  2. compile WebAssembly code.

Let’s start with #2

The tldr; is that you need emsdk (I believe that’s short for emscriptem sdk). Basically, you need a tool that can compile your code into WebAssembly and Emscriptem is, so far, the best tool for the job. Installing emsdk should be pretty straightforward following their instructions here. If you have an Apple computer that uses the M1 chip, you may have to scour the internet for additional steps to install emsdk. Otherwise, the instructions should work as expected.

For #1, if you don’t have the create-react-app toolchain set up on your machine yet, find the instructions here.

Let’s get a React app running

From your terminal, go ahead and create a React app:

npx create-react-app wasm-react

For the sake of simplicity, I’m avoiding using Typescript in this example, but if you wish to use Typescript, go ahead!

Let’s write some C

We’re going to create a basic addition function in C. This file will be compiled into WebAssembly and then we will call it from our React app. Create a new file src/add.c and copy-paste the following into it:

#include <emscripten/emscripten.h>#include <stdlib.h>EMSCRIPTEN_KEEPALIVE int add(int a, int b){return a + b;}

Not very exciting, I know, but there are some good things in there already to keep in mind. Most notably, see that “EMSCRIPTEN_KEEPALIVE” text at the beginning of the function. Without that text, the Emscriptem compiler will deem it dead code and remove it.

Make a Makefile

At the root of your project, create a file titled Makefile. In this file, copy-paste the following code

src/add.mjs: src/add.cemcc --no-entry src/add.c -o src/add.mjs  \-s ENVIRONMENT='web'  \-s SINGLE_FILE=1  \-s EXPORT_NAME='createModule'  \-s USE_ES6_IMPORT_META=0  \-s EXPORTED_FUNCTIONS='["_add", "_malloc", "_free"]'  \-s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]'  \-O3

This Makefile will compile the C code into WebAssembly. Let’s break down the pieces of this code:

  • emcc: The first notable piece of code is “emcc”. This is a tool from emsdk we installed earlier. Do a quick sanity check and make sure that when you run
emcc

in your terminal, you get a message that is notcommand not found: emcc”. If you did, in fact, get the “command not found” message, go back to your emsdk folder and run:

source ./emsdk_env.sh

That should bring back the emcc command.

  • — no-entry: We’re saying we don’t have an entry point (ie. main()).
  • ENVIRONMENT=’web’: It’s possible for the compiled WebAssembly to run in environments such as web workers, Node.js, webview, but in our example case we just want “web”.
  • SINGLE_FILE=1: This ensures we only generated the add.mjs file and not an unnecessary add.wasm file as well. It’s possible to use the .wasm binary rather than the .mjs file, and there are memory usage benefits to using the binary version of the WebAssembly code, but in this tutorial we are avoiding ejecting CRA, so let’s move along for now.
  • EXPORT_NAME=’createModule’: For whatever reason, Emscriptem by default creates a function named Module that you then call to get your Module instance. That gets confusing, so calling it “createModule” makes it clearer that it’s a callable function and not the Module instance (learn more here).
  • USE_ES6_IMPORT_META=0: In the ideal world, we could leave this function to 1. It’s supposed to help with auto-detecting the WASM module path (source code here). My React’s webpack doesn’t understand import.meta.url though, so I’m turning it off.
  • EXPORTED_FUNCTIONS=’[“_add”, “_malloc”, “_free”]’: Declare what functions you’re exporting. _add is the function we created and _malloc and _free are internal functions that our add.mjs code needs.
  • EXPORTED_RUNTIME_METHODS=’[“ccall”, “cwrap”]’: As the name implies, these are the functions we want to export that can run at runtime. ccall lets you immediately call a WebAssembly function, whereas cwrap lets you first store the function into memory and then call it as many times as you wish.
  • -03: This flag declares how you want to optimize your WebAssembly code. 03 is the most optimized of the options at the expense of longer compilation time and potentially a larger code size (learn more here).

As you can see, there’s a lot going on with that Makefile. It’s important to become familiar with the Emscriptem flags though, because when things are going awry, that Makefile is usually one of the first places you’ll go to when debugging.

Go ahead and run the Makefile. In your terminal, run

make

If that was successful, you should see a new file src/add.mjs. If that’s the case, give your self a pat on the back and let’s finish this.

Bringing it all together

Replace src/App.js with the following code:

import React, { useState, useEffect } from "react";import createModule from "./add.mjs";function App() {  const [add, setAdd] = useState();
useEffect( () => { createModule().then((Module) => { setAdd(() => Module.cwrap("add", "number", ["number", "number"])); }); }, []); if (!add) { return "Loading webassembly..."; } return ( <div className="App"> <p>Let's do some basic addition:</p> <div>123 + 234 = {add(123, 234)}</div> </div> );}export default App;

First, look at the useEffect function. What we’re doing is invoking the createModule function (remember that from our Makefile?), and waiting for its promise to resolve. Its promise will be the Module that has our runtime functions. In this example, we used cwrap rather than ccall so that we could convert our [add, setAdd] hook into a function that calls our WebAssembly function.

Finally, take a look at what our React function is returning:

return (  <div className="App">    <p>Let's do some basic addition:</p>    <div>123 + 234 = {add(123, 234)}</div>  </div>);

By using a state hook to store our WebAssembly function, it is now dead easy to call that “add” function.

Before starting your server, go to package.json and add the following JSON to your eslintConfig:

"ignorePatterns": [  "src/add.mjs"]

Start your server! You should see our basic math function working in the browser.

Unrelated PSA: Looking for a new high paying software development job? Send me your resume to alexleondeveloper@gmail.com and I’ll get back to you!

Kudos! Hope this article was helpful and good luck with your WebAssembly journey. Find the full source code here.

Want to learn how to compile a C library into WebAssembly and then use it in React? Check out Part 2.

--

--

Alexander Leon

I help developers with easy-to-understand technical articles encompassing System Design, Algorithms, Cutting-Edge tech, and misc.