Programmatic use (Node.js)
Besides the CLI, the netpack npm package exposes a
small, fully-typed API for driving the bundler from a Node.js program — handy in
build scripts and custom tooling.
Prefer bundling in-process from .NET (ASP.NET, MSBuild, custom tools)? Use the .NET libraries instead — no binary spawn.
The netpack object
netpack mirrors the commands, with typed options and a Promise result. Commands
that produce data (graph, analyze) resolve with the parsed JSON; the others
resolve once the process exits (and reject on a non-zero exit code).
import { netpack } from "netpack";
// Production build
await netpack.bundle("src/index.html", { minify: true, entryNames: "[name]-[hash]" });
// Inspect the dependency graph as JSON
const { graph } = await netpack.graph("src/index.html");
// Analyze the bundle, getting the analysis data back
const { analysis } = await netpack.analyze("src/index.html");
The available functions are bundle, serve, graph, analyze, and inspect.
Options are typed per command and mirror the CLI flags (camelCase, so
--entry-names becomes entryNames, --public-path becomes publicPath, and so
on).
Presets
bundle, serve, and analyze accept a preset option, mirroring the
--preset flag. Pass a reference (a path to a
netpack.json-style file, or a module name resolved like a dependency):
await netpack.bundle("src/index.html", { preset: "./netpack.prod.json" });
await netpack.bundle("src/index.html", { preset: ["@acme/netpack-preset", "./local.json"] });
You can also pass an inline preset object — the API writes it to a temporary
file and points --preset at it, so it feels like configuring the preset in
code:
await netpack.bundle("src/index.html", {
preset: {
minify: true,
define: { __VERSION__: '"1.4.0"' },
hooks: ["./build/my-hook.js"], // relative paths are resolved from cwd
},
});
Relative paths inside an inline preset (./…, ../…) are resolved against the
current working directory before the temp file is written, so references such as
hooks or composed presets keep working. Options passed directly to the command
still take precedence over preset values.
Long-running commands
serve (and bundle with watch: true) run until stopped. Pass an
AbortSignal to control their lifetime:
import { netpack } from "netpack";
const controller = new AbortController();
netpack.serve("src/index.html", { port: 3000, signal: controller.signal });
// …later, to stop the dev server:
controller.abort();
Reacting to (re)builds
serve and bundle({ watch: true }) accept three callbacks so a script can
react to the build lifecycle — most usefully, to know when a rebuild has
finished after a file change:
import { netpack } from "netpack";
const controller = new AbortController();
netpack.serve("src/index.html", {
signal: controller.signal,
onStart: () => console.log("build started…"),
onBuild: ({ initial, durationMs }) =>
console.log(initial ? "first build done" : `rebuilt in ${durationMs ?? "?"} ms`),
onError: (err) => console.error("rebuild failed:", err.message),
});
onStart()— a build or rebuild has begun.onBuild({ initial, durationMs })— a build finished successfully.initialisfalsefor rebuilds triggered by a file change;durationMsis the reported build time when available.onError(error)— a rebuild failed. Underserve/watchthe process keeps running, so more builds can still follow.
These are implemented purely in the npm layer by watching the binary’s normal
stdout/stderr for its build markers — there’s no extra IPC channel, and all
output still reaches your terminal. They’re a no-op for one-shot bundle calls
except that onBuild/onError fire once for that single build.
Low-level run
For full control there’s the lower-level run, which spawns the binary with raw
arguments and returns the child process. Its stdio is inherited, so the bundler’s
output appears in your terminal:
import { run } from "netpack";
run(["bundle", "src/index.html", "--minify"]);
run("bundle", { minify: true }); // command + options form
The netpack object is built on top of run, so anything the CLI can do is
reachable either way.