Platforms
--platform tells netpack which runtime a bundle targets. Like esbuild’s option
of the same name, it decides two things: which bare specifiers are runtime
built-ins (provided by the platform, so netpack keeps them external instead of
bundling a copy) and how a dependency’s package.json entry point is chosen.
npx netpack bundle src/main.js --platform web # default
npx netpack bundle src/server.js --platform node
npx netpack bundle src/main.ts --platform deno
--platform | Built-ins kept external | browser field |
|---|---|---|
web (default) | none | used |
node | node:* and Node core modules (fs, path, http, crypto, …, incl. subpaths like fs/promises) | ignored |
deno | node:*, npm:*, jsr:* | ignored |
Built-ins stay external
A built-in for the target platform is left as a bare import — it is never bundled, because the runtime supplies it:
// in (built with --platform node):
import { readFile } from 'node:fs/promises';
import path from 'path';
// out (esm): the imports are hoisted, bare core names get the `node:` scheme
import { readFile } from "node:fs/promises";
import path from "node:path";
Under --platform node, both the node: scheme and the classic bare names (fs,
path, crypto, test, …) — including subpaths such as fs/promises — are
recognised. A bare core name is emitted under the canonical node: scheme
(path → node:path); an already-prefixed specifier is left as-is (never
node:node:).
A bare name is resolved locally first, so a local module or an installed
package that shares a core module’s name wins — import 'test' picks up your
node_modules/test if it exists, and only falls back to node:test when nothing
resolves. (This differs slightly from Node, where the core module always wins; it
lets you deliberately shadow a built-in.)
Under --platform deno, Deno’s URL-like schemes (node:, npm:, jsr:) are kept
external for the Deno runtime to resolve.
Under --platform web (the default) nothing is treated as a built-in: everything
resolves through node_modules (or an import map / external),
so importing a Node core module on the web is a resolution error rather than a
silent external — which is what you want when shipping to a browser.
This composes with the output format: the external is wired
up the format’s way, e.g. a real import in ESM, a require("node:fs/promises")
in CommonJS, and so on.
Entry-point selection
When a dependency declares an exports
field, that field is authoritative: only the subpaths it lists are importable,
and the legacy browser / module / main fields are ignored. netpack resolves
exports following Node’s algorithm — subpath maps (".", "./feature"),
"*" wildcard patterns, fallback arrays, and null blocking — selecting a
target from the active conditions, which vary by platform:
web—import,module,browser,default.node—import,module,node,default.deno—import,module,deno,node,default.
netpack is ESM-first, so import / module lead and require is deliberately
omitted: a dual package resolves to its ESM entry (better tree-shaking), while a
CJS-only package still resolves through the always-matched default condition or
the legacy fallback below.
Packages without an exports field fall back to top-level field selection,
which the platform also governs:
webprefersbrowser, thenmodule, thenmain— so a package that ships a browser-specific build is used on the web.node/denoignorebrowserand prefermodule, thenmain— so the same package resolves to its Node/universal build instead.
Not covered yet
development/productionconditions. The active condition set is platform-derived; netpack does not yet toggledevelopment/productionexportsconditions from the build mode.- Platform globals / defines. A platform does not (yet) inject or gate
runtime globals (for example service-worker or
Deno.*APIs) beyond the built-in resolution described here; netpack does not type-check, so these are left to the runtime.