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
--platformBuilt-ins kept externalbrowser field
web (default)noneused
nodenode:* and Node core modules (fs, path, http, crypto, …, incl. subpaths like fs/promises)ignored
denonode:*, 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 (pathnode: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:

  • webimport, module, browser, default.
  • nodeimport, module, node, default.
  • denoimport, 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:

  • web prefers browser, then module, then main — so a package that ships a browser-specific build is used on the web.
  • node / deno ignore browser and prefer module, then main — so the same package resolves to its Node/universal build instead.

Not covered yet

  • development / production conditions. The active condition set is platform-derived; netpack does not yet toggle development / production exports conditions 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.