This package enables WebAssembly for React Native powered by C++ TurboModules and Wasm3, a fast and universal WebAssembly runtime.
react-native-webassembly
provides React Native applications with the capability to execute universal Wasm binaries with native speed.
βοΈ This project is still in active development. The following tasks are still remaining to be completed:
- Sanitize C++ memory management practices.
- Normalize execution and result handling of userland
export
functions.- Test framework implementation.
-
First, ensure your React Native application supports the New Architecture:
-
Install
react-native-webassembly
:yarn add react-native-webassembly # React Native npx expo install react-native-webassembly # Expo
-
If you're using Expo, don't forget to run
npx expo prebuild
after installing.
The goal of react-native-webassembly
is to export a browser-equivalent interface to the WebAssembly API.
To initialize a new WebAssembly module, we'll need to instantiate
an module using a buffer populated with a .wasm
binary:
import axios from 'axios';
import * as WebAssembly from 'react-native-webassembly';
import HelloWorld from './hello-world.wasm';
const module = await WebAssembly.instantiate<{
add: (a: number, b: number) => number;
}>(HelloWorld);
Note
To import
.wasm
files directly, you will need to update yourmetro.config.js
.
Alternatively, in the snippet below, we show how to download and instantiate the reference Hello World example stored at a remote location:
import axios from 'axios';
import * as WebAssembly from 'react-native-webassembly';
const {
data: bufferSource,
} = await axios({
url: 'https://github.com/torch2424/wasm-by-example/raw/master/examples/hello-world/demo/assemblyscript/hello-world.wasm',
method: 'get',
responseType: 'arraybuffer',
});
const module = await WebAssembly.instantiate<{
add: (a: number, b: number) => number;
}>(bufferSource);
You'll notice that in our call to instantiate
, we can also pass typing information for the Exports
of the module. In this case, the hello-world.wasm
binary exports a function to add two numbers, add
.
Once configured, we can execute the compiled wasm
module from our JavaScript code, using the type-safe exported interface:
module.instance.exports.add(1, 2); // 3.
It's also possible to declare an importObject
to receive callbacks from the compiled module, which declares a list of callback function implementations which can be invoked by the WebAssembly runtime.
Warning
Some native modules require the presence of certain function implementations. Without specifying module-specific required dependencies, instantiation will fail.
For example, the Circom library converts arithmetic circuits used for generating, evaluating and verifying SNARKs are expressed as WASM modules which require the runtime to define an exceptionHandler
function belonging to the namespace runtime
.
It's simple to define an importObject
:
const module = await WebAssembly.instantiate<{
getVersion: () => number;
getFieldNumLen32: () => number;
// ...
}>(bufferSource, {
// Declare custom memory implementation.
env: {
memory: new WebAssembly.Memory({ initial: 32767 }),
},
// Define the scope of the import functions.
runtime: {
exceptionHandler: (value: number) => console.error(value),
},
});
Here, we declare an exceptionHandler
as runtime
imports to the compiled module. Without declaring this required dependency, the module would fail to compile.
You can find a working implementation of this process in the Example App.
Currently, wasm3
only supports a single memory region. This means that WebAssembly files which contain multiple memory
allocations are not currently supported.
react-native-webassembly
exposes access to the runtime memory element for allocated instances, which is represented using an ArrayBuffer
named memory
. This shares the same backing array as the native runtime.
It can accessed as follows:
const module = WebAssembly.instantiate(...);
const memory: ArrayBuffer | undefined = module.instance.exports.memory;