Type Casting and Compiling a C++ Algorithm into a WebAssembly Module for Execution in a Web Worker within a React Application

Jul 2, 2025

#webassembly #cpp #web-workers #react

demo

This blog walks through an example of how to properly compile a C++ based algorithm into WebAssembly format, establish communication between a React application and the compiled WebAssembly module, and execute the WebAssembly code in a Web Worker without blocking the main thread.

Implementing the C++ based WebAssembly Module

We use a basic C++ algorithm that calculates the Fibonacci sequence as an example, simple enough to follow, yet sufficient to demonstrate how to cast C++ vectors for compatibility with JavaScript arrays.

The implementation is straightforward, the function accepts two parameters: a vector of existing integers and the number of Fibonacci elements to compute. It returns the calculated sequence as a std::vector<int>.

// fib.cpp
#include <vector>

std::vector<int> calculateFibonacci(const std::vector<int>& currSeq, int count) {
    std::vector<int> result = currSeq;
    if (count <= result.size()) {
        result.resize(count);
        return result;
    }
    if (result.empty()) {
        result.push_back(0);
        if (count > 1) result.push_back(1);
    } else if (result.size() == 1) {
        result.push_back(1);
    }
    while (result.size() < count) {
        int n= result.size();
        result.push_back(result[n-1] + result[n-2]);
    }
    return result;
}

We use the Emscripten toolchain to compile the C++ code into WebAssembly. The emscripten::val API is used to receive the JavaScript array input from the client and convert it into a C++ vector for use in the algorithm. Once the calculation is complete, the resulting C++ vector is converted back into a emscripten::val object, producing a JavaScript-compatible array that can be recognized in the browser.

// fib.cpp
#include <emscripten/bind.h>
#include <emscripten/val.h>

emscripten::val calculateFibonacciJS(emscripten::val currSeq, int count) {
    std::vector<int> currSeqVec;
    unsigned len = currSeq["length"].as<unsigned>();
    for (unsigned i = 0; i < len; ++i) {
        currSeqVec.push_back(currSeq[i].as<int>());
    }
    std::vector<int> result = calculateFibonacci(currSeqVec, count);
    emscripten::val jsArray = emscripten::val::array();
    for (size_t i = 0; i < result.size(); ++i) {
        jsArray.set(i, result[i]);
    }
    return jsArray;
}

To make the C++ code callable from JavaScript, we use the EMSCRIPTEN_BINDINGS macro to expose the desired function to the JavaScript runtime.

// fib.cpp
EMSCRIPTEN_BINDINGS(fib_module) {
    emscripten::function("calculateFibonacci", &calculateFibonacciJS);
}

Next, we use the following emcc command to compile the fib.cpp file into WebAssembly:

emcc -I. -o fibAlgoModule.js -O3 -s ENVIRONMENT=web -s MODULARIZE=1 -s ALLOW_MEMORY_GROWTH=1 -s EXPORT_NAME=genFibAlgoModule --bind fib.cpp
  • -O3 enables the highest level of optimization for speed, improving the performance of WebAssembly calls from JavaScript.

  • -s ALLOW_MEMORY_GROWTH=1 allows the WebAssembly module’s memory to grow dynamically at runtime.

  • -s ENVIRONMENT=web specifies that the target environment is the web.

  • -s MODULARIZE=1 wraps the generated JavaScript code in a function that returns a Promise-based module.

  • -s EXPORT_NAME=genFibAlgoModule sets the name of the function that returns the module to genFibAlgoModule.

The command compiles fib.cpp into two files: fibAlgoModule.wasm, which contains the WebAssembly binary, and fibAlgoModule.js, which serves as the JavaScript glue code that bridges JavaScript and WebAssembly.

To integrate this module with our React application, we may need to manually add a default export to fibAlgoModule.js.

// fibAlgoModule.js 
export default genFibAlgoModule;

Executing the algorithm in a Web Worker

Later, in our Web Worker code, we import the genFibAlgoModule function to instantiate the WebAssembly module. Once the worker receives the Fibonacci sequence length from the main thread, it invokes the calculateFibonacci function.

Once the calculation is complete, the worker posts the result back to the main thread for display.

// fibonacciWorker.js
import genFibAlgoModule from "./fibAlgoModule";

self.onmessage = async function (e) {
	const n = e.data;
	const defaultSeq = [1,1]

	const module = await genFibAlgoModule();
	const result = module.calculateFibonacci(defaultSeq, n);

	self.postMessage(result);
};

Integrating from a React Application

The React code is fairly straightforward: we initialize a Web Worker when the component mounts and use a slider to send the Fibonacci sequence count to the worker.

We use a ref to store the worker instance, which prevents it from being recreated on re-renders. The worker listens for the result of the calculation and updates the fibSeq state accordingly when the computation completes.

I'm using Vite to scaffold the application, which allows direct importing of the Web Worker. This behavior may differ in other frameworks such as Next.js or Remix.

// App.tsx
import { useState, useEffect, useRef } from "react";
import "./App.css";
import FibonacciWorker from "./fibonacciWorker.js?worker";

function App() {
	const [count, setCount] = useState(2);
	const [fibSeq, setFibSeq] = useState<number[]>([1, 1]);
	const workerRef = useRef<Worker | null>(null);

	useEffect(() => {
		// create the worker only once
		workerRef.current = new FibonacciWorker();
		return () => {
			workerRef.current?.terminate();
		};
	}, []);

	useEffect(() => {
		if (!workerRef.current) return;
		workerRef.current.onmessage = (e: MessageEvent) => {
			setFibSeq(e.data);
		};
		workerRef.current.postMessage(count);
	}, [count]);

	return (
		<div
			className="App"
			style={{
				width: 600,
				margin: "2rem auto",
				padding: 24,
				border: `3px solid #ccc`,
				borderRadius: 12,
				transition: "border 0.2s",
			}}
		>
			<h1 style={{ fontSize: "26px" }}>Fibonacci Sequence Calculator</h1>
			<label style={{ fontSize: "20px" }} htmlFor="fib-slider">
				Number of elements: (
				<span style={{ color: "orange" }}>input from main thread</span>
				): <b>{count}</b>
			</label>
			<input
				id="fib-slider"
				type="range"
				min={2}
				max={100}
				value={count}
				onChange={(e)=> setCount(Number(e.target.value))}
				style={{
					width: "100%",
					margin: "16px 0",
					border: `3px solid #ccc`,
					borderRadius: 8,
					transition: "border 0.2s",
				}}
			/>
			<div style={{ marginTop: 24 }}>
				<p style={{ fontSize: "20px" }}>
					Sequence: (
					<span style={{ color: "orange" }}>
						computed in worker thread
					</span>{" "}
					<span style={{ color: "orchid" }}>in WASM</span>)
				</p>
				<div style={{ wordBreak: "break-all", fontSize: 18 }}>
					{fibSeq.join(", ")}
				</div>
			</div>
		</div>
	);
}

export default App;