WebAssembly example with React and SwiftWasm

WebAssembly example with React and SwiftWasm

To get started you need the following tools available:

  • create-react-app (to be able to bootstrap a React app)
  • Swift (to be able to compile Swift Code)
  • SwiftWasm (and optionally wasmer or another WebAssembly runtime)
  • your favorite IDE (I use Visual Studio Code)
  • a cup of coffee

All of this code is available in the public repo.

Setting up the React app

Pretty simple, pretty straight forward we use create-react-app to bootstrap a React app.

npx create-react-app . --template cra-template-pwa-typescript

I use two options here as you might noticed:

  • The . instead of "my-app-name" - this creates the React app in the current folder
  • --template cra-template-pwa-typescript - this template bootstraps all settings for a TypeScript based Progressive Web App with React

Just check if everything works:

yarn start

and open http://localhost:3000 you should see the lovely

Setting up SwiftWasm

As SwiftWasm is as the time of writing (December 2020) not part of the official Swift repository you have to download the latest release (with December 2020 - 5.3.1) from their website and install it by hand. But it's quite simple:

  • Download latest (stable) release from
    SwiftWasm Releases.
  • Install it for your OS
  • Set the path to be able to start the toolchain from your command line:
export PATH=/Library/Developer/Toolchains/swift-latest.xctoolchain/usr/bin:"${PATH}"

To check if everything works fine just type

swift --version
SwiftWasm Swift version 5.3 (swiftlang-5.3.0)
Target: x86_64-apple-darwin19.6.0

You should see something similar.

Getting Started with SwiftWasm

Ok let's do a pretty basic example to see if everything works as expected.

echo 'print("Hello from SwiftWasm!")' > hello.swift

Compile this (rather basic) swift code to WebAssembly bytecode

swiftc -target wasm32-unknown-wasi hello.swift -o hello.wasm

Great! We have our first WebAssembly generated from Swift-Code 🚀

But how do we run it?

WebAssembly Runtime

The easiest way to run WebAssembly bytecode is wasmer: https://wasmer.io/

BUT: Before you download the runtime ride away - there is a "bigger" package available that might fight your needs better: Carton (which uses wasmer internally as runtime afaik).

So let's install carton - a "watcher, bundler and test-runner for SwiftWasm-apps"

brew install swiftwasm/tap/carton

As carton installs wasmer as a dependency we can run our previously generated WebAssembly bytecode right away:

wasmer hello.wasm
Hello from SwiftWasm

Exposing a function from Swift Web Assembly to JavaScript

Ok as we are all set let's write some code that actually works between WebAssembly and our React Web App.

Start off with the Swift code we want to expose to the React app.

Implement this code in add.swift:

@_cdecl("add")
func add(_ lhs: Int, _ rhs: Int) -> Int {
    return lhs + rhs
}

A pretty straight forward swift function taking two arguments and returning a result of type Int.

The @_cdecl("add") is an attribute that exposes the function for the host environment - in our example the WebAssembly. Cool!

That's all on the Swift side.

Let's compile it to the WebAssembly:

$ swiftc -target wasm32-unknown-wasi add.swift -o add.wasm -Xlinker --export=add

This compiles the Swift file to a add.wasm WebAssembly. That's it for the Swift side. Simple!

Importing WebAssembly in React

Let's create a new component in our plain vanilla React app which actually handles the loading of the WebAssembly:

The complete source code of the React component can be found in the repo.

  const wasmFs = new WasmFs();

  let wasi = new WASI({
    args: [],
    env: {},
    bindings: {
      ...WASI.defaultBindings,
      fs: wasmFs.fs,
    },
  });

  const loadWasm = async () => {
    const response = await fetch(wasmFilePath);
    const wasmBinary = await response.arrayBuffer();

    // Create the WASM instance
    const { instance }: any = await WebAssembly.instantiate(wasmBinary, {
      wasi_snapshot_preview1: wasi.wasiImport,
    });
    // Get the exported function
    const addFn = instance.exports.add;
    setResult(addFn(first, second));
  };

At first we instantiate a new WASI and WasmFs instances.

This is rather straight forward from the wasmer-docs.

Something special is the loading of the WebAssembly itself in the async function loadWasm():

To instantiate the WebAssembly-Binary the WebAssembly.instantiate()-function requires an ArrayBuffer. Nothing special but we needed to figure that out as it was not straight forward.

After finishing the instantiation of the WebAssembly in the React async function we are ready to access the exposed function with

instance.exports.add

The TypeScript code can then access the addFn and call it like a TypeScript-function().

Let's call it in React and click the Add-button

Super smooth!

Check out the full source code in the GitHub-repo.

Thanks for reading and happy coding!

Liked the story? Stay up to date and follow us on Twitter.