-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Supernote Module Generator adds C, C++, Kotlin, and Java functionality to an existing Supernote plugin and makes it available to JavaScript or TypeScript.
This Wiki assumes that you have already read the official Supernote plugin documentation and have an existing plugin project that you want to add a module to.
The goal of the generator is to make it as easy as possible to use native Android APIs or the raw horsepower of the Supernote hardware without writing more boilerplate than anyone should be subjected to.
Even with the tool, though, native and JVM code introduce another toolchain and more ways for things to go wrong. If JavaScript or TypeScript already does everything your plugin needs, stick with it.
Keep it simple until you can point to a real bottleneck or another concrete reason to complicate things (or if you simply prefer writing C/C++ or Kotlin/Java over JavaScript/TypeScript, which is reason enough in my book).
In the context of React Native, which Supernote plugins are built on, a module is the local package of files that lets the main JavaScript/TypeScript side of your plugin interact with C, C++, Kotlin, or Java code.
In practice, it is a folder containing your implementation code along with the generated build files, bindings, and glue needed to connect that code back to JavaScript/TypeScript.
For example, a document reader module might contain C++ code for reading a file, Kotlin code for asking Android for storage information, and the generated code needed to expose both through one JavaScript API.
The generator adds this folder inside your existing plugin. It is not another plugin and it is not a separate application.
You can think of it as a wrapper that gives the JavaScript runtime the information and connections it needs to interact with code written in another language.
The CLI calls this package a feature because it can be added, updated, validated, and removed on its own. In this Wiki, “module” and “feature” mean the same thing.
A module contains:
document module
├── C and C++ code folder
├── Kotlin and Java code folder
├── generated build and connection code
└── one JavaScript/TypeScript API
You create one feature and use the language that makes sense for each part of the work.
| If you need... | Start with... |
|---|---|
Android APIs or Context
|
Kotlin or Java |
| An existing Kotlin or Java library | Kotlin or Java |
| Kotlin coroutines | Kotlin |
| An existing C or C++ library | C or C++ |
| Low-level file, data, or native processing | C or C++ |
| Native processing that also needs Android services | Both |
This only controls which example files supernote-module add creates. It is not a permanent backend choice.
Start with the language you need now and add the other environment later if the feature grows.
Kotlin or Java is normally the natural choice when the feature needs Android APIs, JVM libraries, Context, or Kotlin coroutines.
@SupernotePluginExport
fun pageCount(): Int = 42You do not need to write C++ glue to make this callable from JavaScript. The generator creates that connection for you.
Read Kotlin and Java.
C or C++ is normally the natural choice for an existing native library, low-level processing, or performance-sensitive work that belongs in native code.
// @SupernotePluginExport
std::int32_t pageCount() {
return 42;
}C23 is fully supported as normal implementation code. The marked JavaScript boundary lives in C++23, so a C library is normally called through a small C++ wrapper.
Read C and C++.
C/C++ and Kotlin/Java do not have to live in separate modules. One exported function can be written in C++ while another is written in Kotlin, and JavaScript sees both through the same module API.
flowchart TB
JS["JavaScript / TypeScript"] --> JSI["Generated JSI API"]
JSI --> Native["C / C++"]
JSI --> JNI["Generated JNI"]
JNI --> JVM["Kotlin / Java"]
The two sides do not have to call each other. When native code needs a JVM helper, the generator can create a typed C++-to-Kotlin/Java call without exposing that helper to JavaScript.
The generator does not generate the reverse Kotlin/Java-to-C++ route.
Read Using Both Environments.
Normal source code is ignored by the generator, even when it is public in C++, Kotlin, or Java. The generator only connects declarations that have a deliberate Supernote marker:
SupernotePluginExport
JavaScript can call it
SupernotePluginInternal
generated code can call it, but JavaScript cannot
SupernotePluginAsync
JavaScript receives a Promise
This means you can change an ordinary helper from private to public for normal language reasons without accidentally changing your JavaScript API.
A normal export is synchronous:
const count = document.pageCount();Mark work as async when it may block or when the JavaScript API should return a Promise:
const bytes = await document.loadPage(3);You can also expose persistent C++ and JVM-backed objects. Each JavaScript object owns its own implementation instance, and only the methods you mark are visible to JavaScript.
Small calls with normal scalar values are practical for ordinary UI events. If you already have a group of values, send one batch instead of crossing a generated boundary once for every item.
This matters most when C++ calls Kotlin or Java. If an operation needs to process ten thousand items, design one call that performs the loop on the JVM side instead of calling a JVM helper ten thousand times.
When the input is already binary data, pass the supported byte buffer once. Large strings and byte arrays are mainly affected by copying, so keep the data on the side that owns it and return only what the caller actually needs.
Do not turn one number or one point into a Uint8Array just to avoid a normal function call. Packing helps when the data is already a real batch.
Read Performance and Benchmarks for the measured results and practical decision guide.
For the declarations you mark, the generator creates and connects the required:
- JavaScript and TypeScript API;
- JSI functions and object wrappers;
- JNI and Kotlin/Java adapters;
- CMake, Gradle, KSP, and React Native integration;
- argument and result conversion;
- Promise scheduling and error delivery; and
- runtime, feature, and object lifetime handling.
That is the repetitive integration work where one wrong name, signature, build setting, or lifetime assumption can stop otherwise correct code from working.
The generator does not:
- create the surrounding Supernote plugin;
- decide whether native code is faster for your workload;
- make an Android API or third-party library compatible with the tablet;
- make your own implementation thread-safe;
- expose unmarked code automatically; or
- prove that a local build will run on every PluginHost or firmware version.
- Add your first module: Getting Started
- Write C or C++: C and C++
- Write Kotlin or Java: Kotlin and Java
- Use both environments: C/C++ and Kotlin/Java Together
- Handle failures: Error Handling
- Review measured call costs: Performance and Benchmarks
- Understand commands and prompts: Using the CLI
- Update, validate, or remove a module: Managing Modules
- Recover from a problem: Troubleshooting
For the exact behavior supported by your installed version, use:
supernote-module --version
supernote-module --help
supernote-module help add