Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Getting started with WASM

This extends the Step-by-step tutorial with React Native. We’ve split this out running the library under WASM involves creating an expo app.

Preparing the library

Add a script to the package.json, if you haven’t already:

    "script": {
+     "ubrn:web": "ubrn web build"
    },

Also, add the entrypoint for browsers:

+  "browser": "src/index.web.ts",
+  "react-native": "src/index.tsx",

You can ensure that the bindings get generated specifically for both react-native and the web, by changing the ubrn.config.yaml file.

rust:
  repo: https://github.com/jhugman/uniffi-starter.git
  branch: jhugman/bump-uniffi-to-0.29
  manifestPath: rust/foobar/Cargo.toml
+ web:
+   ts: src/generated/web
+ bindings:
+   ts: src/generated/rn

Once these changes are done, then you can run:

yarn ubrn:web

This does a number of things, but you end up with:

  • an entrypoint file src/index.web.ts
  • a bindings file called src/generated/web/foobar.rs
  • some wasm-bindgen generated files in src/generated/web/wasm-bindgen:
    • index_bg.wasm
    • index.js
    • index.d.ts
    • index_bg.wasm.d.ts

Now, you should be ready to write an example app.

Making the example app

I’m going to make use expo to make an example app in the directory next to our my-rust-lib directory.

export dir=my-wasm-app
yarn \
    create \
    expo-app \
    --template blank-typescript \
    --yes \
    $dir
cd $dir

We’ll need to install the react-native-web libraries, and associated bits that converts the React Native JSX to Web JSX, which in turn converts to a sea of divs.

npx expo install \
    react-dom \
    react-native-web \
    @expo/metro-runtime

Then, add our my-rust-lib library.

yarn add ../my-rust-lib

Help wanted

I don’t really understand how npm and yarn do linking or workspaces.

Doing yarn add ../my-rust-lib copies everything into the node_modules directory of the example, which is less than ideal.

If you know a better way, please open a PR. Help!

Write a demo

import { StyleSheet, View, Text } from 'react-native';
-import { multiply } from 'react-native-my-rust-lib';
-
-const result = multiply(3, 7);

export default function App() {

Next, add the following lines in place of the lines we just deleted:

import { Calculator, type BinaryOperator, SafeAddition, ComputationResult } from 'my-rust-lib';

// A Rust object
const calculator = new Calculator();
// A Rust object implementing the Rust trait BinaryOperator
const addOp = new SafeAddition();

// A Typescript class, implementing BinaryOperator
class SafeMultiply implements BinaryOperator {
  perform(lhs: bigint, rhs: bigint): bigint {
    return lhs * rhs;
  }
}
const multOp = new SafeMultiply();

// bigints
const three = 3n;
const seven = 7n;

// Perform the calculation, and to get an object
// representing the computation result.
const computation: ComputationResult = calculator
  .calculate(addOp, three, three)
  .calculateMore(multOp, seven)
  .lastResult()!;

// Unpack the bigint value into a string.
const result = computation.value.toString();

Initializing the WASM in the web page

Next, we need to update the timing of App registration.

We need to edit example/input.js:

import { AppRegistry } from 'react-native';
import App from './src/App';
import { name as appName } from './app.json';
+import { uniffiInitAsync } from "my-rust-lib";

+uniffiInitAsync().then(() => {
+   AppRegistry.registerComponent(appName, () => App);
+});
- AppRegistry.registerComponent(appName, () => App);

You may also initialize the WASM in a useEffect block.

Teaching Metro about WASM files

You may have to show your bundler what to do with WASM file—just serve them as binary data files.

// Learn more https://docs.expo.io/guides/customizing-metro
const { getDefaultConfig } = require('expo/metro-config');

/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname);

// Add wasm asset support
config.resolver.assetExts.push('wasm');

// Add COEP and COOP headers to support SharedArrayBuffer
config.server.enhanceMiddleware = (middleware) => {
  return (req, res, next) => {
    res.setHeader('Cross-Origin-Embedder-Policy', 'credentialless');
    res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
    middleware(req, res, next);
  };
};

module.exports = config;

The operative part of this configuration is marking wasm files as assets.

config.resolver.assetExts.push('wasm');

Running in a page

Running yarn web should now open a web page showing the result to be 42.

Getting started without React Native

Outside of the React Native ecosystem, the important two steps are:

  • asynchronously initializing the WASM bundle, using uniffiInitAsync
  • getting your bundler to allow asynchronous serving of WASM files.

For Webpack

The wasm example in the webpack repository is instructive here.

The operative step is to set experiments.asyncWebAssembly = true in your current WASM config.

Help wanted

I’m really not a real web developer, so would very much appreciate help with this documentation from someone who is.