Step-by-step guide for adding new built-in JavaScript modules to mikrojs. Use this skill whenever the user wants to add a new JS-level module (not a raw C module), create a higher-level API wrapper around native C bindings, add a new "mikro/xxx" import, or asks about the TypeScript-to-bytecode pipeline, esbuild bundling, qjsc compilation, or how built-in modules are registered.
Step-by-step guide for adding new built-in JavaScript modules to mikrojs. Use this skill whenever the user wants to add a new JS-level module (not a raw C module), create a higher-level API wrapper around native C bindings, add a new "mikro/xxx" import, or asks about the TypeScript-to-bytecode pipeline, esbuild bundling, qjsc compilation, or how built-in modules are registered.
Adding a Built-in JS Module to mikrojs
Built-in JS modules provide higher-level APIs on top of native C modules. They're written in TypeScript, bundled with esbuild, compiled to bytecode with qjsc, and embedded in the firmware. Users import them as mikrojs/xxx.
There are two types:
Core builtins (mikrojs/xxx) - part of every firmware, compiled via CMake
Driver/board builtins (@mikrojs/driver-xxx) - registered via MIK_REGISTER_BUILTIN, only in firmware that includes the driver
Pipeline Overview
TypeScript source (.ts)
-> esbuild bundle + minify (.js) [scripts/bundle-runtime.js]
-> qjsc bytecode compile (.h) [scripts/compile-bytecode.sh]
-> #include in builtins.cpp [core] or MIK_REGISTER_BUILTIN [driver]
-> Available at import("mikro/xxx") or import("@mikrojs/driver-xxx")
The pipeline is driven by the mikrojs_generate_bytecode() CMake function, which handles both the bundle and compile steps.
// packages/@mikrojs/native/runtime/example/example.tsimport * as native from'native:example'import {typeResult, err, ok} from'mikro/result'exportfunctionread(channel: number): Result<number, {type: 'ReadFailed'}> {
const result = native.read(channel)
if (!result.ok) returnerr({type: 'ReadFailed'asconst})
returnok(result.value)
}
Key rules:
Import native bindings from native:xxx (internal, not user-facing)
Import other builtins from mikrojs/xxx
Both native:* and mikrojs/* are marked external by esbuild
Keep code minimal; every byte becomes firmware flash
2. If needed: create the native C module
Follow the add-native-module skill to create the native:xxx native module with MIK_REGISTER_MODULE().
3. Add to the RUNTIME_MODULES list
packages/@mikrojs/native/CMakeLists.txt and packages/@mikrojs/firmware/components/mikrojs/CMakeLists.txt both call mikrojs_generate_bytecode() with the module list:
Add your module name to the MODULES list in both files.
4. Include in builtins.cpp
packages/@mikrojs/native/src/builtins.cpp - Add the include and table entry:
// Add with the other gen/ includes (alphabetical order)#include"gen/mikrojs_example.h"// Add to the builtins[] table (alphabetical order)staticconstmik_builtin_t builtins[] = {
// ...
{"mikro/example", mikrojs_example_bytecode, mikrojs_example_bytecode_size},
// ...
{NULL, NULL, 0},
};
5. Add type declarations (optional but recommended)
Create type declarations so TypeScript users get autocomplete.