Cross-compiling
Contents
- Output layout
- What every release build does
- Building a subset
- What
index.jsis for - Default platforms
- Custom platforms
- Electron and other hosts
- Optimization
That single command builds your addon for every platform listed in .npm.platforms and lays out the npm package structure ready to publish. Zig's cross-compilation is built in: there are no toolchains to install, no Docker images, no QEMU.
The same command is also safe to run repeatedly. The build owns and reconciles the files it generates; anything you've added or edited (your seam over the addon, user fields on the main package.json, the version, files unrelated to the build) is preserved. Reconciliation happens in a sibling staging directory, then claims the destination with an exclusive directory create and materializes every entry without overwriting an existing path. Content, modes, identities, temporary trees, and the recovery backup are revalidated at their commit and cleanup boundaries, so a copy failure, manifest-write failure, or concurrent edit cannot silently replace or recursively delete user work.
Output layout
The first napikit build --release writes a complete npm/ tree:
<name> is your addon's name. <scope> is the npm scope from the .scope field of the .npm block in build.zig. Every per-platform binding lives under that scope.
Calling addLib more than once in build.zig is supported; each addon gets its own npm/<name>/ subtree and is reconciled independently. A newly generated addon inherits the one strict semantic version already shared by the repository's generated and extra packages; only a repository with no existing publishable package starts at 0.0.0. Existing version drift fails before Zig instead of assigning an arbitrary version to the new addon. See Multiple addons in one repo.
What every release build does
napikit build --release is the only command you need during release. It cross-compiles, then reconciles npm/ against the policy in build.zig. Re-run it as often as you like; you cannot drift npm/ out of sync with build.zig.
The reconciler is conservative about your work:
Before synchronization, a full release build verifies that every generated source is a regular non-symlink file, every optionalDependencies entry has exactly one binding directory, main/dependency/binding versions and licenses agree with generated policy, binding repository provenance matches the main package, and entry points, files, npmjs/public publication policy, and target-specific os/cpu/libc metadata are canonical. Unexpected manifest fields such as lifecycle scripts are rejected. Reconciliation copies from a fingerprint-verified private snapshot so a source replacement during synchronization cannot leak unvalidated bytes into npm/. A --current build permits configured bindings that are intentionally absent from its temporary host-only output, but requires the exact host binding and still rejects extra or inconsistent generated bindings.
The practical guarantee: edit build.zig, then re-run napikit build --release. That works for every change, including renaming the scope, adding or removing platforms, changing .dts, and renaming .host_exe. You do not need to delete npm/ first; the reconciler removes a disabled build-owned declaration and otherwise preserves unrelated user files.
The one exception is renaming the addon's .name itself. The new tree is created fresh under npm/<new-name>/, the old npm/<old-name>/ becomes an orphan, and the build prints a warning that asks you to copy any user fields you want to keep on the new main package.json and delete the old folder. (Renaming a published npm package is a rare and disruptive event; this is intentional.)
Building a subset
A full release build cross-compiles every platform for every addon. During local development you often want something faster.
--current builds only the host platform's binding:
This compiles a single .node for the machine you are on and skips the others. The host must be present in that addon's configured .npm.platforms; otherwise the command fails before writing an undeclared binding package. The main package.json still lists every configured platform in optionalDependencies, and the build is additive: bindings already in npm/ from a previous full build are left in place, not deleted. Run a full napikit build --release before publishing so every platform is present.
--only narrows the build to specific addons by .name, useful when build.zig calls addLib more than once:
Unlisted addons are skipped entirely and their npm/ subtrees are left untouched. Combine the two flags to rebuild just one addon for just your platform:
What index.js is for
binding.js is fully owned by the build. It implements platform detection and loads the matching <scope>/binding-… package. Do not edit it; your changes will be overwritten on the next build.
index.js is your seam. On the first napikit build --release, @teakit/napi walks the same public Zig declarations used by the runtime and declarations, then seeds both default and named exports. For an addon that exports add and greet, the generated shape is:
That is already a working entry point for both import styles. Keep it as-is, or use it for JS-side wrapping, normalization, or higher-level helpers. After it is seeded, edits to index.js survive every rebuild; if you intentionally maintain a custom seam, you are also responsible for keeping its named exports aligned with the addon.
Default platforms
If .platforms is omitted from .npm, you get this set:
That is 11 binaries from one napikit build --release call.
Custom platforms
Override the default list to ship only what you need:
Adding or removing entries from .platforms is a regular edit. The next napikit build --release adds bindings for the new entries and deletes bindings for ones you took out. The full list of Platform values is in build.zig (addLib).
The Zig enum uses names such as .macos_arm64 and .windows_x64; generated npm binding suffixes still use Node/npm platform names such as darwin-arm64 and win32-x64.
Electron and other hosts
By default, addons are compiled to load into node.exe on Windows. To target Electron, set .host_exe:
This only affects Windows import-library generation. On macOS and Linux the addon loads into whatever process imports it without any host-specific configuration.
Optimization
napikit build --release defaults every cross-compiled binary to ReleaseFast. For development, napikit build defaults to Debug. Override the release mode with:
The selected mode applies to every release artifact; safe and small are not reduced to ReleaseFast internally.