build.zig (addLib)

Contents

napi.addLib is the only build.zig API you need. It registers your addon as a Zig artifact, applies the right linker flags for each OS, sets up the .d.ts install, and (with -Dnpm=true) builds the cross-compile graph and generates the npm package skeleton.

You can call it more than once in the same build.zig to ship multiple addons from one repo. Each call writes its own zig-out/lib/<name>.node and its own npm/<name>/ tree, and the CLI (napikit build, napikit build --release, napikit bump, napikit publish, napikit npm-init) picks up every one. Give each addon its own .scope; per-platform binding package names are derived from the scope, so two addons sharing a scope would publish under the same binding names.

const std = @import("std");
const napi = @import("napi");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    napi.addLib(b, b.dependency("napi", .{}), .{
        .name = "my-addon",
        .root = b.path("src/lib.zig"),
        .target = target,
        .optimize = optimize,
        .npm = .{
            .scope = "@myscope",
        },
    });
}

LibOptions

OptionRequiredTypeDescription
.nameYes[]const u8Package name. Used for the .node binary and the npm package.
.rootYesLazyPathPath to the root Zig source file (src/lib.zig).
.targetYesResolvedTargetBuild target. Use b.standardTargetOptions.
.optimizeYesOptimizeModeOptimization mode. Use b.standardOptimizeOption.
.importsNo[]const ImportAdditional Zig modules to import.
.npmNo?NpmConfignpm package config. Required for cross-compile + publish.
.host_exeNo[]const u8Windows host binary (default "node.exe", use "electron.exe", etc.).

Import

pub const Import = struct {
    name: []const u8,
    module: *std.Build.Module,
};

Pass via .imports = &.{ .{ .name = "parser", .module = parser } }. See Project layout: where to put your code.

NpmConfig

OptionDefaultDescription
.scoperequirednpm scope (e.g. "@myscope").
.description""Package description.
.license"MIT"License identifier.
.repository""Git repository ("owner/repo" shorthand or a full URL). See below.
.dts.none.{ .file = path }, .auto, or .none. See TypeScript.
.platformsPlatform.defaultsCross-compilation targets.

.repository

npm requires a repository field on every published package for provenance to verify against the source tree, otherwise napikit publish fails in CI with "package must specify a repository". An addon ships as the main package plus one binding per platform (twelve package.json files with the default platform set), and setting .repository once in build.zig writes the field into every one of them on each release build, so you never have to keep them in sync by hand.

Two accepted forms:

.repository = "teakit/napi",                       // GitHub shorthand
.repository = "git+https://github.com/teakit/napi.git", // explicit URL

The shorthand expands to git+https://github.com/owner/repo.git. Anything starting with http://, https://, git+, git@, or ssh:// is passed through unchanged so non-GitHub hosts work too. If .repository is empty, no field is emitted and any value already in the existing package.json is preserved.

Dts

pub const Dts = union(enum) {
    none,
    auto,
    file: std.Build.LazyPath,
};

See TypeScript declarations for what each mode produces.

With .none, the generated main package has no types field and does not list or contain index.d.ts. If a previously typed release package switches to .none, the release reconciler removes that build-owned declaration without deleting unrelated files.

Platform

A tagged enum of every supported (OS, architecture, libc) tuple. Used in .npm.platforms. Defaults are exposed as Platform.defaults:

Platform valueOSArchlibc
.linux_x64_gnuLinuxx64glibc
.linux_x64_muslLinuxx64musl
.linux_arm64_gnuLinuxarm64glibc
.linux_arm64_muslLinuxarm64musl
.linux_arm_gnuLinuxarmglibc
.linux_arm_muslLinuxarmmusl
.macos_x64macOSx64n/a
.macos_arm64macOSarm64n/a
.windows_x64Windowsx64n/a
.windows_arm64Windowsarm64n/a
.freebsd_x64FreeBSDx64n/a

Override the default set with:

.platforms = &.{ .linux_x64_gnu, .macos_arm64 },

What addLib does

  1. Creates a Zig module from .root and adds the napi import.
  2. Adds any .imports to the module.
  3. Builds a dynamic library named <name>.node.
  4. Configures linker flags per OS:
    • macOS: linker_allow_shlib_undefined = true.
    • Linux/FreeBSD: links libc, restricts exports to N-API entry points via exports.ld.
    • Windows: generates an import library from node_api.def so the linker resolves N-API symbols against the host .exe at runtime.
  5. Drops red zone, unwind tables, and unreferenced sections (smaller binaries, no surprise symbols).
  6. If .npm is set, installs the .d.ts next to the binary in the right format.
  7. If both .npm is set and -Dnpm=true is passed, generates the full cross-compile graph and the npm/ package tree.

napikit build --release is exactly zig build -Dnpm=true -Doptimize=ReleaseFast with the per-platform target loop applied to every entry in .npm.platforms. You can run it directly if you prefer.

Two build options narrow that loop, set for you by the matching CLI flags:

Build optionCLI flagEffect
-Dnpm-only=a,b--only a,bOnly run the npm release for addons whose .name is listed.
-Dnpm-host=true--currentCross-compile only the host platform instead of .platforms.

Under -Dnpm-host the main package.json still lists every platform in optionalDependencies, so a later full build stays complete. See Building a subset.