-
Notifications
You must be signed in to change notification settings - Fork 0
JSI Modules
The CLI calls this type JSI Module.
Use it only when JavaScript genuinely needs to call C++ directly through a synchronous low-overhead API.
Do not choose JSI only because you prefer C or C++. When the result can be returned asynchronously, especially when the feature needs Android APIs or substantial Kotlin or Java integration, a Native JNI Module is normally easier and safer.
JSI is especially useful when:
- JavaScript genuinely needs the result synchronously.
- The operation is short and has predictable worst-case time.
- The operation may be called frequently enough that the normal Native Module path materially affects the feature.
- The target PluginHost and device policy have been tested and allow the generated library to execute.
Possible examples include short mathematical or geometric operations and small direct queries of native state.
JSI can also read files, use the network, wait for other work, or perform large computations. None of that is unsupported just because the API is synchronous.
The tradeoff is that JavaScript cannot continue until the exported function returns. If the call takes a long time, the plugin may appear frozen for that entire time. That may be acceptable when the blocking is deliberate; measure it on the target Supernote and decide based on your feature.
supernote-module doctor --type jsi
supernote-module add fast-math-jsi --type jsi --yesThe examples below use:
Package name: fast-math-jsi
JavaScript name: FastMath
JSI generation requires CMake 3.22.1 or newer and an Android NDK whose Clang compiler supports the generated C23 and C++23 code for arm64-v8a.
Doctor checks the local toolchain. It cannot prove runtime support on a target Supernote.
This page uses C++ examples for clarity, but the same workflow and build setup apply to both C and C++ code.
Exported @SupernoteExport definitions are C++ because the generated JSI boundary is C++23. C code is fully supported as implementation or helper code: put it in .c files and call it from a small exported C++ wrapper through a normal extern "C" header.
The generator creates:
- C++ HostFunctions.
- Conversion between supported JavaScript and C++ values.
- Native installation into the JavaScript runtime.
- Kotlin loading code.
- CMake and Gradle configuration.
- React Native registration.
- The JavaScript wrapper.
- TypeScript declarations.
You write ordinary marked C++ functions rather than HostFunctions and runtime installation code.
Write C and C++ files below:
local_modules/fast-math-jsi/android/src/main/cpp/
Update preserves this complete directory, including files you add and starter files you intentionally delete.
Generated HostFunctions, loaders, registration, CMake, Gradle, package, metadata, and declaration files may be replaced by Update.
Place // @SupernoteExport immediately before an ordinary top-level C++ function definition:
// @SupernoteExport
double add(double left, double right) {
return left + right;
}Call it directly:
import FastMath from 'fast-math-jsi';
const total = FastMath.add(20, 22);Do not use await. The C++ function returns before JavaScript continues.
Errors are synchronous as well.
| C++ | JavaScript or TypeScript |
|---|---|
bool |
boolean |
double |
number |
std::string |
string |
void return |
void |
Strings are treated as UTF-8.
Pointers, references, structs, arrays, arbitrary objects, and other C++ types cannot cross the generated boundary directly. Convert to internal types after entering C++.
// @SupernoteExport(name = "greet")
std::string make_greeting(std::string name) {
return "Hello, " + name;
}const message = FastMath.greet('Ziv');Export names must be unique and valid JavaScript property names.
Files ending in .cc, .cpp, or .cxx can contain exported functions.
Files ending in .c compile as C23 and can contain helper code, but cannot contain @SupernoteExport functions. The generated JSI boundary is C++23.
Use normal extern "C" guards when C++ calls C helper functions.
An exported function must be an ordinary top-level definition with explicitly named parameters.
The generated boundary does not support:
- Overloaded functions.
- Functions inside namespaces.
- Function templates.
- Pointer or reference parameters and returns.
- Variadic or default arguments.
-
static,inline,constexpr, orextern "C"exported functions. - Export markers in
.cfiles.
noexcept is supported.
These restrictions apply only to the exported boundary.
A JSI function normally runs on the same thread that called it from JavaScript. JavaScript cannot continue until the function returns.
Measure:
- The complete call.
- Conversion cost.
- The worst realistic input.
- Performance on the target Supernote rather than only a desktop.
An expensive operation can still use JSI. If blocking JavaScript for its full duration is not acceptable, use JNI or another asynchronous design.
JSI avoids the Promise-based Native Module path for supported values.
It is most valuable for many short calls that truly need to be synchronous. It is not automatically faster for an entire feature.
A single batched Native or JNI call can be simpler and faster than thousands of tiny JSI calls when the actual work dominates the bridge overhead.
Choose based on data flow:
- JSI for frequent, short, synchronous operations.
- JNI for C or C++ work that can be requested asynchronously.
- Native Module for Kotlin, Java, and Android APIs.
JSI gives JavaScript direct access to C++, not direct access to Android APIs.
The generated package includes the Kotlin loading and Android registration required to install the JSI binding. That Kotlin is managed integration code.
When the feature needs substantial custom interaction with Android services or user-owned Kotlin and Java code, use JNI or a separate Native Module.
Manual changes to the generated loader or registration may be replaced by Update.
Rust, Zig, Go, and other languages with a C-compatible ABI can potentially be called through a C or C++ wrapper in the JSI module.
You are responsible for:
- The additional compiler and Android target.
- Static or shared library packaging.
- Runtime requirements.
- The wrapper.
- CMake or Gradle changes.
- Compatibility with PluginHost's loading environment.
From JavaScript's point of view, the export remains a generated C++ JSI function.
A successful local build proves only that the package can compile.
PluginHost must also:
- Provide a compatible React Native and JSI runtime.
- Load the generated library in a compatible linker namespace.
- Be allowed by Android and SELinux policy to execute the library from the plugin's extracted location.
The generator cannot change firmware, PluginHost, linker namespaces, or SELinux policy.
Success on a permissive or userdebug device does not prove support on an enforcing retail device.
A Permission denied or SELinux { execute } denial occurs before your exported implementation runs.
supernote-module validate fast-math-jsi --buildFor full compiler, linker, CMake, and Gradle output:
supernote-module validate fast-math-jsi --build --verboseValidation scans the C++ source, regenerates the binding, and compiles the library. It cannot prove that PluginHost will execute it.
See Managing Modules.
supernote-module update fast-math-jsi
supernote-module remove fast-math-jsiUpdate preserves the complete android/src/main/cpp/ tree. Remove deletes it.