Bare Docs

Migrate to Bare Kit 3

Move a React Native or native app from Bare Kit 2 and react-native-bare-kit 0.16 to Bare Kit 3: link Bare Kit with bare-stow, switch to Bare.IPC, port to the Swift, Kotlin or Python bindings, and add the iOS entitlement.

Bare Kit 3 is a breaking release. react-native-bare-kit 0.17 no longer includes Bare Kit, so new Worklet() throws until the app links Bare Kit itself. The Objective-C and Java bindings are gone, and so is the BareKit global inside the worklet. From Bare Kit 3.1.0, an iOS app that isn't signed with the extended virtual addressing entitlement aborts on a device as soon as a worklet starts.

Bare Kit 3.0.0, released on 2026-10-30, turned Bare Kit into a standalone C library. Its language bindings moved into separate packages, each installed with that language's own package manager. Bare Kit 3.1.0 followed on 2026-11-11 and embeds Bare 1.37.1.

This guide moves an existing app across, whether it's built with react-native-bare-kit or uses the Bare Kit 2 bindings directly. For the full API, see the Bare Kit reference. For each release's changes, see the release overview.

Why this changed

Bare Kit 2 included its own Objective-C and Java bindings, and each release shipped a prebuilt BareKit.xcframework and Android library. react-native-bare-kit embedded a copy of those prebuilds.

Bare Kit 3 keeps only the C library and leaves each language to a package of its own. The app now brings in one copy of Bare Kit, together with the addons and assets of its worklets, so other code in the app can share them. A notification service extension is one example. The pattern itself, a native shell around a Bare core, is unchanged. See One core, many platforms.

What changes

Inside the worklet:

Bare Kit 2Bare Kit 3
BareKit.IPCBare.IPC, the same duplex stream, which also works on Bare Kit 2
BareKit.on('push', …) answers a push from a native hostRequests over Bare.IPC, for example with bare-rpc
The IPC is a pipeThe IPC is an in-process queue. Ending a side is a half-close, and an open IPC keeps the worklet running until both sides have ended it.

In a React Native app:

react-native-bare-kit 0.16 and earlierreact-native-bare-kit 0.17
Includes Bare KitThe app links Bare Kit with bare-stow link --target bare-kit. Without it, new Worklet() throws.
bare-link writes addons to ios/addons and android/src/main/addons of the packagebare-stow link writes a Swift package and a Gradle module with the addons and assets. The package no longer builds those directories.
The assets option, into which a bundle unpacks its assetsA relative filename names a resource of the app, and on Android <name>.assets lists the assets to extract
The host can't half-close the IPC, and doesn't learn the worklet's exit codeworklet.IPC.end() half-closes the host side (0.17.0), and the exit event and worklet.wait() give the exit code (0.17.1)

In a native app:

Bare Kit 2Bare Kit 3
prebuilds.zip from the bare-kit release, with BareKit.xcframework and an Android librarybare-kit-swift from Swift Package Manager, bare-kit-kotlin from JitPack, or bare-kit-python from PyPI
Objective-C BareWorkletConfiguration with memoryLimit and assetsSwift Worklet.Options(memoryLimit:), with no assets
Objective-C BareWorklet: start:source:arguments:, suspendWithLinger:, push:Swift Worklet: start(_:source:arguments:), suspend(linger:), wait(), with no push
Objective-C BareIPC: readable and writable callbacks with synchronous read and write:Swift IPC, an AsyncSequence of Data with write(_:) async throws, end() and close()
Java Worklet.Options, Worklet with push(), and IPC with poll callbacksKotlin Worklet.Options, Worklet with suspend fun wait(), and Ipc with suspend fun write() and a data flow, in the same to.holepunch.bare.kit package
C: bare_worklet_alloc() and bare_worklet_init(), bare_worklet_push()bare_worklet_create() and the rest of Changes from bare-kit 2

Pick a binding

Bare Kit runs Bare as a worklet, a thread inside your app's process. A sidecar runs Bare as a separate process instead. An uncaught error in a worklet aborts the whole app, while a sidecar fails on its own. The sidecar packages are for desktop hosts only. Both kinds reach the host through Bare.IPC.

HostWorkletSidecar
React Native, Exporeact-native-bare-kit, with Bare Kit linked by bare-stow, or expo-bare-kitNone
Swift: iOS 15, macOS 13 or laterbare-kit-swiftbare-sidecar-swift, macOS 13 or later
Kotlin: Android 10 or later, the JVMbare-kit-kotlinNone
Python 3.10 or later: Linux, macOS, Windowsbare-kit-python (pip install bare-kit)bare-sidecar-python (pip install bare-sidecar)
Node.js, ElectronNonebare-sidecar
C or C++The C library, include/bare-kit.hNone

Steps

Upgrade the packages

In a React Native app, upgrade react-native-bare-kit and add bare-stow, which links Bare Kit into the app in the next step. If you linked addons with bare-link before, remove it:

npm i react-native-bare-kit@^0.17.1
npm i -D bare-stow
npm uninstall bare-link

In an Expo app, use expo-bare-kit 0.3.0 or later, the first release that requires react-native-bare-kit 0.17 and links Bare Kit through bare-stow 0.3.

In a native app, remove the BareKit.xcframework or Android library that you copied from a Bare Kit 2 release, and add the binding for your language:

// Package.swift
dependencies: [
  .package(url: "https://github.com/holepunchto/bare-kit-swift", from: "2.3.0")
]

.product(name: "BareKit", package: "bare-kit-swift")
// build.gradle
repositories {
  maven { url "https://jitpack.io" }
}

dependencies {
  implementation "com.github.holepunchto.bare-kit-kotlin:bare-kit-kotlin:v0.2.3"
}
pip install bare-kit

bare-kit-swift 2.3.0, bare-kit-kotlin 0.2.3 and bare-kit-python 0.2.1 build Bare Kit 3.1.0, which embeds Bare 1.37.1. Their version numbers don't follow Bare Kit's. See Bundled Bare version.

React Native only. Pass bare-stow link the entry point of every worklet in the app. It writes a Swift package and a Gradle module that depend on bare-kit-swift and bare-kit-kotlin, and that hold the native addons and assets of those worklets:

npx bare-stow link --target bare-kit --language swift --out ios/BareKitResources worklet.js
npx bare-stow link --target bare-kit --language kotlin --language-option package=com.example.addons --out android/bare-kit-resources worklet.js

Add the Swift package and its bare-stow folder to the app target in Xcode, and include the Gradle module in the Android app. Delete any postinstall script that ran bare-link into node_modules/react-native-bare-kit, because 0.17.0 no longer builds those directories into the app. Run bare-stow link again whenever a worklet's dependencies change. In an Expo app, list the workers in the workers option of expo-bare-kit instead.

If you skip this step, new Worklet() throws:

Bare Kit is not available, so make sure the application includes bare-kit-swift or bare-kit-kotlin (…)

A Swift or Kotlin app without React Native can link the addons of its worklets with the same commands. See Linking addons and assets.

Replace the BareKit global

In every worklet, change BareKit.IPC to Bare.IPC. It's the same stream, so nothing else in the code changes:

// Before
const { IPC } = BareKit
IPC.on('data', (data) => IPC.write(data))

// After
Bare.IPC.on('data', (data) => Bare.IPC.write(data))

Bare.IPC also works on Bare Kit 2, so you can make this change before you upgrade.

If a native host used push to send the worklet one-off requests, which the worklet answered from a push listener on BareKit, send those requests over the IPC instead. bare-rpc gives you request and reply on top of the stream. From bare-rpc 2.0.0, a cancelled request sends a message that bare-rpc 1.x rejects, so pair it with bare-rpc-swift 3.0.0, bare-rpc-kotlin 0.6.0, or bare-rpc-python 0.3.0 or later in the host. See Type a native RPC bridge.

Replace the assets option

Remove assets from the options of the worklet. A worklet whose bundle has no assets needs nothing else, and keeps starting from inline source:

// Before (react-native-bare-kit 0.16)
const worklet = new Worklet({ assets: assetsDirectory })
worklet.start('/app.bundle', bundle)

// After (react-native-bare-kit 0.17)
const worklet = new Worklet()
worklet.start('/app.bundle', bundle)

A worklet whose bundle references assets needs them shipped as resources of the app instead. Let bare-stow lay them out. Pack the worklet into a package with bare-stow pack --target react-native, where the entry exports a function that receives the IPC stream, and start it through that package:

npx bare-stow pack --target react-native --out ./worker worker.js
import worker from 'worker'

const { ipc } = await worker.start()

The package starts the bundle as bare-stow.bundle, a relative filename, with the bundle as its source. Bare Kit then finds the assets in the bare-stow resources that bare-stow link writes from the same entry. On iOS they're resources of the app bundle. On Android, the files that bare-stow.assets lists are extracted from the APK before the bundle first runs. See Loading a bundle.

Port native host code

Native apps only. Replace the Bare Kit 2 classes with the binding for your language. The bindings are async: Swift uses async/await, Kotlin uses coroutines, and Python uses asyncio. Instead of polling the IPC from readable and writable callbacks, you iterate it and await each write.

In Objective-C on Bare Kit 2, a host polled the IPC:

BareWorkletConfiguration *options = [BareWorkletConfiguration defaultWorkletConfiguration];
options.memoryLimit = 24 * 1024 * 1024;

BareWorklet *worklet = [[BareWorklet alloc] initWithConfiguration:options];
BareIPC *ipc = [[BareIPC alloc] initWithWorklet:worklet];

[worklet start:@"/app.bundle" source:bundle arguments:@[]];

ipc.readable = ^(BareIPC *ipc) {
  NSData *data = [ipc read];
  // …
};

In Swift on Bare Kit 3, the same host iterates the IPC:

import BareKit
import Foundation

let worklet = try Worklet(options: .init(memoryLimit: 24 * 1024 * 1024))
let ipc = try IPC(worklet: worklet)

try worklet.start("app.bundle")

try await ipc.write(Data("Hello from Swift".utf8))

for await data in ipc {
  print(String(decoding: data, as: UTF8.self))
}

ipc.close()

let code = await worklet.wait()

In Kotlin, Worklet takes the Android Context first, and Ipc exposes reads as a flow:

import to.holepunch.bare.kit.Ipc
import to.holepunch.bare.kit.Worklet

val worklet = Worklet(context, Worklet.Options(memoryLimit = 24L * 1024 * 1024))
val ipc = Ipc(worklet)

worklet.start("app.bundle")

ipc.write("Hello from Kotlin".encodeToByteArray())

ipc.data.collect { data -> println(data.decodeToString()) }

ipc.close()

val code = worklet.wait()
worklet.close()

Python is new in Bare Kit 3, and its worklet is also its IPC:

async with await start(Path(__file__).parent / "app.bundle") as worklet:
    await worklet.write(b"Hello from Python")

    async for data in worklet:
        print(data.decode())

The read loops end once the worklet ends its side of the IPC, for example with Bare.IPC.end(). An open IPC keeps the worklet running, so close the host side, as ipc.close() does, before you wait for the exit code. Keep a reference to the worklet for as long as it should run. Releasing it in Swift, or closing it in Kotlin and Python, terminates it. A C or C++ host follows Changes from bare-kit 2 instead.

Add the iOS entitlement

From Bare Kit 3.1.0, every iOS app that runs a worklet must be signed with the extended virtual addressing entitlement. This includes a React Native app that links bare-kit-swift 2.3.0 or later. Add it to the entitlements file of the app target:

<key>com.apple.developer.kernel.extended-virtual-addressing</key>
<true/>

Alternatively, in Xcode, add the Extended Virtual Addressing capability under Signing & Capabilities, which writes the same key and, with automatic signing, enables it for the App ID. Without the entitlement, the app aborts on a device when V8 starts:

Failed to reserve virtual memory for process-wide V8 pointer compression cage

This can reach an app before you migrate. bare-stow 0.3.2 through 0.3.6 wrote from: "2.2.0" for bare-kit-swift, which Swift Package Manager resolves to any 2.x release, so the next package update of an app linked with them brings in bare-kit-swift 2.3.0, and with it Bare 1.37.1. Add the entitlement before you update packages, or pin bare-kit-swift to 2.2.0 exactly until you do. See iOS entitlement for why V8 needs it, and for what's known about app extensions.

Check WebAssembly on iOS

Bare Kit 3.1.0 is the first Bare Kit release whose Bare runs WebAssembly on iOS, which Bare does from 1.37.0. V8 can't compile code at runtime there, so it runs WebAssembly in DrumBrake, its interpreter, which is slower than the compiled code other platforms get. The V8 build for the x64 iOS simulator, on Intel Macs, leaves WebAssembly out, so none of this applies there.

Two things change on iOS:

  • The WebAssembly global now exists, where before it was missing. The exports of bare-wasm, which threw WEBASSEMBLY_UNAVAILABLE without it, now work.
  • A worklet that resolves its modules at runtime can fall back to the WebAssembly build of an addon package that has no native build for iOS. A bundle falls back only if it was packed with wasi-wasm32 among its hosts, or imports the package with: { host: 'wasi-wasm32' }. The ios and mobile presets of bare-pack leave that host out, and the bare-kit and react-native targets of bare-stow don't accept it, so a bundled worklet that depends on such a package still fails when it's packed.

If you rely on WebAssembly builds on iOS, keep the native build linked where speed matters. A WebAssembly addon still runs sandboxed through WASI, which limits its files and system calls to what you grant. The sandbox contains a buggy addon, but it isn't a boundary for untrusted code, which can still reach whatever your JavaScript can.

Confirm a clean exit

Run the app on a device. Then check that each worklet exits when you expect it to, and with the code you expect:

  • In React Native 0.17.1, listen for the exit event, or await worklet.wait().
  • In Swift, Kotlin and Python, await wait().

An open IPC now keeps a worklet running until both sides have ended it, so end the host side with IPC.end(), or end() in a native binding, once you have nothing more to send. An uncaught error in the worklet still aborts the whole app, so report errors over the IPC as Error handling describes.

Troubleshooting

SymptomCauseFix
new Worklet() throws Bare Kit is not available, so make sure the application includes bare-kit-swift or bare-kit-kotlinreact-native-bare-kit 0.17 found no Bare Kit in the appRun bare-stow link --target bare-kit and add its output to the app. See step 2.
ReferenceError: BareKit is not defined in the workletBare Kit 3 removed the BareKit globalUse Bare.IPC
The app aborts on an iOS device with Failed to reserve virtual memory for process-wide V8 pointer compression cageThe app isn't signed with the extended virtual addressing entitlementAdd the entitlement
An addon fails to load after the upgradeIt was linked with bare-link into a directory that 0.17 no longer buildsLink it with bare-stow link
A worklet doesn't exitIts IPC is still open on the host sideCall end() or close() on the host side of the IPC

See also

Last updated on

Was this helpful?

On this page