Bringing WebAssembly to React Native With Polygen

We are proud to announce the first public release of Polygen, our innovative toolkit for running WebAssembly in React Native applications, with near-native performance.

Introduction

React Native is an extraordinary piece of technology. It allows you to use JavaScript and TypeScript—the languages we all know—together with React to build performant mobile applications. Contrary to other solutions on the market, React Native leverages native UI components for each platform, making the application fit the ecosystem and the platform’s appearance rather than recreating it.

Let’s not forget that it started as a library for creating single-page applications (SPAs) on the Web. It quickly gained traction and support in the community due to its simplicity and unique approach for working with layout changes—Virtual DOM was something only few people heard of back then.

Today, we can create both native applications and web apps using a single (assuming the usage of react-native-web) codebase using React. We can share business logic and have separate UI layers, or share everything using react-native-web. This gives us a great developer experience, fast iteration times, and faster time to market.

React Native makes it easy to create a mobile application for an already existing React web app by sharing logic code and the other way around, creating a web app from your React Native application. Both journeys are great starting points, but there are quite a few missing pieces and rough edges that we, as a community, are constantly working to fix.

The problem

Unfortunately, there’s one thing that you can stumble upon when trying to bring your React Web App to React Native. If your Web App is working with heavy computations or any other CPU intensive tasks, you are probably using an external library using WebAssembly, as it is the only thing in the Web toolkit that is available. This is what enables efficient 3D graphics in the browser (among other things).

However, there was no easy way to run them in React Native until now. When you wanted to migrate them to React Native, you had two choices:

Use react-native-webassembly

This is probably the easiest approach. This package provides a WebAssembly runtime powered by wasm3. While it works, it has the overhead of interpreting the modules. If you seek performance, this might be a no-go, depending on the module you are trying to run.

You cannot use a faster runtime that performs just-in-time (JIT) compilation of those modules, because it is not allowed on Apple mobile platforms.

Create a Turbo Module for each external dependency

This is the most performant solution, as you have access to native platform APIs. You can, for example, spin up native threads for heavy computations to achieve the best performance. However, you need to write (and most importantly, maintain) the Turbo Module for each native dependency, which includes exposing C or C++ API that can be called from Turbo Modules and bridging the API to React Native using JSI. That’s a lot of work.

Introducing Polygen

At Callstack, we were not satisfied with the available options. While we are huge fans of cross-platform code sharing, we don’t want to sacrifice runtime performance for end-users.

After weeks of work, we are introducing to the React Native Community a new approach for this problem: Polygen, a toolkit for creating native React Native Modules from WebAssembly modules.

How it works

While most WebAssembly runtimes interpret the modules or perform just-in-time compilation at application runtime, Polygen takes a novel approach of compiling the modules to native code (C code specifically), which becomes part of the application itself during compilation and linking.

Each module is compiled ahead of time, providing near-native performance while using the Web API you already know and use.

Consider this simple WebAssembly module, which exports a single function add, that returns the sum of two numbers:

(module
(export "add" (func $module/add))
(func $module/add (param $0 i32) (param $1 i32) (result i32)
local.get $0
local.get $1
i32.add
)
)

To use it in React Native using Polygen, you consume it as you would on the Web (with the sweet metro bundler integration):

// Easy Metro integration
import myModule from './my-module.wasm';
// Load module using WebAssembly WebAPI
const module = new WebAssembly.Module(myModule);
const instance = new WebAssembly.Instance(module, {});
// Get typed reference to add function
type AddFunc = (a: number, b: number) => number;
const add = instance.exports.add as AddFunc;
// Call the function
console.log(`add(1, 2) = ${add(1, 2)}`);

Because the code is precompiled, you are free to use it in the App Store ecosystem, where just-in-time compilation is prohibited.

The possibilities

WebAssembly is a well-defined binary target supported by multiple languages. It is also platform and architecture-agnostic, which makes it a perfect fit for the multi-platform environment React Native.

This allows us to:

  • make code written in different languages (e.g., Rust) to be easily embedded and used in React Native applications,
  • share and consume precompiled libraries without building them from source,
  • simplify native modules in out-of-tree React Native platforms,
  • sandbox external modules.

The limitations

Not everything is perfect yet. There are some limitations to be aware of:

  • No dynamic modules—Because of the approach taken, you cannot load arbitrary modules and execute them at runtime. Each WASM module must be known and defined ahead of time.
  • Execution on JS thread—The WebAssembly functions you call are being executed on a JS thread, which may block it. In the future, this could be solved by adding support to Web Workers. We are also actively researching alternative solutions to this problem using alternative APIs.
  • WebAssembly is in the early stages—WebAssembly is a young standard. It is supported in all major browsers today and the modules are already faster than analogous JavaScript.

It is not quite there yet compared to native code, though. The instruction set and environment are limited, which may negatively impact performance. Due to those limitations, some languages cannot be reliably compiled to WebAssembly. Compiling native code to WebAssembly usually results in a slightly slower execution time.

The good news is that this will only get better with time. There are numerous proposals to enhance the WebAssembly standard with new instructions and new functionalities, which will lower the impact of the mentioned issues.

How to start

Head to our Polygen repo and follow the README to get started.

What’s next

We are constantly working on improving Polygen, so there are a few things that are not there yet but will be in the near future. These include (but are not limited to):

  • Android Support—Right now, only iOS is supported because Polygen integrates with CocoaPods. We are working on integrating Gradle in the upcoming releases.
  • WASI support—Most WebAssembly modules expect some kind of system interface that needs to be provided by the embedder (you). WebAssembly System Interface is just that—a collection of interfaces that can be used to communicate with the host system. Today, when you use modules that depend on WASI, you must provide them manually, and due to React Native Runtime, you cannot easily use Node packages that provide WASI.
  • Better support for External Languages output—Each language that compiles to WebAssembly usually also generates some JavaScript (or TypeScript) glue code. The code simplifies usage of the module by wrapping the WebAssembly WebAPI and exposing typed functions. Due to multiple approaches to bundling in the JS ecosystem and multiple runtimes, generated JS wrappers do not work in some cases because of runtime assumptions (e.g., generating glue code for the node runtime expects the fs module to exist).

Let’s get in touch

Polygen is still in the heavy development phase. If you are interested, make sure to subscribe to our Callstack Incubator newsletter. And, of course, let us know what you think!

Insights

Learn more about Cross-Platform

cover

Feb 27Article

Sparkling: The Missing App Layer for Lynx.js?

A hands-on look at Sparkling for Lynx.js: CLI, native navigation, and the tooling layer that turns Lynx into a full mobile app framework.

cover

Feb 12Live Stream

Sparkling: A New Framework for Lynx

Join the Lynx team live as we explore Sparkling, its boundaries with Lynx, core features, and bootstrap a project from scratch.

cover

Dec 19Article

Visualizing the Dual-Thread Model of Lynx JS

A practical look at Lynx JS’s dual-thread runtime, using real examples to show how main and background threads behave under load.

cover

Dec 2Live Stream

Two Threads of Lynx.js Part 2

Learn how Lynx.js uses a dual-threaded model, how its APIs work, and what it means for performance in this live session with Lukasz Chludzinski and Kewin Wereszczynski.

cover

Nov 27Live Stream

Exploring Lynx.js and Its Two-Thread Runtime

A recap of our live session exploring how Lynx.js splits work across a main thread and a background thread.

cover

Nov 7Article

Exploring Lynx JS: From Quick Start to CLI Workflows

Learn how to set up Lynx JS, explore its ecosystem, and use the Community CLI to build native apps with a familiar JavaScript workflow.

cover

Sep 19Podcast

React Native at Scale With NFL

Discover how the NFL uses React Native, Expo, and a monorepo to ship apps across iOS, Android, web, and TV with one stack.

cover

Aug 5Article

How Node-API Works in React Native: A Deep Dive

A closer look at how Node-API works in React Native, covering addon loading, bundling, the host package, and new build tools like cmake-rn and ferric-cli.

cover

Dec 20Article

From Native to Multiplatform Development: Unpacking Kotlin and Compose

Discover how Kotlin Multiplatform, Jetpack Compose, and Compose Multiplatform compare to React Native. Learn their roles, relationships, and use cases.

cover

Dec 3Article

Aiming for Success in the TV App Development Space With React Native

Learn how to handle platform differences, optimize user interactions, and balance standardization with customization when building a React Native app for TV.

cover

Aug 7Podcast

Open Mobile Hub: Non-GMS and Cross-Platform Possibilities

When you build applications, do you ever consider users who may not have access to Google Mobile Services? They may not be your target audience now, but it's good to be aware of what options you have in store for non-GMS devices — which is exactly what we're talking about in this episode!

cover

May 31Podcast

Stepping Into New Dimension With React Native VisionOS

Try immersing yourself in the React Universe with React Native visionOS! Listen about the latest changes in the framework & watch a 3D app demo.

cover

Mar 19Tutorial

The Hard Way vs. The React Native AI SDK Way: Stop Writing Custom Modules for Every Model

Learn how React Native AI uses a unified provider model to switch between cloud and on-device LLMs without rewriting application logic, enabling reliable AI experiences online and offline.

cover

Mar 12Tutorial

Build Smarter Apps: Tool Calling & AI Orchestration Explained

Learn how on-device models in React Native can call tools, interact with external APIs, and return structured outputs you can use directly in application logic.

cover

Mar 5Tutorial

MLC LLM + React Native: On-Device AI Without the Pain

Learn how to run third-party on-device LLMs in React Native using MLC LLM. Choose your models, run them efficiently across platforms, and keep a unified JavaScript API with React Native AI.

cover

Feb 24Tutorial

Intelligent Fallbacks with Apple Intelligence in React Native

Learn how to use Apple’s built-in Foundation Models in React Native apps, reduce memory usage, and enable fast, fully on-device AI with React Native AI Apple.

cover

Feb 10Tutorial

What Is the React Native AI SDK? A Complete Intro & Quickstart

Learn why on-device AI matters for React Native apps, how local LLMs behave offline, and what problems React Native AI is designed to solve from day one.

cover

Jan 29Tutorial

Implementing an Android TurboModule with Kotlin

Learn how to implement a React Native TurboModule on Android, from the TypeScript spec and Codegen to the Kotlin implementation and package registration.

cover

Jan 19Tutorial

Working with Different Threads in Swift TurboModules

Learn how JavaScript, native module, background, and main threads interact in React Native TurboModules, and how to use GCD queues to run async work safely in Swift.

cover

Jan 9Tutorial

How to Add Type-Safe Constants to Swift TurboModules

Learn how to expose typed, immutable constants from a Swift TurboModule using Codegen, and get full end-to-end type safety between native and JavaScript.

cover

Dec 30Tutorial

Adding Event Emitters to Your TurboModule in Swift

Learn how to emit native events from a Swift TurboModule and subscribe to them in JavaScript, including proper memory management and Codegen integration.

cover

Dec 22Tutorial

Writing Your First TurboModule in Swift

Learn how to build a fully working TurboModule in Swift, integrate it with Codegen, and bridge it through Objective‑C inside an Expo app.