Marmotta builds Node.js native addons written in C or C++ with Node-API. It uses Zig as the compiler toolchain and can install Zig for you, so projects do not need to configure node-gyp or CMake.
- Highlights
- Requirements
- Installation
- Quick start
- Configuration
- Command-line reference
- Zig toolchain
- Contributing
- License
- Team
- Build C and C++ Node-API addons into
.nodefiles. - Use the Node-API headers supplied by Marmotta; no separate Node header installation is needed.
- Reuse node-gyp-oriented sources that use
NODE_GYP_MODULE_NAME. - Build for the host platform or pass a Zig target triple for cross-compilation.
- Install Zig automatically when a suitable compiler is not already on
PATH. - Load the same addon in Node.js, Electron, Deno, and Bun. On Windows, Marmotta binds Node-API functions to the process that loads the addon instead of to
node.exe, so no delay-load hook is needed.
- Node.js 20 or later and npm.
- A C or C++ Node-API addon source file (
.c,.cc,.cpp, or.cxx). tarand internet access when Marmotta needs to download Zig.
Automatic Zig downloads are supported on x64 and ARM64 Linux, macOS, and Windows hosts. On other host platforms, install Zig separately and make it available on PATH.
Install the unscoped package globally:
npm install --global marmotta
marmotta --helpOr add Marmotta to a project and run it with npx:
npm install --save-dev marmotta
npx marmotta --helpThe same CLI is also published as @napi-bindings/marmotta. To install that package instead:
npm install --save-dev @napi-bindings/marmotta
npx marmotta --helpCreate a Node.js project with a C or C++ Node-API source file. For example, save this as src/hello.c:
#include <assert.h>
#include <node_api.h>
static napi_value Hello(napi_env env, napi_callback_info info) {
napi_value result;
napi_status status = napi_create_string_utf8(env, "world", 5, &result);
assert(status == napi_ok);
return result;
}
static napi_value Initialize(napi_env env, napi_value exports) {
napi_property_descriptor descriptor = {
"hello", 0, Hello, 0, 0, 0, napi_default, 0
};
napi_status status = napi_define_properties(env, exports, 1, &descriptor);
assert(status == napi_ok);
return exports;
}
NAPI_MODULE(NODE_GYP_MODULE_NAME, Initialize)Create marmotta.config.json in the project root:
{
"name": "hello",
"sources": ["src/hello.c"],
"outputDir": "build"
}Build and load the addon:
npx marmotta build
node -e "console.log(require('./build/hello.node').hello())"The example prints world. configure can be run to validate the project and prepare Zig before building, but it is optional; build loads the configuration and ensures the toolchain itself.
All paths in either configuration file are resolved relative to the project directory. The configuration file is optional. Without it, Marmotta uses the package or directory name, discovers source files, and places the addon in the project root.
You can use marmotta.config.js to compute configuration values with JavaScript. If both config files are present, marmotta.config.js takes precedence over marmotta.config.json. Export either a configuration object as the default export or a function that returns one; the function may be asynchronous and receives the project directory, requested Zig target, and host platform and architecture:
export default ({ target, platform }) => {
const isWindows = target ? target.includes('windows') : platform === 'win32';
return {
name: 'hello',
sources: ['src/hello.c'],
cFlags: isWindows ? ['-DWIN32_BUILD'] : [],
};
};For .js files, use the module format selected by your project's package.json ("type": "module" for ESM). JavaScript configuration files execute as project code.
| Field | Type | Default | Description |
|---|---|---|---|
name |
string | package.json name, or project directory name |
Addon name, also used in the output filename <name>.node. Characters other than letters, digits, _, and - are replaced with _. |
sources |
string array | discovered source files | C/C++ sources to compile. An empty or omitted array enables recursive discovery. |
includeDirs |
string array | [] |
Additional header search directories. |
cFlags |
string array | [] |
Additional compiler flags for C sources. |
cxxFlags |
string array | [] |
Additional compiler flags for C++ sources. |
linkerFlags |
string array | [] |
Additional linker flags. |
outputDir |
string | . |
Output directory. The generated addon is <outputDir>/<name>.node. |
zigVersion |
string | Zig from PATH, else local/latest stable |
Pin an exact stable Zig version, for example 0.14.1. When set, Marmotta uses or downloads this managed version instead of using Zig from PATH. |
Each array field must contain only strings. During source discovery Marmotta ignores .git, .marmotta, build, dist, and node_modules. Explicit source and include paths must be relative to the project directory or absolute paths; each source must exist and be a file.
Marmotta defines NODE_GYP_MODULE_NAME for every source file using the configured addon name. Characters that cannot appear in a C identifier are replaced with _, and a leading digit is prefixed with _. This allows compatible sources to use NODE_API_MODULE(NODE_GYP_MODULE_NAME, Initialize) without changing the macro.
Run marmotta --help or npx marmotta --help for the built-in usage summary.
| Command | Description |
|---|---|
marmotta configure |
Validate the project configuration and ensure Zig is available. |
marmotta build |
Compile the configured or discovered C/C++ sources into a .node addon. |
marmotta rebuild |
Remove the selected generated addon and build it again. |
marmotta clean |
Remove the generated addon file only. Zig and other build files are left untouched. |
marmotta install |
Ensure a Zig compiler is available, downloading one if needed. |
marmotta list |
List Zig versions managed by Marmotta. |
marmotta remove <version> |
Remove a managed Zig version, for example marmotta remove 0.14.1. |
| Option | Commands | Description |
|---|---|---|
-C, --directory <path> |
configure, build, rebuild, clean |
Project directory. Defaults to the current working directory. |
-o, --output-dir <path> |
build, rebuild, clean |
Override outputDir. Relative paths are resolved from the project directory. |
--target <triple> |
build, rebuild |
Zig target triple used for cross-compilation. |
--debug |
build, rebuild |
Compile without optimization and include debug information (-O0 -g instead of -O2). |
-h, --help |
Any command | Show help. |
-v, --version |
Top-level command | Show the Marmotta version. |
For example, request a build for ARM64 macOS:
npx marmotta build --target aarch64-macosUse a target triple supported by Zig for the intended target. Cross-compilation does not run the resulting addon on the host; load it on a compatible target system.
Without a zigVersion pin, Marmotta uses zig from PATH when available, then a locally managed version, and otherwise downloads the latest stable Zig release for the supported host platform into the Marmotta home directory (see Marmotta home directory). If zigVersion is set, Marmotta ignores PATH and selects that exact managed version, downloading it if necessary. If the pinned version is not available for the host platform, the command fails instead of falling back to another version. If the download index includes a checksum, Marmotta verifies the downloaded archive.
Prepare Zig in advance:
npx marmotta installList and remove toolchain versions managed by Marmotta:
npx marmotta list
npx marmotta remove 0.14.1list and remove operate only on versions managed by Marmotta; they do not list, uninstall, or modify a system Zig executable found on PATH.
Marmotta stores managed Zig toolchains (toolchains/zig/<version>) and temporary build files in its home directory. By default this is .marmotta in the user's home directory (~/.marmotta on Unix-like systems or %USERPROFILE%\.marmotta on Windows).
Set MARMOTTA_HOME to use another directory, for example when the home directory is read-only or not persistent, as on restricted CI runners or in containers:
export MARMOTTA_HOME=/var/cache/marmotta
npx marmotta build- The value must be an absolute path. On Windows it must start with a drive letter (
D:\marmotta) or be a UNC path (\\server\share\marmotta); root-relative paths such as\marmottaand device paths such as\\?\or\\.\are not accepted.~is not expanded, and values with leading or trailing whitespace are rejected instead of being trimmed. Invalid values are rejected withMARMOTTA_HOME_INVALID. - An empty value is treated as unset.
- Marmotta creates the directory, and the subdirectories it needs, when a command first writes to it. If a directory cannot be created or written, for example when building, the command fails with
MARMOTTA_HOME_UNAVAILABLEand names the directory and the cause. A failed Zig installation keeps itsZIG_INSTALL_FAILEDcode and includes the same details. Commands that write nothing, such asmarmotta list, orconfigureandinstallwhen Zig is already available, also work with a read-only directory. - When
MARMOTTA_HOMEis set, Zig's global cache is placed in<MARMOTTA_HOME>/cache, so builds do not write to the home directory. IfZIG_GLOBAL_CACHE_DIRis already set to a non-empty value, Marmotta leaves it unchanged. WithoutMARMOTTA_HOME, Marmotta does not change the cache location and Zig uses its own default. - Marmotta only removes files and directories it created, such as a Zig version removed with
marmotta removeor temporary build directories. It never deletes or cleans theMARMOTTA_HOMEdirectory itself, so it can point to a shared location.
Bug reports, documentation improvements, and code contributions are welcome. See the contributing guide for project contribution and review guidelines.
Marmotta is licensed under the Apache License, Version 2.0. See LICENSE for the full license text.
